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