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_bytes_file::StubBytesFile;
11use crate::vfs::pseudo::stub_empty_file::StubEmptyFile;
12use crate::vfs::{FileSystemHandle, FsStr, FsString};
13use starnix_logging::bug_ref;
14use starnix_uapi::file_mode::mode;
15use std::sync::{Arc, OnceLock};
16
17/// Owner of all the KObjects in sysfs.
18///
19/// Holds strong references to the KObjects that are visible in sysfs. These
20/// objects are organized into hierarchies that make it easier to implement sysfs.
21pub struct KObjectStore {
22    /// Root of the sysfs hierarchy.
23    pub root: Arc<SimpleDirectory>,
24
25    /// Sysfs filesystem in which the KObjects are stored.
26    fs: OnceLock<FileSystemHandle>,
27
28    /// Root `/sys/devices/virtual` device.
29    virtual_device: OnceLock<Device>,
30
31    /// Root `/sys/devices/platform` device.
32    platform_device: OnceLock<Device>,
33
34    /// Root `/sys/devices/system` device.
35    system_device: OnceLock<Device>,
36
37    /// Root `/sys/devices/platform/soc` bus device.
38    soc_device: OnceLock<Device>,
39}
40
41impl KObjectStore {
42    pub fn init(&self, kernel: &Kernel) {
43        self.fs.set(get_sysfs(kernel)).unwrap();
44        self.register_initial_devices(kernel);
45    }
46
47    fn register_initial_devices(&self, kernel: &Kernel) {
48        let registry = &kernel.device_registry;
49
50        // Generic Linux device and class registrations.
51        register_cpu_devices(kernel);
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): Populate or remove stub powercap and udc classes.
66        self.get_or_create_class("powercap".into());
67        self.get_or_create_class("udc".into());
68
69        // Board / SoC-specific stub device registrations.
70        // TODO(https://fxbug.dev/452096300): Replace hardcoded board/SoC stub devices with dynamic
71        // device configuration.
72        let platform_bus = self.platform_bus();
73        let soc = self.soc_device();
74
75        // TODO(https://fxbug.dev/452096300): Stub Qualcomm SPMI PMIC and battery gauge IIO device.
76        let spmi = registry.add_bus_device(
77            "1c40000.qcom,spmi".into(),
78            Some(soc.clone()),
79            platform_bus.clone(),
80            build_device_directory,
81        );
82        let spmi_bus = self.get_or_create_bus("spmi".into());
83        let spmi_0 = registry.add_bus_device(
84            "spmi-0".into(),
85            Some(spmi),
86            spmi_bus.clone(),
87            build_device_directory,
88        );
89        let spmi_0_00 =
90            registry.add_bus_device("0-00".into(), Some(spmi_0), spmi_bus, build_device_directory);
91        let qbg = registry.add_bus_device(
92            "1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00".into(),
93            Some(spmi_0_00),
94            platform_bus.clone(),
95            build_device_directory,
96        );
97        let iio_bus = self.get_or_create_bus("iio".into());
98        registry.add_bus_device("iio:device3".into(), Some(qbg), iio_bus, |device, dir| {
99            build_device_directory(device, dir);
100            add_stub_attributes(dir, &["in_resistance_resistance_id_input"]);
101        });
102
103        // TODO(https://fxbug.dev/452096300): Stub Qualcomm MDSS display controller and DRM
104        // connector.
105        let mdss_mdp = registry.add_bus_device(
106            "5e00000.qcom,mdss_mdp".into(),
107            Some(soc),
108            platform_bus,
109            build_device_directory,
110        );
111        let drm_class = self.get_or_create_class("drm".into());
112        let card0 = registry.add_numberless_device(
113            "card0".into(),
114            Some(mdss_mdp.clone()),
115            drm_class.clone(),
116            build_device_directory,
117        );
118        registry.add_numberless_device(
119            "sde-conn-0-DSI-1".into(),
120            Some(card0.clone()),
121            drm_class.clone(),
122            |device, dir| {
123                build_device_directory(device, dir);
124                dir.entry(
125                    "display_power_state",
126                    StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
127                    mode!(IFREG, 0o644),
128                );
129                dir.entry(
130                    "panel_power_state",
131                    StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
132                    mode!(IFREG, 0o644),
133                );
134            },
135        );
136        registry.add_numberless_device(
137            "card0-DSI-1".into(),
138            Some(card0),
139            drm_class,
140            |device, dir| {
141                build_device_directory(device, dir);
142                dir.subdir("panel", 0o755, |dir| {
143                    dir.entry(
144                        "panel_name",
145                        StubBytesFile::new_node_with_data(
146                            bug_ref!("https://fxbug.dev/560212202"),
147                            b"Placeholder",
148                        ),
149                        mode!(IFREG, 0o444),
150                    );
151                });
152            },
153        );
154
155        // TODO(https://fxbug.dev/452096300): Stub panel backlight device parented by the display
156        // controller.
157        registry.add_numberless_device(
158            "panel0-backlight".into(),
159            Some(mdss_mdp),
160            self.get_or_create_class("backlight".into()),
161            |device, dir| {
162                build_device_directory(device, dir);
163                add_stub_attributes(dir, &["brightness"]);
164            },
165        );
166
167        registry.add_numberless_device(
168            "battery".into(),
169            /* parent = */ None,
170            self.get_or_create_class("power_supply".into()),
171            |device, dir| {
172                build_device_directory(device, dir);
173                dir.entry(
174                    "capacity",
175                    StubBytesFile::new_node_with_data(
176                        bug_ref!("https://fxbug.dev/573313995"),
177                        b"0",
178                    ),
179                    mode!(IFREG, 0o644),
180                );
181            },
182        );
183
184        registry.add_subsystemless_device(
185            "goog-chip-info".into(),
186            Some(self.system_device()),
187            |device, dir| {
188                build_device_directory(device, dir);
189                for name in ["device_table", "dvfs_table", "ids_table"] {
190                    dir.entry(
191                        name,
192                        StubBytesFile::new_node_with_data(
193                            bug_ref!("https://fxbug.dev/560212202"),
194                            b"Placeholder",
195                        ),
196                        mode!(IFREG, 0o444),
197                    );
198                }
199            },
200        );
201
202        // TODO(https://fxbug.dev/452096300): Stub eMMC host, card, and block device. The card's
203        // `block/mmcblk0` directory is left as a plain stub rather than a `block` class device,
204        // since there is no backing block device.
205        // TODO(https://fxbug.dev/425942145): Parent `mmc0` under the SoC's SDHCI controller
206        // platform device, once the product-specific controller address is known.
207        let mmc0 = registry.add_numberless_device(
208            "mmc0".into(),
209            /* parent = */ None,
210            self.get_or_create_class("mmc_host".into()),
211            build_device_directory,
212        );
213        registry.add_bus_device(
214            "mmc0:0001".into(),
215            Some(mmc0),
216            self.get_or_create_bus("mmc".into()),
217            |device, dir| {
218                build_device_directory(device, dir);
219                add_stub_attributes(
220                    dir,
221                    &["fwrev", "hwrev", "life_time", "manfid", "pre_eol_info", "serial"],
222                );
223                dir.subdir("block", 0o755, |dir| {
224                    dir.subdir("mmcblk0", 0o755, |dir| {
225                        add_stub_attributes(dir, &["size"]);
226                    });
227                });
228            },
229        );
230
231        // TODO(https://fxbug.dev/452096300): Stub SoC identification device (`/sys/devices/soc0`),
232        // attached to the `soc` bus.
233        registry.add_bus_device(
234            "soc0".into(),
235            /* parent = */ None,
236            self.get_or_create_bus("soc".into()),
237            |device, dir| {
238                build_device_directory(device, dir);
239                add_stub_attributes(dir, &["revision", "serial_number"]);
240            },
241        );
242    }
243
244    fn fs(&self) -> &FileSystemHandle {
245        self.fs.get().expect("sysfs should be initialized")
246    }
247
248    /// Device bus used for platform devices (`/sys/bus/platform`).
249    pub fn platform_bus(&self) -> Bus {
250        self.get_or_create_bus("platform".into())
251    }
252
253    /// Root device used for virtual devices (`/sys/devices/virtual`).
254    pub fn virtual_device(&self) -> Device {
255        self.virtual_device
256            .get_or_init(|| {
257                self.create_device(
258                    "virtual".into(),
259                    /* parent = */ None,
260                    /* subsystem = */ None,
261                    /* metadata = */ None,
262                    |_, _| {},
263                )
264            })
265            .clone()
266    }
267
268    /// Root device used for platform devices (`/sys/devices/platform`).
269    pub fn platform_device(&self) -> Device {
270        self.platform_device
271            .get_or_init(|| {
272                self.create_device(
273                    "platform".into(),
274                    /* parent = */ None,
275                    /* subsystem = */ None,
276                    /* metadata = */ None,
277                    build_device_directory,
278                )
279            })
280            .clone()
281    }
282
283    /// Root device used for system devices (`/sys/devices/system`).
284    ///
285    /// Subsystems such as `cpu` place their root device beneath this device.
286    pub fn system_device(&self) -> Device {
287        self.system_device
288            .get_or_init(|| {
289                self.create_device(
290                    "system".into(),
291                    /* parent = */ None,
292                    /* subsystem = */ None,
293                    /* metadata = */ None,
294                    |_, _| {},
295                )
296            })
297            .clone()
298    }
299
300    /// Bus device used for SoC peripheral devices (`/sys/devices/platform/soc`).
301    pub fn soc_device(&self) -> Device {
302        // TODO(https://fxbug.dev/452096300): Replace hardcoded SoC bus device with dynamic board
303        // configuration.
304        self.soc_device
305            .get_or_init(|| {
306                self.create_device(
307                    "soc".into(),
308                    Some(self.platform_device()),
309                    Some(self.platform_bus().into()),
310                    /* metadata = */ None,
311                    build_device_directory,
312                )
313            })
314            .clone()
315    }
316
317    /// Device class used for block devices.
318    pub fn block_class(&self) -> Class {
319        self.get_or_create_class("block".into())
320    }
321
322    /// Device class used for thermal devices.
323    pub fn thermal_class(&self) -> Class {
324        self.get_or_create_class("thermal".into())
325    }
326
327    /// Device class used for graphics devices.
328    pub fn graphics_class(&self) -> Class {
329        self.get_or_create_class("graphics".into())
330    }
331
332    /// Device class used for input devices.
333    pub fn input_class(&self) -> Class {
334        self.get_or_create_class("input".into())
335    }
336
337    /// Device class used for mem devices.
338    pub fn mem_class(&self) -> Class {
339        self.get_or_create_class("mem".into())
340    }
341
342    /// Device class used for net devices.
343    pub fn net_class(&self) -> Class {
344        self.get_or_create_class("net".into())
345    }
346
347    /// Device class used for misc devices.
348    pub fn misc_class(&self) -> Class {
349        self.get_or_create_class("misc".into())
350    }
351
352    /// Device class used for tty devices.
353    pub fn tty_class(&self) -> Class {
354        self.get_or_create_class("tty".into())
355    }
356
357    /// Device class used for dma_heap devices.
358    pub fn dma_heap_class(&self) -> Class {
359        self.get_or_create_class("dma_heap".into())
360    }
361
362    /// Incorrect device class.
363    ///
364    /// This class exposes the name "starnix" to userspace, which is incorrect. Instead, devices
365    /// should use a class that represents their usage rather than their implementation.
366    ///
367    /// This class exists because a number of devices incorrectly use this class. We should fix
368    /// those devices to report their proper class.
369    pub fn starnix_class(&self) -> Class {
370        self.get_or_create_class("starnix".into())
371    }
372
373    /// Real-time clock class.
374    ///
375    /// Becomes `/sys/class/rtc/...`.
376    pub fn rtc_class(&self) -> Class {
377        self.get_or_create_class("rtc".into())
378    }
379
380    fn ensure_dir(&self, path: &[&FsStr]) -> Arc<SimpleDirectory> {
381        let fs = self.fs();
382        let mut dir = self.root.clone();
383        for component in path {
384            dir = dir.subdir(fs, component, 0o755);
385        }
386        dir
387    }
388
389    fn lookup_dir(&self, path: &[&FsStr]) -> Option<Arc<SimpleDirectory>> {
390        let mut dir = self.root.clone();
391        for component in path {
392            dir = dir.get_dir(component)?;
393        }
394        Some(dir)
395    }
396
397    /// Get a bus by name.
398    ///
399    /// If the bus does not exist, this function will create it.
400    pub fn get_or_create_bus(&self, name: &FsStr) -> Bus {
401        let name = name.to_owned();
402        let devices = self.ensure_dir(&[b"bus".into(), name.as_ref(), b"devices".into()]);
403        Bus::new(name, devices)
404    }
405
406    /// Get a class by name.
407    ///
408    /// If the class does not exist, this function will create it.
409    pub fn get_or_create_class(&self, name: &FsStr) -> Class {
410        let name = name.to_owned();
411        let devices = self.ensure_dir(&[b"class".into(), name.as_ref()]);
412        Class::new(name, devices)
413    }
414
415    pub fn class_with_dir(
416        &self,
417        name: &FsStr,
418        build_directory: impl FnOnce(&SimpleDirectoryMutator),
419    ) -> Class {
420        let class = self.get_or_create_class(name);
421        class.devices().edit(self.fs(), build_directory);
422        class
423    }
424
425    /// Create a device and add that device to the store.
426    ///
427    /// Rather than use this function directly, you should register your device with the
428    /// [`DeviceRegistry`](crate::device::DeviceRegistry). The
429    /// [`DeviceRegistry`](crate::device::DeviceRegistry) will create the KObject for the device as
430    /// part of the registration process.
431    ///
432    /// If you create the device yourself, userspace will not be able to instantiate the
433    /// device because the [`DeviceId`](starnix_uapi::device_id::DeviceId) will not be registered
434    /// with the [`DeviceRegistry`](crate::device::DeviceRegistry).
435    pub(super) fn create_device(
436        &self,
437        name: &FsStr,
438        parent: Option<Device>,
439        subsystem: Option<Subsystem>,
440        metadata: Option<DeviceMetadata>,
441        build_directory: impl FnOnce(&Device, &SimpleDirectoryMutator),
442    ) -> Device {
443        let parent = match (parent, &subsystem) {
444            (None, Some(Subsystem::Class(_))) => Some(self.virtual_device()),
445            (parent, _) => parent,
446        };
447        let dir = if let Some(glue_dir) =
448            Device::glue_dir_name_for(parent.as_ref(), subsystem.as_ref())
449        {
450            let parent_dir = parent
451                .as_ref()
452                .and_then(Device::dir)
453                .expect("parent device directory exists in sysfs");
454            parent_dir.nested_subdir(self.fs(), glue_dir, 0o755, name, 0o755)
455        } else if let Some(parent) = parent.as_ref() {
456            let parent_dir = parent.dir().expect("parent device directory exists in sysfs");
457            parent_dir.subdir(self.fs(), name, 0o755)
458        } else {
459            self.ensure_dir(&[b"devices".into()]).subdir(self.fs(), name, 0o755)
460        };
461        let device = Device::new(name.to_owned(), parent, subsystem, metadata, dir.clone());
462        dir.edit(self.fs(), |mutator| {
463            build_directory(&device, mutator);
464        });
465        self.add(&device);
466        device
467    }
468
469    fn add(&self, device: &Device) {
470        let name = device.name();
471
472        // Insert the newly created device into various views.
473        match device.subsystem() {
474            Some(Subsystem::Bus(bus)) => {
475                bus.devices().edit(self.fs(), |dir| {
476                    dir.symlink(name, device.path_from_depth(3).as_ref());
477                });
478            }
479            Some(Subsystem::Class(class)) => {
480                class.devices().edit(self.fs(), |dir| {
481                    dir.symlink(name, device.path_from_depth(2).as_ref());
482                });
483            }
484            None => {}
485        }
486
487        if let Some(metadata) = device.metadata() {
488            let device_number = FsString::from(metadata.devt.to_string());
489            let dev_subdir: &FsStr = match metadata.mode {
490                DeviceMode::Block => {
491                    self.ensure_dir(&[b"block".into()]).edit(self.fs(), |dir| {
492                        dir.symlink(name, device.path_from_depth(1).as_ref());
493                    });
494                    b"block".into()
495                }
496                DeviceMode::Char => b"char".into(),
497            };
498            self.ensure_dir(&[b"dev".into(), dev_subdir]).edit(self.fs(), |dir| {
499                dir.symlink(device_number.as_ref(), device.path_from_depth(2).as_ref());
500            });
501        }
502    }
503
504    /// Destroy a device.
505    ///
506    /// This function removes the KObject for the device from the store.
507    ///
508    /// Most clients hold weak references to KObjects, which means those references will become
509    /// invalid shortly after this function is called.
510    pub(super) fn remove(&self, device: &Device) {
511        let name = device.name();
512        // Remove the device from its views in the reverse order in which it was added.
513        if let Some(metadata) = device.metadata() {
514            let device_number: FsString = metadata.devt.to_string().into();
515            let dev_subdir: &FsStr = match metadata.mode {
516                DeviceMode::Block => {
517                    if let Some(block_dir) = self.lookup_dir(&[b"block".into()]) {
518                        block_dir.remove(name);
519                    }
520                    b"block".into()
521                }
522                DeviceMode::Char => b"char".into(),
523            };
524            if let Some(dev_dir) = self.lookup_dir(&[b"dev".into(), dev_subdir]) {
525                dev_dir.remove(device_number.as_ref());
526            }
527        }
528        match device.subsystem() {
529            Some(Subsystem::Bus(bus)) => {
530                bus.devices().remove(name);
531            }
532            Some(Subsystem::Class(class)) => {
533                class.devices().remove(name);
534            }
535            None => {}
536        }
537        // Finally, remove the device from the object store.
538        if let Some(glue_dir) = device.glue_dir_name() {
539            if let Some(parent_dir) = device.parent().and_then(Device::dir) {
540                parent_dir.remove_from_subdir_if_empty(glue_dir, name);
541            }
542        } else if let Some(parent) = device.parent() {
543            if let Some(parent_dir) = parent.dir() {
544                parent_dir.remove(name);
545            }
546        } else if let Some(devices_dir) = self.lookup_dir(&[b"devices".into()]) {
547            devices_dir.remove(name);
548        }
549    }
550}
551
552impl Default for KObjectStore {
553    fn default() -> Self {
554        Self {
555            root: SimpleDirectory::new(),
556            fs: OnceLock::new(),
557            virtual_device: OnceLock::new(),
558            platform_device: OnceLock::new(),
559            system_device: OnceLock::new(),
560            soc_device: OnceLock::new(),
561        }
562    }
563}
564
565/// Adds read-only [`StubEmptyFile`] attributes named `names` to `dir`.
566///
567/// Reads of these attributes are tracked to identify which unimplemented sysfs attributes are used
568/// by userspace.
569fn add_stub_attributes(dir: &SimpleDirectoryMutator, names: &[&str]) {
570    for name in names {
571        dir.entry(
572            name,
573            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
574            mode!(IFREG, 0o444),
575        );
576    }
577}