Skip to main content

starnix_core/device/
kobject_store.rs

1// Copyright 2024 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::device::kobject::{Bus, Class, Device, DeviceMetadata, Subsystem};
7use crate::fs::sysfs::{build_device_directory, get_sysfs, register_cpu_devices};
8use crate::task::Kernel;
9use crate::vfs::pseudo::simple_directory::{SimpleDirectory, SimpleDirectoryMutator};
10use crate::vfs::pseudo::stub_empty_file::StubEmptyFile;
11use crate::vfs::{FileSystemHandle, FsStr, FsString};
12use starnix_logging::bug_ref;
13use starnix_uapi::file_mode::mode;
14use std::sync::{Arc, OnceLock};
15
16/// Owner of all the KObjects in sysfs.
17///
18/// Holds strong references to the KObjects that are visible in sysfs. These
19/// objects are organized into hierarchies that make it easier to implement sysfs.
20pub struct KObjectStore {
21    /// Root of the sysfs hierarchy.
22    pub root: Arc<SimpleDirectory>,
23
24    /// Sysfs filesystem in which the KObjects are stored.
25    fs: OnceLock<FileSystemHandle>,
26
27    /// Root `/sys/devices/virtual` device.
28    virtual_device: OnceLock<Device>,
29
30    /// Root `/sys/devices/platform` device.
31    platform_device: OnceLock<Device>,
32
33    /// Root `/sys/devices/system` device.
34    system_device: OnceLock<Device>,
35
36    /// Root `/sys/devices/platform/soc` bus device.
37    soc_device: OnceLock<Device>,
38}
39
40impl KObjectStore {
41    pub fn init(&self, kernel: &Kernel) {
42        self.fs.set(get_sysfs(kernel)).unwrap();
43        self.register_initial_devices(kernel);
44    }
45
46    fn register_initial_devices(&self, kernel: &Kernel) {
47        let registry = &kernel.device_registry;
48
49        // Generic Linux device and class registrations.
50        register_cpu_devices(kernel);
51        let _ = self.power_supply_class();
52
53        // TODO(https://fxbug.dev/452096300): Register backing_dev_info devices from actual
54        // superblocks instead of a hardcoded 0:80 stub.
55        registry.add_numberless_device(
56            "0:80".into(),
57            /* parent = */ None,
58            self.get_or_create_class("bdi".into()),
59            |device, dir| {
60                build_device_directory(device, dir);
61                add_stub_attributes(dir, &["max_ratio", "read_ahead_kb"]);
62            },
63        );
64
65        // TODO(https://fxbug.dev/452096300): Replace hardcoded board/SoC stub devices with dynamic
66        // device configuration.
67        // Eagerly create `/sys/devices/platform/soc` and `/sys/bus/platform/devices/soc`.
68        let _ = self.soc_device();
69
70        // TODO(https://fxbug.dev/452096300): Stub SoC identification device (`/sys/devices/soc0`),
71        // attached to the `soc` bus.
72        registry.add_bus_device(
73            "soc0".into(),
74            /* parent = */ None,
75            self.get_or_create_bus("soc".into()),
76            |device, dir| {
77                build_device_directory(device, dir);
78                add_stub_attributes(dir, &["revision", "serial_number"]);
79            },
80        );
81    }
82
83    pub(super) fn fs(&self) -> &FileSystemHandle {
84        self.fs.get().expect("sysfs should be initialized")
85    }
86
87    /// Device bus used for platform devices (`/sys/bus/platform`).
88    pub fn platform_bus(&self) -> Bus {
89        self.get_or_create_bus("platform".into())
90    }
91
92    /// Root device used for virtual devices (`/sys/devices/virtual`).
93    pub fn virtual_device(&self) -> Device {
94        self.virtual_device
95            .get_or_init(|| {
96                self.create_device(
97                    "virtual".into(),
98                    /* parent = */ None,
99                    /* subsystem = */ None,
100                    /* metadata = */ None,
101                    |_, _| {},
102                )
103            })
104            .clone()
105    }
106
107    /// Root device used for platform devices (`/sys/devices/platform`).
108    pub fn platform_device(&self) -> Device {
109        self.platform_device
110            .get_or_init(|| {
111                self.create_device(
112                    "platform".into(),
113                    /* parent = */ None,
114                    /* subsystem = */ None,
115                    /* metadata = */ None,
116                    build_device_directory,
117                )
118            })
119            .clone()
120    }
121
122    /// Root device used for system devices (`/sys/devices/system`).
123    ///
124    /// Subsystems such as `cpu` place their root device beneath this device.
125    pub fn system_device(&self) -> Device {
126        self.system_device
127            .get_or_init(|| {
128                self.create_device(
129                    "system".into(),
130                    /* parent = */ None,
131                    /* subsystem = */ None,
132                    /* metadata = */ None,
133                    |_, _| {},
134                )
135            })
136            .clone()
137    }
138
139    /// Bus device used for SoC peripheral devices (`/sys/devices/platform/soc`).
140    pub fn soc_device(&self) -> Device {
141        // TODO(https://fxbug.dev/452096300): Replace hardcoded SoC bus device with dynamic board
142        // configuration.
143        self.soc_device
144            .get_or_init(|| {
145                self.create_device(
146                    "soc".into(),
147                    Some(self.platform_device()),
148                    Some(self.platform_bus().into()),
149                    /* metadata = */ None,
150                    build_device_directory,
151                )
152            })
153            .clone()
154    }
155
156    /// Device class used for block devices.
157    pub fn block_class(&self) -> Class {
158        self.get_or_create_class("block".into())
159    }
160
161    /// Device class used for thermal devices.
162    pub fn thermal_class(&self) -> Class {
163        self.get_or_create_class("thermal".into())
164    }
165
166    /// Device class used for powercap devices.
167    pub fn powercap_class(&self) -> Class {
168        self.get_or_create_class("powercap".into())
169    }
170
171    /// Device class used for power-supply devices (`/sys/class/power_supply`).
172    pub fn power_supply_class(&self) -> Class {
173        self.get_or_create_class("power_supply".into())
174    }
175
176    /// Device class used for graphics devices.
177    pub fn graphics_class(&self) -> Class {
178        self.get_or_create_class("graphics".into())
179    }
180
181    /// Device class used for input devices.
182    pub fn input_class(&self) -> Class {
183        self.get_or_create_class("input".into())
184    }
185
186    /// Device class used for mem devices.
187    pub fn mem_class(&self) -> Class {
188        self.get_or_create_class("mem".into())
189    }
190
191    /// Device class used for net devices.
192    pub fn net_class(&self) -> Class {
193        self.get_or_create_class("net".into())
194    }
195
196    /// Device class used for misc devices.
197    pub fn misc_class(&self) -> Class {
198        self.get_or_create_class("misc".into())
199    }
200
201    /// Device class used for tty devices.
202    pub fn tty_class(&self) -> Class {
203        self.get_or_create_class("tty".into())
204    }
205
206    /// Device class used for dma_heap devices.
207    pub fn dma_heap_class(&self) -> Class {
208        self.get_or_create_class("dma_heap".into())
209    }
210
211    /// Incorrect device class.
212    ///
213    /// This class exposes the name "starnix" to userspace, which is incorrect. Instead, devices
214    /// should use a class that represents their usage rather than their implementation.
215    ///
216    /// This class exists because a number of devices incorrectly use this class. We should fix
217    /// those devices to report their proper class.
218    pub fn starnix_class(&self) -> Class {
219        self.get_or_create_class("starnix".into())
220    }
221
222    /// Real-time clock class.
223    ///
224    /// Becomes `/sys/class/rtc/...`.
225    pub fn rtc_class(&self) -> Class {
226        self.get_or_create_class("rtc".into())
227    }
228
229    fn ensure_dir(&self, path: &[&FsStr]) -> Arc<SimpleDirectory> {
230        let fs = self.fs();
231        let mut dir = self.root.clone();
232        for component in path {
233            dir = dir.subdir(fs, component, 0o755);
234        }
235        dir
236    }
237
238    fn lookup_dir(&self, path: &[&FsStr]) -> Option<Arc<SimpleDirectory>> {
239        let mut dir = self.root.clone();
240        for component in path {
241            dir = dir.get_dir(component)?;
242        }
243        Some(dir)
244    }
245
246    /// Get a bus by name.
247    ///
248    /// If the bus does not exist, this function will create it.
249    pub fn get_or_create_bus(&self, name: &FsStr) -> Bus {
250        let name = name.to_owned();
251        let devices = self.ensure_dir(&[b"bus".into(), name.as_ref(), b"devices".into()]);
252        Bus::new(name, devices)
253    }
254
255    /// Get a class by name.
256    ///
257    /// If the class does not exist, this function will create it.
258    pub fn get_or_create_class(&self, name: &FsStr) -> Class {
259        let name = name.to_owned();
260        let devices = self.ensure_dir(&[b"class".into(), name.as_ref()]);
261        Class::new(name, devices)
262    }
263
264    pub fn class_with_dir(
265        &self,
266        name: &FsStr,
267        build_directory: impl FnOnce(&SimpleDirectoryMutator),
268    ) -> Class {
269        let class = self.get_or_create_class(name);
270        class.devices().edit(self.fs(), build_directory);
271        class
272    }
273
274    /// Create a device and add that device to the store.
275    ///
276    /// Rather than use this function directly, you should register your device with the
277    /// [`DeviceRegistry`](crate::device::DeviceRegistry). The
278    /// [`DeviceRegistry`](crate::device::DeviceRegistry) will create the KObject for the device as
279    /// part of the registration process.
280    ///
281    /// If you create the device yourself, userspace will not be able to instantiate the
282    /// device because the [`DeviceId`](starnix_uapi::device_id::DeviceId) will not be registered
283    /// with the [`DeviceRegistry`](crate::device::DeviceRegistry).
284    pub(super) fn create_device(
285        &self,
286        name: &FsStr,
287        parent: Option<Device>,
288        subsystem: Option<Subsystem>,
289        metadata: Option<DeviceMetadata>,
290        build_directory: impl FnOnce(&Device, &SimpleDirectoryMutator),
291    ) -> Device {
292        let parent = match (parent, &subsystem) {
293            (None, Some(Subsystem::Class(_))) => Some(self.virtual_device()),
294            (parent, _) => parent,
295        };
296        let dir = if let Some(glue_dir) =
297            Device::glue_dir_name_for(parent.as_ref(), subsystem.as_ref())
298        {
299            let parent_dir = parent
300                .as_ref()
301                .and_then(Device::dir)
302                .expect("parent device directory exists in sysfs");
303            parent_dir.nested_subdir(self.fs(), glue_dir, 0o755, name, 0o755)
304        } else if let Some(parent) = parent.as_ref() {
305            let parent_dir = parent.dir().expect("parent device directory exists in sysfs");
306            parent_dir.subdir(self.fs(), name, 0o755)
307        } else {
308            self.ensure_dir(&[b"devices".into()]).subdir(self.fs(), name, 0o755)
309        };
310        let device = Device::new(name.to_owned(), parent, subsystem, metadata, dir.clone());
311        dir.edit(self.fs(), |mutator| {
312            build_directory(&device, mutator);
313        });
314        self.add(&device);
315        device
316    }
317
318    fn add(&self, device: &Device) {
319        let name = device.name();
320
321        // Insert the newly created device into various views.
322        match device.subsystem() {
323            Some(Subsystem::Bus(bus)) => {
324                bus.devices().edit(self.fs(), |dir| {
325                    dir.symlink(name, device.path_from_depth(3).as_ref());
326                });
327            }
328            Some(Subsystem::Class(class)) => {
329                class.devices().edit(self.fs(), |dir| {
330                    dir.symlink(name, device.path_from_depth(2).as_ref());
331                });
332            }
333            None => {}
334        }
335
336        if let Some(metadata) = device.metadata() {
337            let device_number = FsString::from(metadata.devt.to_string());
338            let dev_subdir: &FsStr = match metadata.mode {
339                DeviceMode::Block => {
340                    self.ensure_dir(&[b"block".into()]).edit(self.fs(), |dir| {
341                        dir.symlink(name, device.path_from_depth(1).as_ref());
342                    });
343                    b"block".into()
344                }
345                DeviceMode::Char => b"char".into(),
346            };
347            self.ensure_dir(&[b"dev".into(), dev_subdir]).edit(self.fs(), |dir| {
348                dir.symlink(device_number.as_ref(), device.path_from_depth(2).as_ref());
349            });
350        }
351    }
352
353    /// Destroy a device.
354    ///
355    /// This function removes the KObject for the device from the store.
356    ///
357    /// Most clients hold weak references to KObjects, which means those references will become
358    /// invalid shortly after this function is called.
359    pub(super) fn remove(&self, device: &Device) {
360        let name = device.name();
361        // Remove the device from its views in the reverse order in which it was added.
362        if let Some(metadata) = device.metadata() {
363            let device_number: FsString = metadata.devt.to_string().into();
364            let dev_subdir: &FsStr = match metadata.mode {
365                DeviceMode::Block => {
366                    if let Some(block_dir) = self.lookup_dir(&[b"block".into()]) {
367                        block_dir.remove(name);
368                    }
369                    b"block".into()
370                }
371                DeviceMode::Char => b"char".into(),
372            };
373            if let Some(dev_dir) = self.lookup_dir(&[b"dev".into(), dev_subdir]) {
374                dev_dir.remove(device_number.as_ref());
375            }
376        }
377        match device.subsystem() {
378            Some(Subsystem::Bus(bus)) => {
379                bus.devices().remove(name);
380            }
381            Some(Subsystem::Class(class)) => {
382                class.devices().remove(name);
383            }
384            None => {}
385        }
386        // Finally, remove the device from the object store.
387        if let Some(glue_dir) = device.glue_dir_name() {
388            if let Some(parent_dir) = device.parent().and_then(Device::dir) {
389                parent_dir.remove_from_subdir_if_empty(glue_dir, name);
390            }
391        } else if let Some(parent) = device.parent() {
392            if let Some(parent_dir) = parent.dir() {
393                parent_dir.remove(name);
394            }
395        } else if let Some(devices_dir) = self.lookup_dir(&[b"devices".into()]) {
396            devices_dir.remove(name);
397        }
398    }
399}
400
401impl Default for KObjectStore {
402    fn default() -> Self {
403        Self {
404            root: SimpleDirectory::new(),
405            fs: OnceLock::new(),
406            virtual_device: OnceLock::new(),
407            platform_device: OnceLock::new(),
408            system_device: OnceLock::new(),
409            soc_device: OnceLock::new(),
410        }
411    }
412}
413
414/// Adds read-only [`StubEmptyFile`] attributes named `names` to `dir`.
415///
416/// Reads of these attributes are tracked to identify which unimplemented sysfs attributes are used
417/// by userspace.
418fn add_stub_attributes(dir: &SimpleDirectoryMutator, names: &[&str]) {
419    for name in names {
420        dir.entry(
421            name,
422            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
423            mode!(IFREG, 0o444),
424        );
425    }
426}