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 fidl_fuchsia_hardware_cpu_ctrl as fcpuctrl;
11use fidl_fuchsia_power_cpu as fcpu;
12use fuchsia_component::client::connect_to_protocol_sync;
13use itertools::Itertools;
14use starnix_logging::{bug_ref, log_warn};
15use starnix_uapi::errors::Errno;
16use starnix_uapi::file_mode::mode;
17use starnix_uapi::{errno, error, from_status_like_fdio};
18use zx;
19
20pub fn build_cpu_class_directory(dir: &SimpleDirectoryMutator) {
21 let cpu_domains = get_cpu_domains();
22
23 let mut core_to_domain_map: Vec<(u64, &fcpu::DomainInfo)> = cpu_domains
24 .iter()
25 .flat_map(|domain| {
26 domain
27 .core_ids
28 .as_ref()
29 .expect("core_ids not available")
30 .iter()
31 .map(move |id| (*id, domain))
32 })
33 .collect();
34 core_to_domain_map.sort_by_key(|(id, _)| *id);
35 core_to_domain_map.dedup_by_key(|(id, _)| *id);
36
37 for (core_id, domain) in &core_to_domain_map {
38 let name = format!("cpu{}", core_id);
39 dir.subdir(&name, 0o755, |dir| build_cpu_directory(dir, domain));
40 }
41
42 let core_count = core_to_domain_map.len();
43
44 dir.entry(
45 "online",
46 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
47 mode!(IFREG, 0o444),
48 );
49 dir.entry(
50 "possible",
51 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
52 mode!(IFREG, 0o444),
53 );
54 dir.subdir("vulnerabilities", 0o755, |dir| {
55 for (name, contents) in VULNERABILITIES {
56 let contents = contents.to_string();
57 dir.entry(name, BytesFile::new_node(contents.into_bytes()), mode!(IFREG, 0o444));
58 }
59 });
60 dir.subdir("cpufreq", 0o755, |dir| {
61 for domain in &cpu_domains {
62 let min_core_id = domain
63 .core_ids
64 .as_ref()
65 .expect("core_ids not available")
66 .iter()
67 .min()
68 .expect("core_ids is empty");
69 let name = format!("policy{}", min_core_id);
70 dir.subdir(&name, 0o755, |dir| build_cpufreq_directory(dir, domain));
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() -> Vec<fcpu::DomainInfo> {
93 if let Ok(domain_controller) = connect_to_protocol_sync::<fcpu::DomainControllerMarker>() {
95 if let Ok(mut domains) = domain_controller.list_domains(zx::MonotonicInstant::INFINITE) {
96 domains
98 .retain(|d| d.id.is_some() && d.core_ids.as_ref().map_or(false, |c| !c.is_empty()));
99 if !domains.is_empty() {
100 return domains;
101 }
102 }
103 }
104
105 log_warn!(
106 "Could not retrieve CPU domains from fuchsia.power.cpu.DomainController, using CPU control devices instead."
107 );
108
109 if let Ok(proxies) = connect_to_cpu_devices() {
111 let mut domains = Vec::new();
112
113 for proxy in proxies {
114 let cpu_count = match proxy.get_num_logical_cores(zx::MonotonicInstant::INFINITE) {
115 Ok(count) => count,
116 Err(e) => {
117 log_warn!("get_num_logical_cores returned error: {}", e);
118 continue;
119 }
120 };
121 let domain_id = match proxy.get_domain_id(zx::MonotonicInstant::INFINITE) {
122 Ok(id) => id as u64,
123 Err(e) => {
124 log_warn!("get_domain_id returned error: {}", e);
125 continue;
126 }
127 };
128
129 let mut core_ids = Vec::with_capacity(cpu_count as usize);
130 let mut get_core_failed = false;
131 for i in 0..cpu_count {
132 let core_id = match proxy.get_logical_core_id(i, zx::MonotonicInstant::INFINITE) {
133 Ok(id) => id,
134 Err(e) => {
135 log_warn!("get_logical_core_id error in domain {}: {}", domain_id, e);
136 get_core_failed = true;
137 break;
138 }
139 };
140 core_ids.push(core_id);
141 }
142 if get_core_failed || core_ids.is_empty() {
143 log_warn!("get_logical_core_id failed in domain {}, skipping", domain_id);
144 continue;
145 }
146
147 let available_frequencies_hz =
148 match proxy.get_operating_point_count(zx::MonotonicInstant::INFINITE) {
149 Ok(Ok(count)) => {
150 let mut freqs = Vec::with_capacity(count as usize);
151 for i in 0..count {
152 if let Ok(Ok(info)) =
153 proxy.get_operating_point_info(i, zx::MonotonicInstant::INFINITE)
154 {
155 if info.frequency_hz > 0 {
156 freqs.push(info.frequency_hz as u64);
157 }
158 }
159 }
160 freqs.sort();
161 freqs.dedup();
162 Some(freqs)
163 }
164 _ => None,
165 };
166
167 domains.push(fcpu::DomainInfo {
168 id: Some(domain_id),
169 core_ids: Some(core_ids),
170 available_frequencies_hz,
171 ..Default::default()
172 });
173 }
174
175 if !domains.is_empty() {
176 return domains;
177 }
178 }
179
180 log_warn!(
181 "Could not connect to CPU control devices, using default domain info from kernel CPU count."
182 );
183
184 let cpu_count = zx::system_get_num_cpus();
186 vec![fcpu::DomainInfo {
187 id: Some(0),
188 core_ids: Some((0..cpu_count as u64).collect()),
189 available_frequencies_hz: None,
190 name: None,
191 ..Default::default()
192 }]
193}
194
195fn hz_to_khz(hz: u64) -> u64 {
196 hz / 1000
197}
198
199fn get_available_frequencies(domain: &fcpu::DomainInfo) -> Vec<u64> {
200 domain
201 .available_frequencies_hz
202 .as_deref()
203 .map(|freqs| freqs.iter().map(|f| hz_to_khz(*f)).sorted().dedup().collect())
204 .unwrap_or_default()
205}
206
207fn build_cpu_directory(dir: &SimpleDirectoryMutator, domain: &fcpu::DomainInfo) {
208 let cluster_id = domain.id.as_ref().expect("id not available");
209
210 dir.entry(
211 "cpu_capacity",
212 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
213 mode!(IFREG, 0o444),
214 );
215 dir.subdir("cpufreq", 0o755, |dir| {
216 build_cpufreq_directory(dir, domain);
217 });
218 dir.subdir("topology", 0o755, |dir| {
219 dir.entry(
220 "cluster_id",
221 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
222 mode!(IFREG, 0o444),
223 );
224 dir.entry(
225 "physical_package_id",
226 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
227 mode!(IFREG, 0o444),
228 );
229 });
230}
231
232fn build_cpufreq_directory(dir: &SimpleDirectoryMutator, domain: &fcpu::DomainInfo) {
233 let scaling_available_frequencies = get_available_frequencies(domain);
234 let core_ids = domain.core_ids.as_ref().expect("core_ids not available");
235
236 dir.subdir("stats", 0o755, |dir| {
237 dir.entry("reset", CpuFreqStatsResetFile::new_node(), mode!(IFREG, 0o200));
238 dir.entry(
239 "time_in_state",
240 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
241 mode!(IFREG, 0o444),
242 );
243 });
244
245 let related_cpus_str = format!("{}\n", core_ids.iter().sorted().join(" "));
246 dir.entry(
247 "related_cpus",
248 BytesFile::new_node(related_cpus_str.into_bytes()),
249 mode!(IFREG, 0o444),
250 );
251 dir.entry(
252 "scaling_cur_freq",
253 create_scaling_cur_freq_file(*domain.id.as_ref().expect("domain id missing")),
254 mode!(IFREG, 0o444),
255 );
256 dir.entry(
257 "scaling_min_freq",
258 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
259 mode!(IFREG, 0o444),
260 );
261 dir.entry(
262 "scaling_max_freq",
263 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
264 mode!(IFREG, 0o444),
265 );
266 dir.entry(
267 "scaling_available_frequencies",
268 BytesFile::new_node((scaling_available_frequencies.iter().join(" ") + "\n").into_bytes()),
269 mode!(IFREG, 0o444),
270 );
271 dir.entry(
272 "scaling_available_governors",
273 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
274 mode!(IFREG, 0o444),
275 );
276 dir.entry(
277 "scaling_governor",
278 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
279 mode!(IFREG, 0o444),
280 );
281 dir.entry(
282 "cpuinfo_max_freq",
283 BytesFile::new_node(
284 format!(
285 "{}\n",
286 scaling_available_frequencies.last().map(|f| f.to_string()).unwrap_or_default()
287 )
288 .into_bytes(),
289 ),
290 mode!(IFREG, 0o444),
291 );
292}
293
294const VULNERABILITIES: &[(&str, &str)] = &[
295 ("gather_data_sampling", "Not affected\n"),
296 ("itlb_multihit", "Not affected\n"),
297 ("l1tf", "Not affected\n"),
298 ("mds", "Not affected\n"),
299 ("meltdown", "Not affected\n"),
300 ("mmio_stale_data", "Not affected\n"),
301 ("retbleed", "Not affected\n"),
302 ("spec_rstack_overflow", "Not affected\n"),
303 ("spec_store_bypass", "Not affected\n"),
304 ("spectre_v1", "Not affected\n"),
305 ("spectre_v2", "Not affected\n"),
306 ("srbds", "Not affected\n"),
307 ("tsx_async_abort", "Not affected\n"),
308];
309
310struct CpuFreqStatsResetFile {}
311
312impl CpuFreqStatsResetFile {
313 pub fn new_node() -> impl FsNodeOps {
314 BytesFile::new_node(Self {})
315 }
316}
317
318impl BytesFileOps for CpuFreqStatsResetFile {
319 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
321 Ok(())
322 }
323}
324
325const CPU_DIRECTORY: &str = "/svc/fuchsia.hardware.cpu.ctrl.Service";
326
327fn connect_to_cpu_devices() -> Result<Vec<fcpuctrl::DeviceSynchronousProxy>, Errno> {
328 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
329
330 let proxies: Vec<_> = dir
331 .filter_map(|r| r.ok())
332 .filter_map(|entry| {
333 let path = entry.path().join("device").into_os_string().into_string().ok()?;
334 let (client, server) = zx::Channel::create();
335 fdio::service_connect(&path, server).ok()?;
336 Some(fcpuctrl::DeviceSynchronousProxy::new(client))
337 })
338 .collect();
339
340 if proxies.is_empty() { error!(ENOENT) } else { Ok(proxies) }
341}
342
343fn connect_to_cpu_device_by_domain_id(
344 domain_id: u64,
345) -> Result<fcpuctrl::DeviceSynchronousProxy, Errno> {
346 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
347
348 dir.filter_map(|r| r.ok())
349 .find_map(|entry| {
350 let path = entry.path().join("device").into_os_string().into_string().ok()?;
351 let (client, server) = zx::Channel::create();
352 fdio::service_connect(&path, server).ok()?;
353 let proxy = fcpuctrl::DeviceSynchronousProxy::new(client);
354
355 let dev_domain_id = proxy.get_domain_id(zx::MonotonicInstant::INFINITE).ok()?;
356 if domain_id == dev_domain_id as u64 { Some(proxy) } else { None }
357 })
358 .ok_or_else(|| errno!(ENOENT))
359}
360
361fn create_scaling_cur_freq_file(domain_id: u64) -> impl FsNodeOps {
362 let proxy_cache = starnix_sync::Mutex::new(None::<fcpuctrl::DeviceSynchronousProxy>);
363 SimpleFileNode::new(move |_| {
364 let mut guard = proxy_cache.lock();
365 if guard.is_none() {
366 let proxy = connect_to_cpu_device_by_domain_id(domain_id)?;
367 *guard = Some(proxy);
368 }
369 let proxy = guard.as_ref().expect("must have a valid proxy");
370 let opp = match proxy.get_current_operating_point(zx::MonotonicInstant::INFINITE) {
371 Ok(opp) => opp,
372 Err(_) => {
373 *guard = None;
374 return error!(EINVAL);
375 }
376 };
377 let info = match proxy.get_operating_point_info(opp, zx::MonotonicInstant::INFINITE) {
378 Ok(info) => info,
379 Err(_) => {
380 *guard = None;
381 return error!(EINVAL);
382 }
383 };
384 let info = info.map_err(|e| from_status_like_fdio!(zx::Status::err_from_raw(e)))?;
385 if info.frequency_hz <= 0 {
386 return error!(EINVAL);
387 }
388 let freq_khz = hz_to_khz(info.frequency_hz as u64);
389 Ok(BytesFile::new(format!("{}\n", freq_khz).into_bytes()))
390 })
391}