starnix_core/fs/sysfs/
cpu_class_directory.rs

1// Copyright 2023 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::task::CurrentTask;
6use crate::vfs::FsNodeOps;
7use crate::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
8use crate::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
9use crate::vfs::pseudo::stub_empty_file::StubEmptyFile;
10use anyhow::Error;
11use fuchsia_component::client::connect_to_protocol_sync;
12use itertools::Itertools;
13use starnix_logging::{bug_ref, log_warn};
14use starnix_uapi::errors::Errno;
15use starnix_uapi::file_mode::mode;
16use starnix_uapi::{errno, error, from_status_like_fdio};
17use std::collections::HashMap;
18use {fidl_fuchsia_hardware_cpu_ctrl as fcpuctrl, fidl_fuchsia_power_cpu as fcpu, zx};
19
20pub fn build_cpu_class_directory(dir: &SimpleDirectoryMutator) {
21    let cpu_domains = get_cpu_domains();
22    let cpu_count = match &cpu_domains {
23        Ok(domains) => {
24            let domain_map: HashMap<u64, &fcpu::DomainInfo> = domains
25                .iter()
26                .flat_map(|domain| {
27                    domain
28                        .core_ids
29                        .as_ref()
30                        .expect("core_ids not available.")
31                        .iter()
32                        .map(move |id| (*id, domain))
33                })
34                .collect();
35
36            for (core_id, domain) in domain_map.iter() {
37                let name = format!("cpu{}", core_id);
38                dir.subdir(&name, 0o755, |dir| build_cpu_directory(dir, domain));
39            }
40
41            domain_map.len()
42        }
43        Err(e) => {
44            log_warn!(
45                "Could not retrieve CPU domains from fuchsia.power.cpu.DomainController, using kernel CPU count instead: {e:?}"
46            );
47            zx::system_get_num_cpus() as usize
48        }
49    };
50
51    dir.entry(
52        "online",
53        BytesFile::new_node(format!("0-{}\n", cpu_count - 1).into_bytes()),
54        mode!(IFREG, 0o444),
55    );
56    dir.entry(
57        "possible",
58        BytesFile::new_node(format!("0-{}\n", cpu_count - 1).into_bytes()),
59        mode!(IFREG, 0o444),
60    );
61    dir.subdir("vulnerabilities", 0o755, |dir| {
62        for (name, contents) in VULNERABILITIES {
63            let contents = contents.to_string();
64            dir.entry(name, BytesFile::new_node(contents.into_bytes()), mode!(IFREG, 0o444));
65        }
66    });
67    dir.subdir("cpufreq", 0o755, |dir| {
68        dir.subdir("policy0", 0o755, |dir| {
69            dir.subdir("stats", 0o755, |dir| {
70                dir.entry("reset", CpuFreqStatsResetFile::new_node(), mode!(IFREG, 0o200));
71            });
72
73            let related_cpus = (0..cpu_count).map(|i| i.to_string()).join(" ") + "\n";
74            dir.entry(
75                "related_cpus",
76                BytesFile::new_node(related_cpus.into_bytes()),
77                mode!(IFREG, 0o444),
78            );
79            dir.entry("scaling_cur_freq", ScalingCurFreqFile::new_node(), mode!(IFREG, 0o444));
80            dir.entry(
81                "scaling_min_freq",
82                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
83                mode!(IFREG, 0o444),
84            );
85            dir.entry(
86                "scaling_max_freq",
87                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
88                mode!(IFREG, 0o444),
89            );
90            dir.entry(
91                "scaling_available_frequencies",
92                BytesFile::new_node(
93                    get_all_available_frequencies(cpu_domains.as_ref().ok()).into_bytes(),
94                ),
95                mode!(IFREG, 0o444),
96            );
97            dir.entry(
98                "scaling_available_governors",
99                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
100                mode!(IFREG, 0o444),
101            );
102            dir.entry(
103                "scaling_governor",
104                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
105                mode!(IFREG, 0o444),
106            );
107        });
108    });
109    dir.subdir("soc", 0o755, |dir| {
110        dir.subdir("0", 0o755, |dir| {
111            dir.entry(
112                "machine",
113                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
114                mode!(IFREG, 0o444),
115            );
116        });
117    });
118}
119
120fn get_cpu_domains() -> Result<Vec<fcpu::DomainInfo>, Error> {
121    let domain_controller: fcpu::DomainControllerSynchronousProxy =
122        connect_to_protocol_sync::<fcpu::DomainControllerMarker>().map_err(|e| {
123            anyhow::anyhow!("Failed to connect to fuchsia.power.cpu.DomainController: {e:?}")
124        })?;
125    domain_controller
126        .list_domains(zx::MonotonicInstant::INFINITE)
127        .map_err(|e| anyhow::anyhow!("Failed to get power domains: {e:?}"))
128}
129
130fn hz_to_khz(hz: u64) -> u64 {
131    return hz / 1000;
132}
133
134fn get_all_available_frequencies(domains: Option<&Vec<fcpu::DomainInfo>>) -> String {
135    let Some(domains) = domains else {
136        return "\n".to_string();
137    };
138    let all_frequencies_khz: Vec<_> = domains
139        .iter()
140        .filter_map(|d| d.available_frequencies_hz.as_ref())
141        .flat_map(|freqs| freqs.iter())
142        .map(|f| hz_to_khz(*f))
143        .sorted()
144        .dedup()
145        .collect();
146    all_frequencies_khz.iter().join(" ") + "\n"
147}
148
149fn build_cpu_directory(dir: &SimpleDirectoryMutator, domain: &fcpu::DomainInfo) {
150    let scaling_available_frequencies =
151        domain.available_frequencies_hz.as_ref().expect("available_frequencies_hz not available");
152    let cpuinfo_max_freq_khz = hz_to_khz(scaling_available_frequencies[0]);
153    let cluster_id = domain.id.as_ref().expect("id not available");
154
155    dir.entry(
156        "cpu_capacity",
157        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
158        mode!(IFREG, 0o444),
159    );
160    dir.subdir("cpufreq", 0o755, |dir| {
161        dir.entry(
162            "cpuinfo_max_freq",
163            BytesFile::new_node(format!("{cpuinfo_max_freq_khz}\n").into_bytes()),
164            mode!(IFREG, 0o444),
165        );
166
167        let frequencies_str =
168            scaling_available_frequencies.iter().map(|f| hz_to_khz(*f)).sorted().join(" ");
169        dir.entry(
170            "scaling_available_frequencies",
171            BytesFile::new_node(format!("{frequencies_str}\n").into_bytes()),
172            mode!(IFREG, 0o444),
173        );
174        dir.entry(
175            "scaling_boost_frequencies",
176            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
177            mode!(IFREG, 0o644),
178        );
179        dir.subdir("stats", 0o755, |dir| {
180            dir.entry(
181                "time_in_state",
182                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
183                mode!(IFREG, 0o444),
184            );
185        });
186    });
187    dir.subdir("topology", 0o755, |dir| {
188        dir.entry(
189            "cluster_id",
190            BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
191            mode!(IFREG, 0o444),
192        );
193        dir.entry(
194            "physical_package_id",
195            BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
196            mode!(IFREG, 0o444),
197        );
198    });
199}
200
201const VULNERABILITIES: &[(&str, &str)] = &[
202    ("gather_data_sampling", "Not affected\n"),
203    ("itlb_multihit", "Not affected\n"),
204    ("l1tf", "Not affected\n"),
205    ("mds", "Not affected\n"),
206    ("meltdown", "Not affected\n"),
207    ("mmio_stale_data", "Not affected\n"),
208    ("retbleed", "Not affected\n"),
209    ("spec_rstack_overflow", "Not affected\n"),
210    ("spec_store_bypass", "Not affected\n"),
211    ("spectre_v1", "Not affected\n"),
212    ("spectre_v2", "Not affected\n"),
213    ("srbds", "Not affected\n"),
214    ("tsx_async_abort", "Not affected\n"),
215];
216
217struct CpuFreqStatsResetFile {}
218
219impl CpuFreqStatsResetFile {
220    pub fn new_node() -> impl FsNodeOps {
221        BytesFile::new_node(Self {})
222    }
223}
224
225impl BytesFileOps for CpuFreqStatsResetFile {
226    // Currently a no-op. The value written to this node does not matter.
227    fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
228        Ok(())
229    }
230}
231
232const CPU_DIRECTORY: &str = "/svc/fuchsia.hardware.cpu.ctrl.Service";
233
234struct ScalingCurFreqFile {}
235
236impl ScalingCurFreqFile {
237    pub fn new_node() -> impl FsNodeOps {
238        BytesFile::new_node(Self {})
239    }
240}
241
242fn connect_to_device() -> Result<fcpuctrl::DeviceSynchronousProxy, Errno> {
243    let mut dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
244    let Some(Ok(entry)) = dir.next() else {
245        return error!(EBUSY);
246    };
247    let path =
248        entry.path().join("device").into_os_string().into_string().map_err(|_| errno!(EINVAL))?;
249    let (client, server) = zx::Channel::create();
250    fdio::service_connect(&path, server).map_err(|_| errno!(EINVAL))?;
251    Ok(fcpuctrl::DeviceSynchronousProxy::new(client))
252}
253
254impl BytesFileOps for ScalingCurFreqFile {
255    fn read(&self, _current_task: &CurrentTask) -> Result<std::borrow::Cow<'_, [u8]>, Errno> {
256        let cpu_ctrl_proxy = connect_to_device()?;
257        let opp = cpu_ctrl_proxy
258            .get_current_operating_point(zx::Instant::INFINITE)
259            .map_err(|_| errno!(EINVAL))?;
260        let info = cpu_ctrl_proxy
261            .get_operating_point_info(opp, zx::Instant::INFINITE)
262            .map_err(|_| errno!(EINVAL))?;
263        let freq_khz = hz_to_khz(
264            info.map_err(|e| from_status_like_fdio!(zx::Status::from_raw(e)))?.frequency_hz as u64,
265        );
266        Ok(format!("{}\n", freq_khz).into_bytes().into())
267    }
268}