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