Skip to main content

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