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