Skip to main content

starnix_core/fs/sysfs/
fs.rs

1// Copyright 2021 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::fs::sysfs::{build_kernel_directory, build_power_directory};
6use crate::task::{CurrentTask, Kernel};
7use crate::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
8use crate::vfs::pseudo::simple_file::BytesFile;
9use crate::vfs::pseudo::stub_empty_file::StubEmptyFile;
10use crate::vfs::{
11    CacheMode, DirEntry, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsStr,
12};
13use starnix_logging::{Level, bug_ref, track_stub_log};
14use starnix_types::vfs::default_statfs;
15use starnix_uapi::errors::Errno;
16use starnix_uapi::file_mode::mode;
17use starnix_uapi::{SYSFS_MAGIC, errno, statfs};
18
19struct SysFs;
20impl FileSystemOps for SysFs {
21    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
22        Ok(default_statfs(SYSFS_MAGIC))
23    }
24    fn name(&self) -> &'static FsStr {
25        "sysfs".into()
26    }
27}
28
29/// Reports lookups of missing `/sys` entries via `track_stub_log!`, to surface sysfs nodes that
30/// userspace expects but which Starnix does not (yet) provide.
31///
32/// Each distinct missing path is logged once, and counted in the kernel's Inspect `stubs` node.
33fn sysfs_not_found_handler(entry: &DirEntry, name: &FsStr) -> Errno {
34    let message = format!("Looking for {name} in {entry:?}");
35    track_stub_log!(Level::Warn, TODO("https://fxbug.dev/493488790"), &message);
36    errno!(ENOENT, message)
37}
38
39impl SysFs {
40    fn new_fs(kernel: &Kernel, options: FileSystemOptions) -> FileSystemHandle {
41        let fs =
42            FileSystem::new(kernel, CacheMode::Cached(kernel.fs_cache_config()), SysFs, options)
43                .expect("sysfs constructed with valid options");
44
45        fn empty_dir(_: &SimpleDirectoryMutator) {}
46
47        let registry = &kernel.device_registry;
48        let root = &registry.objects.root;
49        fs.create_root(fs.allocate_ino(), root.clone());
50        if kernel.features.log_sysfs_lookup_misses {
51            root.set_not_found_handler(sysfs_not_found_handler);
52        }
53        let dir = SimpleDirectoryMutator::new(fs.clone(), root.clone());
54
55        let dir_mode = 0o755;
56        dir.subdir("fs", dir_mode, |dir| {
57            dir.subdir("selinux", dir_mode, empty_dir);
58            dir.subdir("bpf", dir_mode, empty_dir);
59            dir.subdir("cgroup", dir_mode, empty_dir);
60            dir.subdir("fuse", dir_mode, |dir| {
61                dir.subdir("connections", dir_mode, empty_dir);
62            });
63            dir.subdir("pstore", dir_mode, empty_dir);
64        });
65
66        dir.subdir("block", dir_mode, empty_dir);
67
68        dir.subdir("bus", dir_mode, |dir| {
69            dir.subdir("platform", dir_mode, |dir| {
70                dir.subdir("drivers", dir_mode, |dir| {
71                    dir.entry(
72                        "trusty",
73                        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
74                        mode!(IFREG, 0o444),
75                    );
76                });
77            });
78        });
79
80        dir.subdir("class", dir_mode, |dir| {
81            dir.subdir("net", dir_mode, empty_dir);
82        });
83        dir.subdir("dev", dir_mode, |dir| {
84            dir.subdir("char", dir_mode, empty_dir);
85            dir.subdir("block", dir_mode, empty_dir);
86        });
87        dir.subdir("firmware", dir_mode, |dir| {
88            dir.subdir("devicetree", dir_mode, |dir| {
89                dir.subdir("base", dir_mode, |dir| {
90                    dir.subdir("chosen", dir_mode, |dir| {
91                        dir.subdir("plat", dir_mode, |dir| {
92                            if let Some(device_tree) = &kernel.device_tree {
93                                if let Some(product_bytes) =
94                                    device_tree.root_node.find("plat").and_then(|n| {
95                                        n.get_property("product").map(|p| p.value.clone())
96                                    })
97                                {
98                                    let product_bytes = if product_bytes.len() >= 4 {
99                                        product_bytes.to_vec()
100                                    } else {
101                                        let mut padded_bytes = vec![0; 4];
102                                        let start = 4 - product_bytes.len();
103                                        padded_bytes[start..].copy_from_slice(&product_bytes);
104                                        padded_bytes
105                                    };
106                                    dir.entry(
107                                        "product",
108                                        BytesFile::new_node(product_bytes),
109                                        mode!(IFREG, 0o444),
110                                    );
111                                }
112                            }
113                        });
114                        dir.subdir("config", dir_mode, |dir| {
115                            dir.entry(
116                                "pcbcfg",
117                                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
118                                mode!(IFREG, 0o444),
119                            );
120                        });
121                    });
122                    dir.subdir("firmware", dir_mode, |dir| {
123                        dir.subdir("android", 0o755, |dir| {
124                            dir.entry(
125                                "compatible",
126                                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
127                                mode!(IFREG, 0o444),
128                            );
129                            dir.subdir("vbmeta", 0o755, |dir| {
130                                dir.entry(
131                                    "parts",
132                                    StubEmptyFile::new_node(bug_ref!(
133                                        "https://fxbug.dev/452096300"
134                                    )),
135                                    mode!(IFREG, 0o444),
136                                );
137                            });
138                        });
139                    });
140                    dir.subdir("mcu", dir_mode, |dir| {
141                        dir.entry(
142                            "board_type",
143                            BytesFile::new_node(b"starnix".to_vec()),
144                            mode!(IFREG, 0o444),
145                        );
146                    });
147                });
148            });
149        });
150
151        dir.subdir("kernel", dir_mode, |dir| {
152            build_kernel_directory(kernel, dir);
153        });
154
155        dir.subdir("power", 0o755, |dir| {
156            build_power_directory(kernel, dir);
157        });
158
159        dir.subdir("leds", dir_mode, |dir| {
160            dir.entry(
161                "leds",
162                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
163                mode!(IFREG, 0o444),
164            );
165        });
166
167        dir.subdir("module", dir_mode, |dir| {
168            dir.subdir("dm_bufio", dir_mode, |dir| {
169                dir.subdir("parameters", dir_mode, |dir| {
170                    dir.entry(
171                        "max_age_seconds",
172                        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
173                        mode!(IFREG, 0o644),
174                    );
175                });
176            });
177            dir.subdir("dm_verity", dir_mode, |dir| {
178                dir.subdir("parameters", dir_mode, |dir| {
179                    dir.entry(
180                        "prefetch_cluster",
181                        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322893670")),
182                        mode!(IFREG, 0o644),
183                    );
184                });
185            });
186        });
187
188        dir.subdir("devices", dir_mode, |dir| {
189            dir.subdir("leds", dir_mode, |_dir| {});
190            dir.subdir("virtual", dir_mode, |dir| {
191                dir.subdir("leds", dir_mode, |_dir| {});
192                dir.subdir("power_supply", dir_mode, |dir| {
193                    dir.subdir("bms", dir_mode, |dir| {
194                        dir.entry(
195                            "capacity",
196                            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
197                            mode!(IFREG, 0o444),
198                        );
199                        dir.entry(
200                            "capacity_level",
201                            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
202                            mode!(IFREG, 0o444),
203                        );
204                        dir.entry(
205                            "status",
206                            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
207                            mode!(IFREG, 0o444),
208                        );
209                    });
210                });
211            });
212        });
213
214        fs
215    }
216}
217
218struct SysFsHandle(FileSystemHandle);
219
220pub fn sys_fs(
221    current_task: &CurrentTask,
222    _options: FileSystemOptions,
223) -> Result<FileSystemHandle, Errno> {
224    Ok(get_sysfs(current_task.kernel()))
225}
226
227pub fn get_sysfs(kernel: &Kernel) -> FileSystemHandle {
228    kernel
229        .expando
230        .get_or_init(|| SysFsHandle(SysFs::new_fs(kernel, FileSystemOptions::default())))
231        .0
232        .clone()
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use crate::task::KernelFeatures;
239    use crate::testing::{
240        anon_test_file, spawn_kernel_and_run, spawn_kernel_with_features_and_run,
241    };
242    use crate::vfs::{MountInfo, VecInputBuffer, VecOutputBuffer};
243    use starnix_uapi::open_flags::OpenFlags;
244    use std::sync::Arc;
245
246    fn read_sysfs_file(current_task: &CurrentTask, path: &str) -> Vec<u8> {
247        let root = &current_task.kernel().device_registry.objects.root;
248        let node = root.lookup(path.into()).unwrap_or_else(|| panic!("{path} should exist"));
249        assert_eq!(node.info().mode, mode!(IFREG, 0o444), "{path} mode");
250        let file_ops = node.create_file_ops(current_task, OpenFlags::RDONLY).unwrap();
251        let file = anon_test_file(current_task, file_ops, OpenFlags::RDONLY);
252        let mut buf = VecOutputBuffer::new(16);
253        let n = file.read(current_task, &mut buf).unwrap();
254        Vec::from(buf)[..n].to_vec()
255    }
256
257    #[::fuchsia::test]
258    async fn sysfs_registers_platform_and_soc_devices() {
259        spawn_kernel_and_run(async |current_task| {
260            let objects = &current_task.kernel().device_registry.objects;
261            let root = &objects.root;
262
263            let soc_uevent = root
264                .lookup("devices/platform/soc/uevent".into())
265                .expect("soc uevent node should exist");
266            // Calling soc_device() again should return the cached Device without replacing its
267            // sysfs entries.
268            let _soc = objects.soc_device();
269            let soc_uevent_after = root
270                .lookup("devices/platform/soc/uevent".into())
271                .expect("soc uevent node should still exist");
272            assert!(Arc::ptr_eq(&soc_uevent, &soc_uevent_after));
273
274            assert!(root.lookup("devices/platform/uevent".into()).is_some());
275            assert!(root.lookup("bus/platform/devices/soc".into()).is_some());
276            assert!(root.lookup("bus/platform/devices/1c40000.qcom,spmi".into()).is_some());
277            assert!(root.lookup("bus/spmi/devices/spmi-0".into()).is_some());
278            assert!(root.lookup("bus/spmi/devices/0-00".into()).is_some());
279            assert!(root.lookup("bus/iio/devices/iio:device3".into()).is_some());
280            assert!(
281                root.lookup(
282                    "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00/iio:device3/in_resistance_resistance_id_input"
283                        .into(),
284                )
285                .is_some()
286            );
287            assert!(root.lookup("class/drm/card0".into()).is_some());
288            assert!(root.lookup("class/drm/sde-conn-0-DSI-1".into()).is_some());
289            assert!(
290                root.lookup(
291                    "devices/platform/soc/5e00000.qcom,mdss_mdp/drm/card0/sde-conn-0-DSI-1/display_power_state"
292                        .into(),
293                )
294                .is_some()
295            );
296            assert!(root.lookup("class/drm/card0-DSI-1".into()).is_some());
297            assert_eq!(
298                read_sysfs_file(
299                    current_task,
300                    "devices/platform/soc/5e00000.qcom,mdss_mdp/drm/card0/card0-DSI-1/panel/panel_name",
301                ),
302                b"Placeholder",
303            );
304        })
305        .await;
306    }
307
308    #[::fuchsia::test]
309    async fn test_log_sysfs_lookup_misses() {
310        for (log_sysfs_lookup_misses, name) in
311            [(false, "missing_when_disabled"), (true, "missing_when_enabled")]
312        {
313            spawn_kernel_with_features_and_run(
314                async move |current_task| {
315                    let sysfs = get_sysfs(current_task.kernel());
316                    let res = sysfs.root().component_lookup(
317                        current_task,
318                        &MountInfo::detached(),
319                        name.into(),
320                    );
321                    assert_eq!(res.unwrap_err(), errno!(ENOENT));
322                },
323                KernelFeatures { log_sysfs_lookup_misses, ..Default::default() },
324            )
325            .await;
326
327            let inspector =
328                starnix_logging::track_stub_lazy_node_callback().await.expect("lazy node callback");
329            let hierarchy = fuchsia_inspect::reader::read(&inspector).await.expect("read inspect");
330            let expected =
331                format!(r#"Looking for {name} in DirEntry {{ fs: "sysfs", path: "/" }}"#);
332            assert_eq!(hierarchy.get_child(&expected).is_some(), log_sysfs_lookup_misses);
333        }
334    }
335
336    #[::fuchsia::test]
337    async fn sysfs_registers_stub_devices() {
338        spawn_kernel_and_run(async |current_task| {
339            let root = &current_task.kernel().device_registry.objects.root;
340
341            // Backlight, parented by the display controller.
342            assert!(root.lookup("class/backlight/panel0-backlight".into()).is_some());
343            assert!(
344                root.lookup(
345                    "devices/platform/soc/5e00000.qcom,mdss_mdp/backlight/panel0-backlight/brightness"
346                        .into(),
347                )
348                .is_some()
349            );
350
351            // eMMC host, card and block stub share a single device directory tree.
352            assert!(root.lookup("class/mmc_host/mmc0".into()).is_some());
353            assert!(root.lookup("bus/mmc/devices/mmc0:0001".into()).is_some());
354            assert!(root.lookup("devices/virtual/mmc_host/mmc0/uevent".into()).is_some());
355            assert!(
356                root.lookup("devices/virtual/mmc_host/mmc0/mmc0:0001/life_time".into()).is_some()
357            );
358            assert!(
359                root.lookup("devices/virtual/mmc_host/mmc0/mmc0:0001/block/mmcblk0/size".into())
360                    .is_some()
361            );
362
363            // SoC identification device.
364            assert!(root.lookup("bus/soc/devices/soc0".into()).is_some());
365            assert!(root.lookup("devices/soc0/revision".into()).is_some());
366            assert!(root.lookup("devices/soc0/uevent".into()).is_some());
367
368            // Backing device info.
369            assert!(root.lookup("class/bdi/0:80".into()).is_some());
370            assert!(root.lookup("devices/virtual/bdi/0:80/read_ahead_kb".into()).is_some());
371
372            // Battery power supply.
373            assert!(root.lookup("class/power_supply/battery".into()).is_some());
374            let battery_capacity = root
375                .lookup("devices/virtual/power_supply/battery/capacity".into())
376                .expect("battery capacity node should exist");
377            assert_eq!(battery_capacity.info().mode, mode!(IFREG, 0o644));
378            let file_ops =
379                battery_capacity.create_file_ops(current_task, OpenFlags::RDWR).unwrap();
380            let file = anon_test_file(current_task, file_ops, OpenFlags::RDWR);
381            let mut input = VecInputBuffer::new(b"77\n");
382            assert_eq!(file.write(current_task, &mut input).unwrap(), 3);
383
384            // Google SoC chip info.
385            assert!(root.lookup("devices/system/goog-chip-info/uevent".into()).is_some());
386            for name in ["device_table", "dvfs_table", "ids_table"] {
387                let path = format!("devices/system/goog-chip-info/{name}");
388                assert_eq!(read_sysfs_file(current_task, &path), b"Placeholder");
389            }
390
391            // Device-less classes.
392            assert!(root.lookup("class/powercap".into()).is_some());
393            assert!(root.lookup("class/udc".into()).is_some());
394        })
395        .await;
396    }
397
398    #[::fuchsia::test]
399    async fn sysfs_registers_cpu_devices() {
400        spawn_kernel_and_run(async |current_task| {
401            let root = &current_task.kernel().device_registry.objects.root;
402
403            assert!(root.lookup("devices/system/cpu/uevent".into()).is_some());
404            assert!(root.lookup("devices/system/cpu/online".into()).is_some());
405            assert!(root.lookup("devices/system/cpu/possible".into()).is_some());
406            assert!(root.lookup("devices/system/cpu/cpufreq".into()).is_some());
407            assert!(root.lookup("devices/system/cpu/cpu0/uevent".into()).is_some());
408            assert!(root.lookup("devices/system/cpu/cpu0/topology".into()).is_some());
409            assert!(root.lookup("bus/cpu/devices/cpu0".into()).is_some());
410        })
411        .await;
412    }
413}