rcpufetch 0.0.5

[ALPHA] A rusty crossplatform, but simple CLI binutil for reading CPU information.
//! ARM/AArch64 detection from a MIDR_EL1-style register value. The register itself has to
//! be supplied by the OS layer (Linux: `/proc/cpuinfo` "CPU implementer"/"CPU part"/etc,
//! macOS: no MIDR access, mapped instead from `sysctl hw.cpufamily`, BSDs: `sysctl
//! hw.model`/`machdep.cpu*` where available) since reading it directly requires a
//! privileged instruction on real hardware.

use super::{ArchInfo, tables::arm_uarch};

pub struct Midr {
    pub implementer: u8,
    pub part: u32,
    pub variant: Option<u32>,
    pub revision: Option<u32>,
}

pub fn decode_midr(midr: u32) -> Midr {
    Midr {
        implementer: ((midr >> 24) & 0xFF) as u8,
        part: (midr >> 4) & 0xFFF,
        variant: Some((midr >> 20) & 0xF),
        revision: Some(midr & 0xF),
    }
}

fn implementer_vendor(implementer: u8) -> &'static str {
    match implementer {
        b'A' => "ARM",
        b'B' => "Broadcom",
        b'C' => "Cavium",
        b'H' => "HiSilicon",
        b'N' => "Nvidia",
        b'P' => "APM",
        b'Q' => "Qualcomm",
        b'S' => "Samsung",
        b'V' => "Marvell",
        b'a' => "Apple",
        b'F' => "Fujitsu",
        b'M' => "Ampere",
        _ => "Unknown",
    }
}

fn lookup_uarch(midr: &Midr) -> Option<(&'static str, &'static str)> {
    arm_uarch::ARM_UARCH
        .iter()
        .find_map(|&(im, part, variant, revision, name, vendor)| {
            let ok = im == midr.implementer
                && part == midr.part
                && variant.is_none_or(|v| Some(v) == midr.variant)
                && revision.is_none_or(|v| Some(v) == midr.revision);
            ok.then_some((name, vendor))
        })
}

/// Detect uarch/vendor from a raw MIDR value read by the OS layer.
pub fn detect_from_midr(midr_raw: u32) -> ArchInfo {
    let midr = decode_midr(midr_raw);
    match lookup_uarch(&midr) {
        Some((name, vendor)) => ArchInfo {
            vendor: Some(vendor.to_string()),
            uarch: Some(name.to_string()),
            soc: None,
            process_nm: None,
            features: Vec::new(),
        },
        None => ArchInfo {
            vendor: Some(implementer_vendor(midr.implementer).to_string()),
            uarch: None,
            soc: None,
            process_nm: None,
            features: Vec::new(),
        },
    }
}

/// A modest marketing-name table for common SoCs, matched by substring against
/// `/proc/cpuinfo`'s "Hardware"/"model name" field or the linux device-tree "compatible"
/// string. This is a small subset of upstream cpufetch's ~415-entry table (Raspberry Pi,
/// mainstream Snapdragon/Exynos/MediaTek/Apple/Ampere/Rockchip parts) rather than a full
/// port -- extend `SOC_HINTS` as more platforms come up.
pub static SOC_HINTS: &[(&str, &str)] = &[
    ("bcm2712", "Broadcom BCM2712 (Raspberry Pi 5)"),
    ("bcm2711", "Broadcom BCM2711 (Raspberry Pi 4)"),
    ("bcm2837", "Broadcom BCM2837 (Raspberry Pi 3)"),
    ("bcm2835", "Broadcom BCM2835 (Raspberry Pi 1/Zero)"),
    ("sm8650", "Qualcomm Snapdragon 8 Gen 3"),
    ("sm8550", "Qualcomm Snapdragon 8 Gen 2"),
    ("sm8450", "Qualcomm Snapdragon 8 Gen 1"),
    ("exynos2400", "Samsung Exynos 2400"),
    ("exynos990", "Samsung Exynos 990"),
    ("mt6989", "MediaTek Dimensity 9300"),
    ("mt6985", "MediaTek Dimensity 9200"),
    ("rk3588", "Rockchip RK3588"),
    ("rk3399", "Rockchip RK3399"),
    ("ampereone", "Ampere AmpereOne"),
    ("ampere altra", "Ampere Altra"),
    ("thunderx", "Marvell/Cavium ThunderX"),
    ("kunpeng", "HiSilicon Kunpeng"),
];

pub fn soc_from_hint(haystack: &str) -> Option<&'static str> {
    let lower = haystack.to_ascii_lowercase();
    SOC_HINTS
        .iter()
        .find(|(needle, _)| lower.contains(needle))
        .map(|(_, name)| *name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn decodes_cortex_a76_midr() {
        // Real MIDR for a Cortex-A76: implementer 'A' (0x41), part 0xD0B.
        let midr_raw: u32 = (0x41 << 24) | (0xD0B << 4) | 0x1;
        let info = detect_from_midr(midr_raw);
        assert_eq!(info.uarch.as_deref(), Some("Cortex-A76"));
        assert_eq!(info.vendor.as_deref(), Some("ARM"));
    }

    #[test]
    fn unknown_part_falls_back_to_implementer_vendor_only() {
        let midr_raw: u32 = (0x51 << 24) | (0xFFF << 4); // Qualcomm implementer, bogus part
        let info = detect_from_midr(midr_raw);
        assert_eq!(info.uarch, None);
        assert_eq!(info.vendor.as_deref(), Some("Qualcomm"));
    }

    #[test]
    fn soc_hint_matches_case_insensitively() {
        assert_eq!(
            soc_from_hint("Raspberry Pi 5 (BCM2712)"),
            Some("Broadcom BCM2712 (Raspberry Pi 5)")
        );
        assert_eq!(soc_from_hint("totally unknown board"), None);
    }
}