1use 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
29fn 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 = ®istry.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 = ¤t_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 = ¤t_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 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 = ¤t_task.kernel().device_registry.objects.root;
340
341 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 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 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 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 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 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 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 = ¤t_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}