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, SimpleFileNode};
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            let domains = cpu_domains.as_ref().map(|v| v.as_slice()).unwrap_or_default();
70            build_cpufreq_directory(dir, domains);
71        });
72    });
73    dir.subdir("soc", 0o755, |dir| {
74        dir.subdir("0", 0o755, |dir| {
75            dir.entry(
76                "machine",
77                StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
78                mode!(IFREG, 0o444),
79            );
80        });
81    });
82}
83
84fn get_cpu_domains() -> Result<Vec<fcpu::DomainInfo>, Error> {
85    let domain_controller: fcpu::DomainControllerSynchronousProxy =
86        connect_to_protocol_sync::<fcpu::DomainControllerMarker>().map_err(|e| {
87            anyhow::anyhow!("Failed to connect to fuchsia.power.cpu.DomainController: {e:?}")
88        })?;
89    domain_controller
90        .list_domains(zx::MonotonicInstant::INFINITE)
91        .map_err(|e| anyhow::anyhow!("Failed to get power domains: {e:?}"))
92}
93
94fn hz_to_khz(hz: u64) -> u64 {
95    return hz / 1000;
96}
97
98fn get_all_available_frequencies(domains: &[fcpu::DomainInfo]) -> Vec<u64> {
99    domains
100        .iter()
101        .filter_map(|d| d.available_frequencies_hz.as_ref())
102        .flat_map(|freqs| freqs.iter())
103        .map(|f| hz_to_khz(*f))
104        .sorted()
105        .dedup()
106        .collect()
107}
108
109fn build_cpu_directory(dir: &SimpleDirectoryMutator, domain: &fcpu::DomainInfo) {
110    let cluster_id = domain.id.as_ref().expect("id not available");
111
112    dir.entry(
113        "cpu_capacity",
114        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
115        mode!(IFREG, 0o444),
116    );
117    dir.subdir("cpufreq", 0o755, |dir| {
118        build_cpufreq_directory(dir, std::slice::from_ref(domain));
119    });
120    dir.subdir("topology", 0o755, |dir| {
121        dir.entry(
122            "cluster_id",
123            BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
124            mode!(IFREG, 0o444),
125        );
126        dir.entry(
127            "physical_package_id",
128            BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
129            mode!(IFREG, 0o444),
130        );
131    });
132}
133
134fn build_cpufreq_directory(dir: &SimpleDirectoryMutator, domain: &[fcpu::DomainInfo]) {
135    let scaling_available_frequencies = get_all_available_frequencies(domain);
136    let cpu_count = zx::system_get_num_cpus() as usize;
137    dir.subdir("stats", 0o755, |dir| {
138        dir.entry("reset", CpuFreqStatsResetFile::new_node(), mode!(IFREG, 0o200));
139        dir.entry(
140            "time_in_state",
141            StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
142            mode!(IFREG, 0o444),
143        );
144    });
145
146    let related_cpus = (0..cpu_count).map(|i| i.to_string()).join(" ") + "\n";
147    dir.entry("related_cpus", BytesFile::new_node(related_cpus.into_bytes()), mode!(IFREG, 0o444));
148    dir.entry("scaling_cur_freq", create_scaling_cur_freq_file(), mode!(IFREG, 0o444));
149    dir.entry(
150        "scaling_min_freq",
151        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
152        mode!(IFREG, 0o444),
153    );
154    dir.entry(
155        "scaling_max_freq",
156        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
157        mode!(IFREG, 0o444),
158    );
159    dir.entry(
160        "scaling_available_frequencies",
161        BytesFile::new_node((scaling_available_frequencies.iter().join(" ") + "\n").into_bytes()),
162        mode!(IFREG, 0o444),
163    );
164    dir.entry(
165        "scaling_available_governors",
166        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
167        mode!(IFREG, 0o444),
168    );
169    dir.entry(
170        "scaling_governor",
171        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
172        mode!(IFREG, 0o444),
173    );
174    dir.entry(
175        "cpuinfo_max_freq",
176        BytesFile::new_node(
177            format!(
178                "{}\n",
179                scaling_available_frequencies.last().map(|f| f.to_string()).unwrap_or_default()
180            )
181            .into_bytes(),
182        ),
183        mode!(IFREG, 0o444),
184    );
185}
186
187const VULNERABILITIES: &[(&str, &str)] = &[
188    ("gather_data_sampling", "Not affected\n"),
189    ("itlb_multihit", "Not affected\n"),
190    ("l1tf", "Not affected\n"),
191    ("mds", "Not affected\n"),
192    ("meltdown", "Not affected\n"),
193    ("mmio_stale_data", "Not affected\n"),
194    ("retbleed", "Not affected\n"),
195    ("spec_rstack_overflow", "Not affected\n"),
196    ("spec_store_bypass", "Not affected\n"),
197    ("spectre_v1", "Not affected\n"),
198    ("spectre_v2", "Not affected\n"),
199    ("srbds", "Not affected\n"),
200    ("tsx_async_abort", "Not affected\n"),
201];
202
203struct CpuFreqStatsResetFile {}
204
205impl CpuFreqStatsResetFile {
206    pub fn new_node() -> impl FsNodeOps {
207        BytesFile::new_node(Self {})
208    }
209}
210
211impl BytesFileOps for CpuFreqStatsResetFile {
212    // Currently a no-op. The value written to this node does not matter.
213    fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
214        Ok(())
215    }
216}
217
218const CPU_DIRECTORY: &str = "/svc/fuchsia.hardware.cpu.ctrl.Service";
219
220fn connect_to_device() -> Result<fcpuctrl::DeviceSynchronousProxy, Errno> {
221    let mut dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
222    let Some(Ok(entry)) = dir.next() else {
223        return error!(EBUSY);
224    };
225    let path =
226        entry.path().join("device").into_os_string().into_string().map_err(|_| errno!(EINVAL))?;
227    let (client, server) = zx::Channel::create();
228    fdio::service_connect(&path, server).map_err(|_| errno!(EINVAL))?;
229    Ok(fcpuctrl::DeviceSynchronousProxy::new(client))
230}
231
232fn create_scaling_cur_freq_file() -> impl FsNodeOps {
233    SimpleFileNode::new(|_, _| {
234        let proxy = connect_to_device()?;
235        let opp =
236            proxy.get_current_operating_point(zx::Instant::INFINITE).map_err(|_| errno!(EINVAL))?;
237        let info = proxy
238            .get_operating_point_info(opp, zx::Instant::INFINITE)
239            .map_err(|_| errno!(EINVAL))?;
240        let freq_khz = hz_to_khz(
241            info.map_err(|e| from_status_like_fdio!(zx::Status::from_raw(e)))?.frequency_hz as u64,
242        );
243        Ok(BytesFile::new(format!("{}\n", freq_khz).into_bytes()))
244    })
245}