sysinfo/linux/
cpu.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3#![allow(clippy::too_many_arguments)]
4
5use std::collections::HashSet;
6use std::fs::File;
7use std::io::{BufRead, BufReader, Read};
8use std::time::Instant;
9
10use crate::sys::utils::to_u64;
11use crate::{CpuExt, CpuRefreshKind, SystemExt};
12
13macro_rules! to_str {
14    ($e:expr) => {
15        unsafe { std::str::from_utf8_unchecked($e) }
16    };
17}
18
19pub(crate) struct CpusWrapper {
20    pub(crate) global_cpu: Cpu,
21    pub(crate) cpus: Vec<Cpu>,
22    /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`.
23    ///
24    /// The reason behind this is to avoid calling the `update_cpus` more than necessary.
25    /// For example when running `refresh_all` or `refresh_specifics`.
26    need_cpus_update: bool,
27    got_cpu_frequency: bool,
28    /// This field is needed to prevent updating when not enough time passed since last update.
29    last_update: Option<Instant>,
30}
31
32impl CpusWrapper {
33    pub(crate) fn new() -> Self {
34        Self {
35            global_cpu: Cpu::new_with_values(
36                "",
37                0,
38                0,
39                0,
40                0,
41                0,
42                0,
43                0,
44                0,
45                0,
46                0,
47                0,
48                String::new(),
49                String::new(),
50            ),
51            cpus: Vec::with_capacity(4),
52            need_cpus_update: true,
53            got_cpu_frequency: false,
54            last_update: None,
55        }
56    }
57
58    pub(crate) fn refresh_if_needed(
59        &mut self,
60        only_update_global_cpu: bool,
61        refresh_kind: CpuRefreshKind,
62    ) {
63        if self.need_cpus_update {
64            self.refresh(only_update_global_cpu, refresh_kind);
65        }
66    }
67
68    pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
69        let need_cpu_usage_update = self
70            .last_update
71            .map(|last_update| last_update.elapsed() > crate::System::MINIMUM_CPU_UPDATE_INTERVAL)
72            .unwrap_or(true);
73
74        let first = self.cpus.is_empty();
75        let (vendor_id, brand) = if first {
76            get_vendor_id_and_brand()
77        } else {
78            (String::new(), String::new())
79        };
80
81        // If the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`),
82        // we don't want to update CPUs times.
83        if need_cpu_usage_update {
84            self.last_update = Some(Instant::now());
85            let f = match File::open("/proc/stat") {
86                Ok(f) => f,
87                Err(_e) => {
88                    sysinfo_debug!("failed to retrieve CPU information: {:?}", _e);
89                    return;
90                }
91            };
92            let buf = BufReader::new(f);
93
94            self.need_cpus_update = false;
95            let mut i: usize = 0;
96            let mut it = buf.split(b'\n');
97
98            if first || refresh_kind.cpu_usage() {
99                if let Some(Ok(line)) = it.next() {
100                    if &line[..4] != b"cpu " {
101                        return;
102                    }
103                    let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
104                    if first {
105                        self.global_cpu.name = to_str!(parts.next().unwrap_or(&[])).to_owned();
106                    } else {
107                        parts.next();
108                    }
109                    self.global_cpu.set(
110                        parts.next().map(to_u64).unwrap_or(0),
111                        parts.next().map(to_u64).unwrap_or(0),
112                        parts.next().map(to_u64).unwrap_or(0),
113                        parts.next().map(to_u64).unwrap_or(0),
114                        parts.next().map(to_u64).unwrap_or(0),
115                        parts.next().map(to_u64).unwrap_or(0),
116                        parts.next().map(to_u64).unwrap_or(0),
117                        parts.next().map(to_u64).unwrap_or(0),
118                        parts.next().map(to_u64).unwrap_or(0),
119                        parts.next().map(to_u64).unwrap_or(0),
120                    );
121                }
122                if first || !only_update_global_cpu {
123                    while let Some(Ok(line)) = it.next() {
124                        if &line[..3] != b"cpu" {
125                            break;
126                        }
127
128                        let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty());
129                        if first {
130                            self.cpus.push(Cpu::new_with_values(
131                                to_str!(parts.next().unwrap_or(&[])),
132                                parts.next().map(to_u64).unwrap_or(0),
133                                parts.next().map(to_u64).unwrap_or(0),
134                                parts.next().map(to_u64).unwrap_or(0),
135                                parts.next().map(to_u64).unwrap_or(0),
136                                parts.next().map(to_u64).unwrap_or(0),
137                                parts.next().map(to_u64).unwrap_or(0),
138                                parts.next().map(to_u64).unwrap_or(0),
139                                parts.next().map(to_u64).unwrap_or(0),
140                                parts.next().map(to_u64).unwrap_or(0),
141                                parts.next().map(to_u64).unwrap_or(0),
142                                0,
143                                vendor_id.clone(),
144                                brand.clone(),
145                            ));
146                        } else {
147                            parts.next(); // we don't want the name again
148                            self.cpus[i].set(
149                                parts.next().map(to_u64).unwrap_or(0),
150                                parts.next().map(to_u64).unwrap_or(0),
151                                parts.next().map(to_u64).unwrap_or(0),
152                                parts.next().map(to_u64).unwrap_or(0),
153                                parts.next().map(to_u64).unwrap_or(0),
154                                parts.next().map(to_u64).unwrap_or(0),
155                                parts.next().map(to_u64).unwrap_or(0),
156                                parts.next().map(to_u64).unwrap_or(0),
157                                parts.next().map(to_u64).unwrap_or(0),
158                                parts.next().map(to_u64).unwrap_or(0),
159                            );
160                        }
161
162                        i += 1;
163                    }
164                }
165            }
166        }
167
168        if refresh_kind.frequency() {
169            #[cfg(feature = "multithread")]
170            use rayon::iter::{
171                IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
172            };
173
174            #[cfg(feature = "multithread")]
175            // This function is voluntarily made generic in case we want to generalize it.
176            fn iter_mut<'a, T>(
177                val: &'a mut T,
178            ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter
179            where
180                &'a mut T: rayon::iter::IntoParallelIterator,
181            {
182                val.par_iter_mut()
183            }
184
185            #[cfg(not(feature = "multithread"))]
186            fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> {
187                val.iter_mut()
188            }
189
190            // `get_cpu_frequency` is very slow, so better run it in parallel.
191            self.global_cpu.frequency = iter_mut(&mut self.cpus)
192                .enumerate()
193                .map(|(pos, proc_)| {
194                    proc_.frequency = get_cpu_frequency(pos);
195                    proc_.frequency
196                })
197                .max()
198                .unwrap_or(0);
199
200            self.got_cpu_frequency = true;
201        }
202
203        if first {
204            self.global_cpu.vendor_id = vendor_id;
205            self.global_cpu.brand = brand;
206        }
207    }
208
209    pub(crate) fn get_global_raw_times(&self) -> (u64, u64) {
210        (self.global_cpu.total_time, self.global_cpu.old_total_time)
211    }
212
213    pub(crate) fn len(&self) -> usize {
214        self.cpus.len()
215    }
216
217    pub(crate) fn is_empty(&self) -> bool {
218        self.cpus.is_empty()
219    }
220
221    pub(crate) fn set_need_cpus_update(&mut self) {
222        self.need_cpus_update = true;
223    }
224}
225
226/// Struct containing values to compute a CPU usage.
227#[derive(Clone, Copy)]
228pub(crate) struct CpuValues {
229    user: u64,
230    nice: u64,
231    system: u64,
232    idle: u64,
233    iowait: u64,
234    irq: u64,
235    softirq: u64,
236    steal: u64,
237    guest: u64,
238    guest_nice: u64,
239}
240
241impl CpuValues {
242    /// Creates a new instance of `CpuValues` with everything set to `0`.
243    pub fn new() -> CpuValues {
244        CpuValues {
245            user: 0,
246            nice: 0,
247            system: 0,
248            idle: 0,
249            iowait: 0,
250            irq: 0,
251            softirq: 0,
252            steal: 0,
253            guest: 0,
254            guest_nice: 0,
255        }
256    }
257
258    /// Sets the given argument to the corresponding fields.
259    pub fn set(
260        &mut self,
261        user: u64,
262        nice: u64,
263        system: u64,
264        idle: u64,
265        iowait: u64,
266        irq: u64,
267        softirq: u64,
268        steal: u64,
269        guest: u64,
270        guest_nice: u64,
271    ) {
272        // `guest` is already accounted in `user`.
273        self.user = user.saturating_sub(guest);
274        // `guest_nice` is already accounted in `nice`.
275        self.nice = nice.saturating_sub(guest_nice);
276        self.system = system;
277        self.idle = idle;
278        self.iowait = iowait;
279        self.irq = irq;
280        self.softirq = softirq;
281        self.steal = steal;
282        self.guest = guest;
283        self.guest_nice = guest_nice;
284    }
285
286    /// Returns work time.
287    pub fn work_time(&self) -> u64 {
288        self.user
289            .saturating_add(self.nice)
290            .saturating_add(self.system)
291            .saturating_add(self.irq)
292            .saturating_add(self.softirq)
293    }
294
295    /// Returns total time.
296    pub fn total_time(&self) -> u64 {
297        self.work_time()
298            .saturating_add(self.idle)
299            .saturating_add(self.iowait)
300            // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest"
301            // into the computation.
302            .saturating_add(self.guest)
303            .saturating_add(self.guest_nice)
304            .saturating_add(self.steal)
305    }
306}
307
308#[doc = include_str!("../../md_doc/cpu.md")]
309pub struct Cpu {
310    old_values: CpuValues,
311    new_values: CpuValues,
312    pub(crate) name: String,
313    cpu_usage: f32,
314    total_time: u64,
315    old_total_time: u64,
316    pub(crate) frequency: u64,
317    pub(crate) vendor_id: String,
318    pub(crate) brand: String,
319}
320
321impl Cpu {
322    pub(crate) fn new_with_values(
323        name: &str,
324        user: u64,
325        nice: u64,
326        system: u64,
327        idle: u64,
328        iowait: u64,
329        irq: u64,
330        softirq: u64,
331        steal: u64,
332        guest: u64,
333        guest_nice: u64,
334        frequency: u64,
335        vendor_id: String,
336        brand: String,
337    ) -> Cpu {
338        let mut new_values = CpuValues::new();
339        new_values.set(
340            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
341        );
342        Cpu {
343            name: name.to_owned(),
344            old_values: CpuValues::new(),
345            new_values,
346            cpu_usage: 0f32,
347            total_time: 0,
348            old_total_time: 0,
349            frequency,
350            vendor_id,
351            brand,
352        }
353    }
354
355    pub(crate) fn set(
356        &mut self,
357        user: u64,
358        nice: u64,
359        system: u64,
360        idle: u64,
361        iowait: u64,
362        irq: u64,
363        softirq: u64,
364        steal: u64,
365        guest: u64,
366        guest_nice: u64,
367    ) {
368        macro_rules! min {
369            ($a:expr, $b:expr, $def:expr) => {
370                if $a > $b {
371                    ($a - $b) as f32
372                } else {
373                    $def
374                }
375            };
376        }
377        self.old_values = self.new_values;
378        self.new_values.set(
379            user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice,
380        );
381        self.total_time = self.new_values.total_time();
382        self.old_total_time = self.old_values.total_time();
383        self.cpu_usage = min!(self.new_values.work_time(), self.old_values.work_time(), 0.)
384            / min!(self.total_time, self.old_total_time, 1.)
385            * 100.;
386        if self.cpu_usage > 100. {
387            self.cpu_usage = 100.; // to prevent the percentage to go above 100%
388        }
389    }
390}
391
392impl CpuExt for Cpu {
393    fn cpu_usage(&self) -> f32 {
394        self.cpu_usage
395    }
396
397    fn name(&self) -> &str {
398        &self.name
399    }
400
401    /// Returns the CPU frequency in MHz.
402    fn frequency(&self) -> u64 {
403        self.frequency
404    }
405
406    fn vendor_id(&self) -> &str {
407        &self.vendor_id
408    }
409
410    fn brand(&self) -> &str {
411        &self.brand
412    }
413}
414
415pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
416    let mut s = String::new();
417    if File::open(format!(
418        "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq",
419    ))
420    .and_then(|mut f| f.read_to_string(&mut s))
421    .is_ok()
422    {
423        let freq_option = s.trim().split('\n').next();
424        if let Some(freq_string) = freq_option {
425            if let Ok(freq) = freq_string.parse::<u64>() {
426                return freq / 1000;
427            }
428        }
429    }
430    s.clear();
431    if File::open("/proc/cpuinfo")
432        .and_then(|mut f| f.read_to_string(&mut s))
433        .is_err()
434    {
435        return 0;
436    }
437    let find_cpu_mhz = s.split('\n').find(|line| {
438        line.starts_with("cpu MHz\t")
439            || line.starts_with("BogoMIPS")
440            || line.starts_with("clock\t")
441            || line.starts_with("bogomips per cpu")
442    });
443    find_cpu_mhz
444        .and_then(|line| line.split(':').last())
445        .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
446        .map(|speed| speed as u64)
447        .unwrap_or_default()
448}
449
450#[allow(unused_assignments)]
451pub(crate) fn get_physical_core_count() -> Option<usize> {
452    let mut s = String::new();
453    if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) {
454        sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e);
455        return None;
456    }
457
458    macro_rules! add_core {
459        ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{
460            if !$core_id.is_empty() && !$physical_id.is_empty() {
461                $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id));
462            } else if !$cpu.is_empty() {
463                // On systems with only physical cores like raspberry, there is no "core id" or
464                // "physical id" fields. So if one of them is missing, we simply use the "CPU"
465                // info and count it as a physical core.
466                $core_ids_and_physical_ids.insert($cpu.to_owned());
467            }
468            $core_id = "";
469            $physical_id = "";
470            $cpu = "";
471        }};
472    }
473
474    let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
475    let mut core_id = "";
476    let mut physical_id = "";
477    let mut cpu = "";
478
479    for line in s.lines() {
480        if line.is_empty() {
481            add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
482        } else if line.starts_with("processor") {
483            cpu = line
484                .splitn(2, ':')
485                .last()
486                .map(|x| x.trim())
487                .unwrap_or_default();
488        } else if line.starts_with("core id") {
489            core_id = line
490                .splitn(2, ':')
491                .last()
492                .map(|x| x.trim())
493                .unwrap_or_default();
494        } else if line.starts_with("physical id") {
495            physical_id = line
496                .splitn(2, ':')
497                .last()
498                .map(|x| x.trim())
499                .unwrap_or_default();
500        }
501    }
502    add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu);
503
504    Some(core_ids_and_physical_ids.len())
505}
506
507/// Obtain the implementer of this CPU core.
508///
509/// This has been obtained from util-linux's lscpu implementation, see
510/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240
511///
512/// This list will have to be updated every time a new vendor appears, please keep it synchronized
513/// with util-linux and update the link above with the commit you have used.
514fn get_arm_implementer(implementer: u32) -> Option<&'static str> {
515    Some(match implementer {
516        0x41 => "ARM",
517        0x42 => "Broadcom",
518        0x43 => "Cavium",
519        0x44 => "DEC",
520        0x46 => "FUJITSU",
521        0x48 => "HiSilicon",
522        0x49 => "Infineon",
523        0x4d => "Motorola/Freescale",
524        0x4e => "NVIDIA",
525        0x50 => "APM",
526        0x51 => "Qualcomm",
527        0x53 => "Samsung",
528        0x56 => "Marvell",
529        0x61 => "Apple",
530        0x66 => "Faraday",
531        0x69 => "Intel",
532        0x70 => "Phytium",
533        0xc0 => "Ampere",
534        _ => return None,
535    })
536}
537
538/// Obtain the part of this CPU core.
539///
540/// This has been obtained from util-linux's lscpu implementation, see
541/// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L34
542///
543/// This list will have to be updated every time a new core appears, please keep it synchronized
544/// with util-linux and update the link above with the commit you have used.
545fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> {
546    Some(match (implementer, part) {
547        // ARM
548        (0x41, 0x810) => "ARM810",
549        (0x41, 0x920) => "ARM920",
550        (0x41, 0x922) => "ARM922",
551        (0x41, 0x926) => "ARM926",
552        (0x41, 0x940) => "ARM940",
553        (0x41, 0x946) => "ARM946",
554        (0x41, 0x966) => "ARM966",
555        (0x41, 0xa20) => "ARM1020",
556        (0x41, 0xa22) => "ARM1022",
557        (0x41, 0xa26) => "ARM1026",
558        (0x41, 0xb02) => "ARM11 MPCore",
559        (0x41, 0xb36) => "ARM1136",
560        (0x41, 0xb56) => "ARM1156",
561        (0x41, 0xb76) => "ARM1176",
562        (0x41, 0xc05) => "Cortex-A5",
563        (0x41, 0xc07) => "Cortex-A7",
564        (0x41, 0xc08) => "Cortex-A8",
565        (0x41, 0xc09) => "Cortex-A9",
566        (0x41, 0xc0d) => "Cortex-A17", // Originally A12
567        (0x41, 0xc0f) => "Cortex-A15",
568        (0x41, 0xc0e) => "Cortex-A17",
569        (0x41, 0xc14) => "Cortex-R4",
570        (0x41, 0xc15) => "Cortex-R5",
571        (0x41, 0xc17) => "Cortex-R7",
572        (0x41, 0xc18) => "Cortex-R8",
573        (0x41, 0xc20) => "Cortex-M0",
574        (0x41, 0xc21) => "Cortex-M1",
575        (0x41, 0xc23) => "Cortex-M3",
576        (0x41, 0xc24) => "Cortex-M4",
577        (0x41, 0xc27) => "Cortex-M7",
578        (0x41, 0xc60) => "Cortex-M0+",
579        (0x41, 0xd01) => "Cortex-A32",
580        (0x41, 0xd02) => "Cortex-A34",
581        (0x41, 0xd03) => "Cortex-A53",
582        (0x41, 0xd04) => "Cortex-A35",
583        (0x41, 0xd05) => "Cortex-A55",
584        (0x41, 0xd06) => "Cortex-A65",
585        (0x41, 0xd07) => "Cortex-A57",
586        (0x41, 0xd08) => "Cortex-A72",
587        (0x41, 0xd09) => "Cortex-A73",
588        (0x41, 0xd0a) => "Cortex-A75",
589        (0x41, 0xd0b) => "Cortex-A76",
590        (0x41, 0xd0c) => "Neoverse-N1",
591        (0x41, 0xd0d) => "Cortex-A77",
592        (0x41, 0xd0e) => "Cortex-A76AE",
593        (0x41, 0xd13) => "Cortex-R52",
594        (0x41, 0xd20) => "Cortex-M23",
595        (0x41, 0xd21) => "Cortex-M33",
596        (0x41, 0xd40) => "Neoverse-V1",
597        (0x41, 0xd41) => "Cortex-A78",
598        (0x41, 0xd42) => "Cortex-A78AE",
599        (0x41, 0xd43) => "Cortex-A65AE",
600        (0x41, 0xd44) => "Cortex-X1",
601        (0x41, 0xd46) => "Cortex-A510",
602        (0x41, 0xd47) => "Cortex-A710",
603        (0x41, 0xd48) => "Cortex-X2",
604        (0x41, 0xd49) => "Neoverse-N2",
605        (0x41, 0xd4a) => "Neoverse-E1",
606        (0x41, 0xd4b) => "Cortex-A78C",
607        (0x41, 0xd4c) => "Cortex-X1C",
608        (0x41, 0xd4d) => "Cortex-A715",
609        (0x41, 0xd4e) => "Cortex-X3",
610
611        // Broadcom
612        (0x42, 0x00f) => "Brahma-B15",
613        (0x42, 0x100) => "Brahma-B53",
614        (0x42, 0x516) => "ThunderX2",
615
616        // Cavium
617        (0x43, 0x0a0) => "ThunderX",
618        (0x43, 0x0a1) => "ThunderX-88XX",
619        (0x43, 0x0a2) => "ThunderX-81XX",
620        (0x43, 0x0a3) => "ThunderX-83XX",
621        (0x43, 0x0af) => "ThunderX2-99xx",
622
623        // DEC
624        (0x44, 0xa10) => "SA110",
625        (0x44, 0xa11) => "SA1100",
626
627        // Fujitsu
628        (0x46, 0x001) => "A64FX",
629
630        // HiSilicon
631        (0x48, 0xd01) => "Kunpeng-920", // aka tsv110
632
633        // NVIDIA
634        (0x4e, 0x000) => "Denver",
635        (0x4e, 0x003) => "Denver 2",
636        (0x4e, 0x004) => "Carmel",
637
638        // APM
639        (0x50, 0x000) => "X-Gene",
640
641        // Qualcomm
642        (0x51, 0x00f) => "Scorpion",
643        (0x51, 0x02d) => "Scorpion",
644        (0x51, 0x04d) => "Krait",
645        (0x51, 0x06f) => "Krait",
646        (0x51, 0x201) => "Kryo",
647        (0x51, 0x205) => "Kryo",
648        (0x51, 0x211) => "Kryo",
649        (0x51, 0x800) => "Falkor-V1/Kryo",
650        (0x51, 0x801) => "Kryo-V2",
651        (0x51, 0x802) => "Kryo-3XX-Gold",
652        (0x51, 0x803) => "Kryo-3XX-Silver",
653        (0x51, 0x804) => "Kryo-4XX-Gold",
654        (0x51, 0x805) => "Kryo-4XX-Silver",
655        (0x51, 0xc00) => "Falkor",
656        (0x51, 0xc01) => "Saphira",
657
658        // Samsung
659        (0x53, 0x001) => "exynos-m1",
660
661        // Marvell
662        (0x56, 0x131) => "Feroceon-88FR131",
663        (0x56, 0x581) => "PJ4/PJ4b",
664        (0x56, 0x584) => "PJ4B-MP",
665
666        // Apple
667        (0x61, 0x020) => "Icestorm-A14",
668        (0x61, 0x021) => "Firestorm-A14",
669        (0x61, 0x022) => "Icestorm-M1",
670        (0x61, 0x023) => "Firestorm-M1",
671        (0x61, 0x024) => "Icestorm-M1-Pro",
672        (0x61, 0x025) => "Firestorm-M1-Pro",
673        (0x61, 0x028) => "Icestorm-M1-Max",
674        (0x61, 0x029) => "Firestorm-M1-Max",
675        (0x61, 0x030) => "Blizzard-A15",
676        (0x61, 0x031) => "Avalanche-A15",
677        (0x61, 0x032) => "Blizzard-M2",
678        (0x61, 0x033) => "Avalanche-M2",
679
680        // Faraday
681        (0x66, 0x526) => "FA526",
682        (0x66, 0x626) => "FA626",
683
684        // Intel
685        (0x69, 0x200) => "i80200",
686        (0x69, 0x210) => "PXA250A",
687        (0x69, 0x212) => "PXA210A",
688        (0x69, 0x242) => "i80321-400",
689        (0x69, 0x243) => "i80321-600",
690        (0x69, 0x290) => "PXA250B/PXA26x",
691        (0x69, 0x292) => "PXA210B",
692        (0x69, 0x2c2) => "i80321-400-B0",
693        (0x69, 0x2c3) => "i80321-600-B0",
694        (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x",
695        (0x69, 0x2d2) => "PXA210C",
696        (0x69, 0x411) => "PXA27x",
697        (0x69, 0x41c) => "IPX425-533",
698        (0x69, 0x41d) => "IPX425-400",
699        (0x69, 0x41f) => "IPX425-266",
700        (0x69, 0x682) => "PXA32x",
701        (0x69, 0x683) => "PXA930/PXA935",
702        (0x69, 0x688) => "PXA30x",
703        (0x69, 0x689) => "PXA31x",
704        (0x69, 0xb11) => "SA1110",
705        (0x69, 0xc12) => "IPX1200",
706
707        // Phytium
708        (0x70, 0x660) => "FTC660",
709        (0x70, 0x661) => "FTC661",
710        (0x70, 0x662) => "FTC662",
711        (0x70, 0x663) => "FTC663",
712
713        _ => return None,
714    })
715}
716
717/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
718pub(crate) fn get_vendor_id_and_brand() -> (String, String) {
719    let mut s = String::new();
720    if File::open("/proc/cpuinfo")
721        .and_then(|mut f| f.read_to_string(&mut s))
722        .is_err()
723    {
724        return (String::new(), String::new());
725    }
726
727    fn get_value(s: &str) -> String {
728        s.split(':')
729            .last()
730            .map(|x| x.trim().to_owned())
731            .unwrap_or_default()
732    }
733
734    fn get_hex_value(s: &str) -> u32 {
735        s.split(':')
736            .last()
737            .map(|x| x.trim())
738            .filter(|x| x.starts_with("0x"))
739            .map(|x| u32::from_str_radix(&x[2..], 16).unwrap())
740            .unwrap_or_default()
741    }
742
743    let mut vendor_id = None;
744    let mut brand = None;
745    let mut implementer = None;
746    let mut part = None;
747
748    for it in s.split('\n') {
749        if it.starts_with("vendor_id\t") {
750            vendor_id = Some(get_value(it));
751        } else if it.starts_with("model name\t") {
752            brand = Some(get_value(it));
753        } else if it.starts_with("CPU implementer\t") {
754            implementer = Some(get_hex_value(it));
755        } else if it.starts_with("CPU part\t") {
756            part = Some(get_hex_value(it));
757        } else {
758            continue;
759        }
760        if (brand.is_some() && vendor_id.is_some()) || (implementer.is_some() && part.is_some()) {
761            break;
762        }
763    }
764    if let (Some(implementer), Some(part)) = (implementer, part) {
765        vendor_id = get_arm_implementer(implementer).map(String::from);
766        // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve
767        // the brand from "CPU part", we will then use the value from "model name".
768        //
769        // Example from raspberry pi 3B+:
770        //
771        // ```
772        // model name      : ARMv7 Processor rev 4 (v7l)
773        // CPU implementer : 0x41
774        // CPU part        : 0xd03
775        // ```
776        brand = get_arm_part(implementer, part).map(String::from).or(brand);
777    }
778    (vendor_id.unwrap_or_default(), brand.unwrap_or_default())
779}