1use 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 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}