riscfetch_core/
lib.rs

1//! RISC-V system information library
2//!
3//! Provides functions to detect and query RISC-V specific system information
4//! including ISA extensions, hardware IDs, vector capabilities, and more.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use riscfetch_core::*;
10//!
11//! if is_riscv() {
12//!     println!("ISA: {}", get_isa_string());
13//!     println!("Extensions: {}", get_extensions_compact());
14//! }
15//! ```
16
17mod hardware;
18mod parsing;
19mod system;
20mod types;
21
22// Re-export types
23pub use types::{CacheInfo, HardwareIds, SystemInfo, VectorInfo};
24
25// Re-export parsing functions
26pub use parsing::{
27    parse_extensions_compact, parse_extensions_explained, parse_vector_from_isa,
28    parse_z_extensions, parse_z_extensions_explained,
29};
30
31// Re-export hardware functions
32pub use hardware::{
33    get_board_info, get_cache_info, get_hardware_ids, get_hart_count, get_hart_count_num,
34    get_isa_string, get_vector_detail,
35};
36
37// Re-export system functions
38pub use system::{
39    get_kernel_info, get_memory_bytes, get_memory_info, get_os_info, get_uptime, get_uptime_seconds,
40};
41
42use std::fs;
43use std::process::Command;
44use sysinfo::System;
45
46/// Check if the current system is RISC-V architecture
47pub fn is_riscv() -> bool {
48    if let Ok(output) = Command::new("uname").arg("-m").output() {
49        let arch = String::from_utf8_lossy(&output.stdout);
50        if arch.contains("riscv") {
51            return true;
52        }
53    }
54
55    if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
56        if content.contains("riscv") || content.contains("RISC-V") {
57            return true;
58        }
59    }
60
61    false
62}
63
64/// Get compact extension list (e.g., "I M A F D C V")
65pub fn get_extensions_compact() -> String {
66    parse_extensions_compact(&get_isa_string())
67}
68
69/// Get Z-extensions as compact string
70pub fn get_z_extensions() -> String {
71    parse_z_extensions(&get_isa_string())
72}
73
74/// Get extensions with explanations
75pub fn get_extensions_explained() -> Vec<(String, String)> {
76    parse_extensions_explained(&get_isa_string())
77}
78
79/// Get Z-extensions with explanations
80pub fn get_z_extensions_explained() -> Vec<(String, String)> {
81    parse_z_extensions_explained(&get_isa_string())
82}
83
84/// Collect all information into a single struct
85pub fn collect_all_info() -> SystemInfo {
86    let mut sys = System::new();
87    sys.refresh_memory();
88    sys.refresh_cpu_all();
89
90    let isa = get_isa_string();
91    let exts: Vec<String> = get_extensions_explained()
92        .into_iter()
93        .map(|(name, _)| name)
94        .collect();
95    let z_exts: Vec<String> = get_z_extensions_explained()
96        .into_iter()
97        .map(|(name, _)| name)
98        .collect();
99
100    let hw_ids = get_hardware_ids();
101    let isa_lower = isa.to_lowercase();
102    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
103
104    SystemInfo {
105        isa,
106        extensions: exts,
107        z_extensions: z_exts,
108        vector: VectorInfo {
109            enabled: base.contains('v') || isa_lower.contains("zve"),
110            vlen: None,
111            elen: None,
112        },
113        hart_count: sys.cpus().len(),
114        hardware_ids: hw_ids,
115        cache: CacheInfo::default(),
116        board: get_board_info(),
117        memory_used_bytes: sys.used_memory(),
118        memory_total_bytes: sys.total_memory(),
119        kernel: get_kernel_info(),
120        os: get_os_info(),
121        uptime_seconds: System::uptime(),
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    // Real ISA strings from actual RISC-V systems for testing
130    const ISA_VISIONFIVE2: &str = "rv64imafdc_zicntr_zicsr_zifencei_zihpm_zba_zbb";
131    const ISA_SPACEMIT_K1: &str = "rv64imafdcv_zicbom_zicboz_zicntr_zicsr_zifencei_zihintpause_zihpm_zba_zbb_zbc_zbs_zkt_zvkt_zvl128b_zvl256b_zvl32b_zvl64b";
132    const ISA_MINIMAL: &str = "rv64imac";
133    const ISA_RV32: &str = "rv32imc";
134
135    // === ISA Parsing Tests ===
136
137    #[test]
138    fn test_parse_extensions_compact_visionfive2() {
139        let result = parse_extensions_compact(ISA_VISIONFIVE2);
140        assert_eq!(result, "I M A F D C");
141    }
142
143    #[test]
144    fn test_parse_extensions_compact_spacemit() {
145        let result = parse_extensions_compact(ISA_SPACEMIT_K1);
146        assert_eq!(result, "I M A F D C V");
147    }
148
149    #[test]
150    fn test_parse_extensions_compact_minimal() {
151        let result = parse_extensions_compact(ISA_MINIMAL);
152        assert_eq!(result, "I M A C");
153    }
154
155    #[test]
156    fn test_parse_extensions_compact_rv32() {
157        let result = parse_extensions_compact(ISA_RV32);
158        assert_eq!(result, "I M C");
159    }
160
161    #[test]
162    fn test_parse_extensions_compact_unknown() {
163        let result = parse_extensions_compact("unknown");
164        assert_eq!(result, "");
165    }
166
167    // === Z-Extension Tests ===
168
169    #[test]
170    fn test_parse_z_extensions_visionfive2() {
171        let result = parse_z_extensions(ISA_VISIONFIVE2);
172        assert!(result.contains("zicntr"));
173        assert!(result.contains("zicsr"));
174        assert!(result.contains("zifencei"));
175        assert!(result.contains("zba"));
176        assert!(result.contains("zbb"));
177    }
178
179    #[test]
180    fn test_parse_z_extensions_spacemit() {
181        let result = parse_z_extensions(ISA_SPACEMIT_K1);
182        assert!(result.contains("zicbom"));
183        assert!(result.contains("zicboz"));
184        assert!(result.contains("zbc"));
185        assert!(result.contains("zbs"));
186        assert!(result.contains("zvl256b"));
187    }
188
189    #[test]
190    fn test_parse_z_extensions_minimal() {
191        let result = parse_z_extensions(ISA_MINIMAL);
192        assert!(result.is_empty());
193    }
194
195    // === Explained Extensions Tests ===
196
197    #[test]
198    fn test_parse_extensions_explained_visionfive2() {
199        let result = parse_extensions_explained(ISA_VISIONFIVE2);
200        assert_eq!(result.len(), 6); // I M A F D C
201        assert!(result.iter().any(|(n, _)| n == "I"));
202        assert!(result.iter().any(|(n, _)| n == "M"));
203        assert!(result.iter().any(|(n, _)| n == "F"));
204        assert!(result.iter().any(|(n, _)| n == "D"));
205        assert!(result.iter().any(|(n, _)| n == "C"));
206    }
207
208    #[test]
209    fn test_parse_z_extensions_explained_spacemit() {
210        let result = parse_z_extensions_explained(ISA_SPACEMIT_K1);
211        assert!(result
212            .iter()
213            .any(|(n, d)| n == "Zba" && d == "Address Generation"));
214        assert!(result
215            .iter()
216            .any(|(n, d)| n == "Zbb" && d == "Basic Bit Manipulation"));
217        assert!(result
218            .iter()
219            .any(|(n, d)| n == "Zbc" && d == "Carry-less Multiply"));
220    }
221
222    // === Vector Tests ===
223
224    #[test]
225    fn test_parse_vector_no_vector() {
226        let result = parse_vector_from_isa(ISA_VISIONFIVE2);
227        assert!(result.is_none());
228    }
229
230    #[test]
231    fn test_parse_vector_with_v() {
232        let result = parse_vector_from_isa(ISA_SPACEMIT_K1);
233        assert!(result.is_some());
234        let detail = result.unwrap();
235        assert!(detail.contains("Enabled"));
236        assert!(detail.contains("VLEN>=256"));
237    }
238
239    #[test]
240    fn test_parse_vector_zve_only() {
241        let isa = "rv64imac_zve32x";
242        let result = parse_vector_from_isa(isa);
243        assert!(result.is_some());
244        assert!(result.unwrap().contains("Enabled"));
245    }
246
247    // === System Info Tests (these work on any system) ===
248
249    #[test]
250    fn test_get_uptime() {
251        let uptime = get_uptime();
252        assert!(!uptime.is_empty());
253    }
254
255    #[test]
256    fn test_get_uptime_seconds() {
257        let secs = get_uptime_seconds();
258        assert!(secs > 0);
259    }
260
261    #[test]
262    fn test_get_memory_bytes() {
263        let (used, total) = get_memory_bytes();
264        assert!(total > 0);
265        assert!(used <= total);
266    }
267
268    #[test]
269    fn test_get_kernel_info() {
270        let kernel = get_kernel_info();
271        assert!(!kernel.is_empty());
272    }
273
274    #[test]
275    fn test_get_os_info() {
276        let os = get_os_info();
277        assert!(!os.is_empty());
278    }
279
280    #[test]
281    fn test_hardware_ids_default() {
282        let ids = HardwareIds::default();
283        assert!(ids.mvendorid.is_empty());
284        assert!(ids.marchid.is_empty());
285        assert!(ids.mimpid.is_empty());
286    }
287
288    // === Edge Cases ===
289
290    #[test]
291    fn test_parse_extensions_case_insensitive() {
292        let upper = parse_extensions_compact("RV64IMAFDC");
293        let lower = parse_extensions_compact("rv64imafdc");
294        assert_eq!(upper, lower);
295    }
296
297    #[test]
298    fn test_parse_empty_isa() {
299        let result = parse_extensions_compact("");
300        assert_eq!(result, "");
301    }
302
303    #[test]
304    fn test_parse_z_extensions_order_preserved() {
305        let result = parse_z_extensions("rv64i_zba_zbb_zbc");
306        let parts: Vec<&str> = result.split(' ').collect();
307        assert_eq!(parts, vec!["zba", "zbb", "zbc"]);
308    }
309
310    // =======================================================
311    // Specification-based tests (from SPEC.md)
312    // =======================================================
313
314    mod spec_tests {
315        use super::*;
316
317        #[test]
318        fn spec_g_expansion() {
319            assert_eq!(parse_extensions_compact("rv64gc"), "I M A F D C");
320        }
321
322        #[test]
323        fn spec_g_expansion_uppercase() {
324            assert_eq!(parse_extensions_compact("RV64GC"), "I M A F D C");
325        }
326
327        #[test]
328        fn spec_e_extension() {
329            assert_eq!(parse_extensions_compact("rv32e"), "E");
330        }
331
332        #[test]
333        fn spec_e_with_c() {
334            assert_eq!(parse_extensions_compact("rv32ec"), "E C");
335        }
336
337        #[test]
338        fn spec_standard_extensions() {
339            assert_eq!(parse_extensions_compact("rv64imafdc"), "I M A F D C");
340        }
341
342        #[test]
343        fn spec_with_vector() {
344            assert_eq!(parse_extensions_compact("rv64imafdcv"), "I M A F D C V");
345        }
346
347        #[test]
348        fn spec_rv64_prefix_not_vector() {
349            let result = parse_extensions_compact("rv64imafdc");
350            assert!(!result.contains("V"));
351        }
352
353        #[test]
354        fn spec_z_extensions_ignored() {
355            assert_eq!(
356                parse_extensions_compact("rv64imafdc_zba_zbb"),
357                "I M A F D C"
358            );
359        }
360
361        #[test]
362        fn spec_empty_input() {
363            assert_eq!(parse_extensions_compact(""), "");
364        }
365
366        #[test]
367        fn spec_invalid_input() {
368            assert_eq!(parse_extensions_compact("unknown"), "");
369        }
370
371        #[test]
372        fn spec_rv64_only() {
373            assert_eq!(parse_extensions_compact("rv64"), "");
374        }
375
376        #[test]
377        fn spec_z_extensions_basic() {
378            assert_eq!(parse_z_extensions("rv64i_zicsr_zifencei"), "zicsr zifencei");
379        }
380
381        #[test]
382        fn spec_z_extensions_order() {
383            assert_eq!(parse_z_extensions("rv64i_zba_zbb_zbc"), "zba zbb zbc");
384        }
385
386        #[test]
387        fn spec_s_extensions() {
388            let result = parse_z_extensions("rv64i_sstc");
389            assert!(result.contains("sstc"));
390        }
391
392        #[test]
393        fn spec_z_extensions_none() {
394            assert_eq!(parse_z_extensions("rv64imafdc"), "");
395        }
396
397        #[test]
398        fn spec_z_extensions_g_implies() {
399            assert_eq!(parse_z_extensions("rv64gc"), "zicsr zifencei");
400        }
401
402        #[test]
403        fn spec_z_extensions_case() {
404            let result = parse_z_extensions("rv64i_Zicsr");
405            assert_eq!(result, "zicsr");
406        }
407
408        #[test]
409        fn spec_vector_with_v() {
410            let result = parse_vector_from_isa("rv64imafdcv");
411            assert!(result.is_some());
412            assert!(result.unwrap().contains("Enabled"));
413        }
414
415        #[test]
416        fn spec_vector_none() {
417            let result = parse_vector_from_isa("rv64imafdc");
418            assert!(result.is_none());
419        }
420
421        #[test]
422        fn spec_vector_zve() {
423            let result = parse_vector_from_isa("rv64imac_zve32x");
424            assert!(result.is_some());
425        }
426
427        #[test]
428        fn spec_vector_vlen_256() {
429            let result = parse_vector_from_isa("rv64imafdcv_zvl256b");
430            assert!(result.is_some());
431            assert!(result.unwrap().contains("VLEN>=256"));
432        }
433
434        #[test]
435        fn spec_vector_vlen_largest() {
436            let result = parse_vector_from_isa("rv64imafdcv_zvl128b_zvl256b");
437            assert!(result.is_some());
438            assert!(result.unwrap().contains("VLEN>=256"));
439        }
440
441        #[test]
442        fn spec_vector_no_default_vlen() {
443            let result = parse_vector_from_isa("rv64imafdcv");
444            assert!(result.is_some());
445            let detail = result.unwrap();
446            assert!(detail.contains("Enabled"));
447            assert!(!detail.contains("VLEN"));
448        }
449    }
450
451    // =======================================================
452    // RISC-V Hardware Tests (only run on actual RISC-V)
453    // =======================================================
454
455    #[cfg(target_arch = "riscv64")]
456    mod riscv_hardware_tests {
457        use super::*;
458
459        #[test]
460        fn hw_is_riscv() {
461            assert!(is_riscv());
462        }
463
464        #[test]
465        fn hw_isa_string_valid() {
466            let isa = get_isa_string();
467            assert!(isa.starts_with("rv64") || isa.starts_with("rv32"));
468        }
469
470        #[test]
471        fn hw_isa_string_has_base() {
472            let isa = get_isa_string();
473            assert!(isa.contains('i') || isa.contains('e'));
474        }
475
476        #[test]
477        fn hw_extensions_not_empty() {
478            let ext = get_extensions_compact();
479            assert!(!ext.is_empty());
480            assert!(ext.contains('I') || ext.contains('E'));
481        }
482
483        #[test]
484        fn hw_hart_count_positive() {
485            let hart_str = get_hart_count();
486            assert!(hart_str.contains("hart"));
487            let num: String = hart_str
488                .chars()
489                .take_while(|c| c.is_ascii_digit())
490                .collect();
491            let count: usize = num.parse().unwrap_or(0);
492            assert!(count > 0);
493        }
494
495        #[test]
496        fn hw_hardware_ids_present() {
497            let ids = get_hardware_ids();
498            let has_any =
499                !ids.mvendorid.is_empty() || !ids.marchid.is_empty() || !ids.mimpid.is_empty();
500            assert!(has_any);
501        }
502
503        #[test]
504        fn hw_collect_all_info() {
505            let info = collect_all_info();
506            assert!(!info.isa.is_empty());
507            assert!(!info.extensions.is_empty());
508            assert!(info.hart_count > 0);
509        }
510    }
511}