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 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
84/// Retrieves CPU topology and domain information with a tiered fallback approach.
85///
86/// 1. Get CPU domains from the `fuchsia.power.cpu.DomainController` FIDL protocol.
87/// 2. If that fails, use the `fuchsia.hardware.cpu.ctrl.Service` FIDL service to connect to
88///    individual CPU control devices.
89/// 3. If that fails, get CPU information from the kernel directly. In this case, no CPU control
90///    or topological information is available, so only a limited set of sysfs entries
91///    will be populated.
92fn get_cpu_domains() -> Vec<fcpu::DomainInfo> {
93    // Tier 1: Try DomainController
94    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            // Remove any domains without an ID or empty core_ids.
97            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    // Tier 2: Try cpu.ctrl devices
110    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    // Tier 3: Fallback to kernel CPU count
185    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    // Currently a no-op. The value written to this node does not matter.
320    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}