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};
7use crate::fs::sysfs::get_sysfs;
8use crate::task::Kernel;
9use crate::vfs::pseudo::simple_directory::{SimpleDirectory, SimpleDirectoryMutator};
10use crate::vfs::{FileSystemHandle, FsStr, FsString};
11use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked};
12use std::sync::{Arc, OnceLock};
13
14/// The owner of all the KObjects in sysfs.
15///
16/// This structure holds strong references to the KObjects that are visible in sysfs. These
17/// objects are organized into hierarchies that make it easier to implement sysfs.
18pub struct KObjectStore {
19    /// The root of the sysfs hierarchy.
20    pub root: Arc<SimpleDirectory>,
21
22    /// The sysfs filesystem in which the KObjects are stored.
23    fs: OnceLock<FileSystemHandle>,
24}
25
26impl KObjectStore {
27    pub fn init<L>(&self, locked: &mut Locked<L>, kernel: &Kernel)
28    where
29        L: LockEqualOrBefore<FileOpsCore>,
30    {
31        self.fs.set(get_sysfs(locked, kernel)).unwrap();
32    }
33
34    fn fs(&self) -> &FileSystemHandle {
35        self.fs.get().expect("sysfs should be initialized")
36    }
37
38    /// The virtual bus kobject where all virtual and pseudo devices are stored.
39    pub fn virtual_bus(&self) -> Bus {
40        let name: FsString = b"virtual".into();
41        let dir = self.ensure_dir(&[b"devices".into(), name.as_ref()]);
42        Bus::new(name, dir, None)
43    }
44
45    /// The device class used for virtual block devices.
46    pub fn virtual_block_class(&self) -> Class {
47        self.get_or_create_class("block".into(), self.virtual_bus())
48    }
49
50    /// The device class used for virtual thermal devices.
51    pub fn virtual_thermal_class(&self) -> Class {
52        self.get_or_create_class("thermal".into(), self.virtual_bus())
53    }
54
55    /// The device class used for virtual graphics devices.
56    pub fn graphics_class(&self) -> Class {
57        self.get_or_create_class("graphics".into(), self.virtual_bus())
58    }
59
60    /// The device class used for virtual input devices.
61    pub fn input_class(&self) -> Class {
62        self.get_or_create_class("input".into(), self.virtual_bus())
63    }
64
65    /// The device class used for virtual mem devices.
66    pub fn mem_class(&self) -> Class {
67        self.get_or_create_class("mem".into(), self.virtual_bus())
68    }
69
70    /// The device class used for virtual net devices.
71    pub fn net_class(&self) -> Class {
72        self.get_or_create_class("net".into(), self.virtual_bus())
73    }
74
75    /// The device class used for virtual misc devices.
76    pub fn misc_class(&self) -> Class {
77        self.get_or_create_class("misc".into(), self.virtual_bus())
78    }
79
80    /// The device class used for virtual tty devices.
81    pub fn tty_class(&self) -> Class {
82        self.get_or_create_class("tty".into(), self.virtual_bus())
83    }
84
85    /// The device class used for virtual dma_heap devices.
86    pub fn dma_heap_class(&self) -> Class {
87        self.get_or_create_class("dma_heap".into(), self.virtual_bus())
88    }
89
90    /// An incorrect device class.
91    ///
92    /// This class exposes the name "starnix" to userspace, which is incorrect. Instead, devices
93    /// should use a class that represents their usage rather than their implementation.
94    ///
95    /// This class exists because a number of devices incorrectly use this class. We should fix
96    /// those devices to report their proper class.
97    pub fn starnix_class(&self) -> Class {
98        self.get_or_create_class("starnix".into(), self.virtual_bus())
99    }
100
101    fn ensure_dir(&self, path: &[&FsStr]) -> Arc<SimpleDirectory> {
102        let fs = self.fs();
103        let mut dir = self.root.clone();
104        for component in path {
105            dir = dir.subdir(fs, component, 0o755);
106        }
107        dir
108    }
109
110    fn edit_dir(&self, path: &[&FsStr], callback: impl FnOnce(&SimpleDirectoryMutator)) {
111        let dir = self.ensure_dir(path);
112        let mutator = SimpleDirectoryMutator::new(self.fs().clone(), dir);
113        callback(&mutator);
114    }
115
116    /// Get a bus by name.
117    ///
118    /// If the bus does not exist, this function will create it.
119    pub fn get_or_create_bus(&self, name: &FsStr) -> Bus {
120        let name = name.to_owned();
121        let dir = self.ensure_dir(&[b"devices".into(), name.as_ref()]);
122        let collection = self.ensure_dir(&[b"bus".into(), name.as_ref(), b"devices".into()]);
123        Bus::new(name, dir, Some(collection))
124    }
125
126    /// Get a class by name.
127    ///
128    /// If the bus does not exist, this function will create it.
129    pub fn get_or_create_class(&self, name: &FsStr, bus: Bus) -> Class {
130        let name = name.to_owned();
131        let dir = bus.dir.subdir(self.fs(), name.as_ref(), 0o755);
132        let collection = self.ensure_dir(&[b"class".into(), name.as_ref()]);
133        Class::new(name, dir, bus, collection)
134    }
135
136    pub fn class_with_dir(
137        &self,
138        name: &FsStr,
139        bus: Bus,
140        build_collection: impl FnOnce(&SimpleDirectoryMutator),
141    ) -> Class {
142        let class = self.get_or_create_class(name, bus);
143        let mutator = SimpleDirectoryMutator::new(self.fs().clone(), class.collection.clone());
144        build_collection(&mutator);
145        class
146    }
147
148    fn block(&self, callback: impl FnOnce(&SimpleDirectoryMutator)) {
149        self.edit_dir(&[b"block".into()], callback);
150    }
151
152    fn dev_block(&self, callback: impl FnOnce(&SimpleDirectoryMutator)) {
153        self.edit_dir(&[b"dev".into(), b"block".into()], callback);
154    }
155
156    fn dev_char(&self, callback: impl FnOnce(&SimpleDirectoryMutator)) {
157        self.edit_dir(&[b"dev".into(), b"char".into()], callback);
158    }
159
160    /// Create a device and add that device to the store.
161    ///
162    /// Rather than use this function directly, you should register your device with the
163    /// `DeviceRegistry`. The `DeviceRegistry` will create the KObject for the device as
164    /// part of the registration process.
165    ///
166    /// If you create the device yourself, userspace will not be able to instantiate the
167    /// device because the `DeviceType` will not be registered with the `DeviceRegistry`.
168    pub(super) fn create_device(
169        &self,
170        name: &FsStr,
171        metadata: Option<DeviceMetadata>,
172        class: Class,
173        build_directory: impl FnOnce(&Device, &SimpleDirectoryMutator),
174    ) -> Device {
175        let device = Device::new(name.to_owned(), class, metadata);
176        device.class.dir.edit(self.fs(), |dir| {
177            dir.subdir2(name, 0o755, |dir| {
178                build_directory(&device, dir);
179            });
180        });
181        self.add(&device);
182        device
183    }
184
185    fn add(&self, device: &Device) {
186        let class = &device.class;
187        let name = device.name.as_ref();
188
189        let up_device = device.path_from_depth(1);
190        let up_up_device = device.path_from_depth(2);
191
192        // Insert the newly created device into various views.
193        class.collection.edit(self.fs(), |dir| {
194            dir.symlink(name, up_up_device.as_ref());
195        });
196
197        if let Some(metadata) = &device.metadata {
198            let device_number = FsString::from(metadata.device_type.to_string());
199            match metadata.mode {
200                DeviceMode::Block => {
201                    self.block(|dir| dir.symlink(name, up_device.as_ref()));
202                    self.dev_block(|dir| {
203                        dir.symlink(device_number.as_ref(), up_up_device.as_ref());
204                    });
205                }
206                DeviceMode::Char => {
207                    self.dev_char(|dir| {
208                        dir.symlink(device_number.as_ref(), up_up_device.as_ref());
209                    });
210                }
211            }
212        }
213
214        if let Some(bus_collection) = &class.bus.collection {
215            bus_collection.edit(self.fs(), |dir| {
216                dir.symlink(name, device.path_from_depth(3).as_ref());
217            });
218        }
219    }
220
221    /// Destroy a device.
222    ///
223    /// This function removes the KObject for the device from the store.
224    ///
225    /// Most clients hold weak references to KObjects, which means those references will become
226    /// invalid shortly after this function is called.
227    pub(super) fn remove(&self, device: &Device) {
228        let name = device.name.as_ref();
229        // Remove the device from its views in the reverse order in which it was added.
230        if let Some(bus_collection) = &device.class.bus.collection {
231            bus_collection.remove(name);
232        }
233        if let Some(metadata) = &device.metadata {
234            let device_number: FsString = metadata.device_type.to_string().into();
235            match metadata.mode {
236                DeviceMode::Block => {
237                    self.dev_block(|dir| dir.remove(device_number.as_ref()));
238                    self.block(|dir| dir.remove(name));
239                }
240                DeviceMode::Char => {
241                    self.dev_char(|dir| dir.remove(device_number.as_ref()));
242                }
243            }
244        }
245        device.class.collection.remove(name);
246        // Finally, remove the device from the object store.
247        device.class.dir.remove(name);
248    }
249}
250
251impl Default for KObjectStore {
252    fn default() -> Self {
253        Self { root: SimpleDirectory::new(), fs: OnceLock::new() }
254    }
255}