libcpuname 0.1.3

Identify CPU vendors, chips, and cores across multiple architectures
Documentation
use cores::open_core_name;
use err::{CoreNameError, VendorError};

mod cores;
/// Errors that can be produced by functions in this module.
pub mod err;
mod vendors;

static MAX_JEDEC_COMPANY_ID_BANK: u32 = 1 << 25;
static MAX_JEDEC_COMPANY_ID_OFFSET: u8 = (1 << 7) - 2;

#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug)]
/// A company with a registered JEDEC manufacturer identification code.
///
/// This struct encapsulates information about a manufacturer for use by this library's internals.
/// It is not intended to be used directly.
pub struct Manufacturer {
    bank: u32,
    offset: u8,
    name: &'static str,
}

#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug)]
/// A vendor of RISC-V CPUs.
///
/// The RISC-V specification declares two types of vendors:
/// - [Commercial][`Vendor::Commercial`] entities, which have a corresponding [JEDEC company code](https://www.jedec.org/standards-documents/docs/jep-106ab).
///   This company code's components, the 25-bit "bank" and 7-bit "offset", are encoded within the 32-bit `mvendorid` register.
/// - [Non-commercial][`Vendor::NonCommercial`] entities, which are not individually identified and simply encode a value of 0 in the `mvendorid` register.
///
/// The [`TryFrom`] trait can be used to convert an `mvendorid` value into one of these values.
///
/// # Example
///
/// ```rust
/// # use libcpuname::riscv::Vendor;
/// # fn main() -> Result<(), libcpuname::riscv::err::VendorError> {
/// let vendor = Vendor::try_from(0)?;
/// println!("vendor='{vendor}'");
/// # Ok(())
/// # }
pub enum Vendor {
    /// Any non-commercial vendor.
    NonCommercial,
    /// A commercial vendor with a JEDEC-registered company code and name.
    Commercial(Manufacturer),
}

impl Vendor {
    /// The name of this vendor.
    ///
    /// If the vendor is [non-commercial][`Vendor::NonCommercial`], this function will return the string "Non-Commercial".
    /// If the vendor is [commercial][`Vendor::Commercial`], this function will return the JEDEC-registered name of the commercial entity.
    /// This is a convenience method for obtaining the [`str`] representation directly; for printing, use the [Display][`core::fmt::Display`] trait instead.
    ///
    /// This function is marked as `const` and may be evaluated at compile-time.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use libcpuname::riscv::Vendor;
    /// # fn main() {
    /// println!("{}", Vendor::NonCommercial.name()); // prints "Non-Commercial"
    /// # }
    #[must_use]
    #[deprecated(since = "0.1.2", note = "please use `to_string()` instead")]
    pub const fn name(&self) -> &'static str {
        match self {
            Self::NonCommercial => "Non-Commercial",
            Self::Commercial(m) => m.name,
        }
    }
}

impl TryFrom<u32> for Vendor {
    type Error = VendorError;

    fn try_from(mvendorid: u32) -> Result<Self, Self::Error> {
        if mvendorid == 0 {
            return Ok(Self::NonCommercial);
        }

        let bank = (mvendorid >> 7) + 1;

        let offset = (mvendorid << 25 >> 25) as u8;
        if offset > MAX_JEDEC_COMPANY_ID_OFFSET {
            return Err(VendorError::InvalidOffset(offset));
        }

        vendors::jedec_company_name(bank, offset)
            .map(|name| Manufacturer { bank, offset, name })
            .map(Vendor::Commercial)
    }
}

macro_rules! try_from_int_impl_for_vendor {
    ($t:tt) => {
        impl TryFrom<$t> for Vendor {
            type Error = VendorError;

            fn try_from(mvendorid: $t) -> Result<Self, Self::Error> {
                match u32::try_from(mvendorid) {
                    Ok(v) => Vendor::try_from(v),
                    Err(_) => {
                        let bank = (mvendorid >> 7 << 7) + 1;
                        Err(VendorError::InvalidBank(bank as u32))
                    }
                }
            }
        }
    };
}

try_from_int_impl_for_vendor!(u8);
try_from_int_impl_for_vendor!(u16);
try_from_int_impl_for_vendor!(u64);
try_from_int_impl_for_vendor!(u128);
try_from_int_impl_for_vendor!(usize);

try_from_int_impl_for_vendor!(i16);
try_from_int_impl_for_vendor!(i32);
try_from_int_impl_for_vendor!(i64);
try_from_int_impl_for_vendor!(i128);
try_from_int_impl_for_vendor!(isize);

macro_rules! from_vendor_impl_for_int {
    ($t:tt) => {
        impl From<Vendor> for $t {
            fn from(value: Vendor) -> Self {
                match value {
                    Vendor::NonCommercial => 0,
                    Vendor::Commercial(manufacturer) => {
                        $t::from((manufacturer.bank - 1) << 7) + $t::from(manufacturer.offset)
                    }
                }
            }
        }
    };
}

from_vendor_impl_for_int!(u32);
from_vendor_impl_for_int!(u64);
from_vendor_impl_for_int!(u128);

from_vendor_impl_for_int!(i64);
from_vendor_impl_for_int!(i128);

impl core::fmt::Display for Vendor {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
        match self {
            Self::NonCommercial => write!(f, "Non-Commercial"),
            Self::Commercial(m) => write!(f, "{}", m.name),
        }
    }
}

/// Find the codename of a **32-bit** hart's microarchitecture.
///
/// This function will return a string with a static lifetime that represents the core microarchitecture(s) that correspond to the input register values.
/// Each parameter is dependent on the one before it - a given `marchid` value may return different results depending on the given `vendor`.
/// If the input values correspond to multiple cores, the returned string will contain all known names that correspond to those values, separated by the delimiter ` / `.
///
/// This function should ONLY be used for RV32 CPUs as the `marchid` register's length is dependent on the hart's bit width.
/// Use the [`core_name_rv64`] function for RV64 CPUs.
///
/// # Errors
///
/// An error will be returned if the input could not be resolved to a known core name.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), libcpuname::riscv::err::Error> {
/// let mvendorid: u32 = 0;
/// let marchid: u32 = 24;
/// let vendor = libcpuname::riscv::Vendor::try_from(mvendorid)?;
/// let core = libcpuname::riscv::core_name_rv32(vendor, marchid)?;
/// println!("vendor='{vendor}', core='{core}'");
/// # Ok(())
/// # }
/// ```
pub const fn core_name_rv32(vendor: Vendor, marchid: u32) -> Result<&'static str, CoreNameError> {
    if marchid == 0 {
        return Err(CoreNameError::ZeroedMarchid(marchid as u64));
    }

    if marchid >> 31 == 0 {
        return open_core_name(marchid as u64);
    }

    match vendor {
        Vendor::NonCommercial => Err(CoreNameError::MismatchedInput(vendor, marchid as u64)),
        Vendor::Commercial(..) => {
            // once we have known names, make sure to clear the MSB before parsing
            Err(CoreNameError::NoKnownNamesForVendor(vendor))
        }
    }
}

/// Find the codename of a **64-bit** hart's microarchitecture.
///
/// This function will return a string with a static lifetime that represents the core microarchitecture(s) that correspond to the input register values.
/// Each parameter is dependent on the one before it - a given `marchid` value may return different results depending on the given `vendor`.
/// If the input values correspond to multiple cores, the returned string will contain all known names that correspond to those values, separated by the delimiter ` / `.
///
/// This function should ONLY be used for RV64 CPUs as the `marchid` register's length is dependent on the hart's bit width.
/// Use the [`core_name_rv32`] function for RV32 CPUs.
///
/// # Errors
///
/// An error will be returned if the input could not be resolved to a known core name.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), libcpuname::riscv::err::Error> {
/// let mvendorid: u32 = 0;
/// let marchid: u64 = 24;
/// let vendor = libcpuname::riscv::Vendor::try_from(mvendorid)?;
/// let core = libcpuname::riscv::core_name_rv64(vendor, marchid)?;
/// println!("vendor='{vendor}', core='{core}'");
/// # Ok(())
/// # }
/// ```
pub const fn core_name_rv64(vendor: Vendor, marchid: u64) -> Result<&'static str, CoreNameError> {
    if marchid == 0 {
        return Err(CoreNameError::ZeroedMarchid(marchid));
    }

    if marchid >> 63 == 0 {
        return open_core_name(marchid);
    }

    match vendor {
        Vendor::NonCommercial => Err(CoreNameError::MismatchedInput(vendor, marchid)),
        Vendor::Commercial(..) => {
            // once we have known names, make sure to clear the MSB before parsing
            Err(CoreNameError::NoKnownNamesForVendor(vendor))
        }
    }
}

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

    #[test]
    fn int_to_vendor() {
        assert_eq!(Vendor::try_from(0), Ok(Vendor::NonCommercial));
        assert_eq!(
            Vendor::try_from(1),
            Ok(Vendor::Commercial(Manufacturer {
                bank: 1,
                offset: 1,
                name: "AMD",
            }))
        );

        assert_eq!(
            Vendor::try_from(0xFF),
            Err(VendorError::InvalidOffset(0x7F))
        );
        assert_eq!(
            Vendor::try_from(u32::MAX - 2),
            Err(VendorError::UnknownBank(0x2000000))
        );
        assert_eq!(
            Vendor::try_from(u64::MAX - 2),
            Err(VendorError::InvalidBank(0xFFFFFF81))
        );
    }

    #[test]
    fn vendor_to_int() {
        assert_eq!(Vendor::try_from(0).map(u32::from), Ok(0));
        assert_eq!(
            Vendor::try_from((1 << 7) + 1).map(u32::from),
            Ok((1 << 7) + 1)
        );
    }

    #[test]
    fn implementer_display() {
        assert_eq!(Vendor::NonCommercial.to_string(), "Non-Commercial");
        assert_eq!(
            Vendor::try_from(1).map(|v| v.to_string()),
            Ok("AMD".to_string())
        );
    }

    #[test]
    fn core_name_32bit() {
        assert_eq!(
            core_name_rv32(Vendor::NonCommercial, 0),
            Err(CoreNameError::ZeroedMarchid(0)),
        );
        assert_eq!(
            core_name_rv32(Vendor::NonCommercial, 1 << 31),
            Err(CoreNameError::MismatchedInput(
                Vendor::NonCommercial,
                1 << 31
            ))
        );
        assert!(matches!(
            core_name_rv32(Vendor::try_from(1).unwrap(), 1 << 31),
            Err(CoreNameError::NoKnownNamesForVendor(_))
        ));

        assert_eq!(core_name_rv32(Vendor::NonCommercial, 1), Ok("Rocket"));
    }

    #[test]
    fn core_name_64bit() {
        assert_eq!(
            core_name_rv64(Vendor::NonCommercial, 0),
            Err(CoreNameError::ZeroedMarchid(0)),
        );
        assert_eq!(
            core_name_rv64(Vendor::NonCommercial, 1 << 63),
            Err(CoreNameError::MismatchedInput(
                Vendor::NonCommercial,
                1 << 63
            ))
        );
        assert!(matches!(
            core_name_rv64(Vendor::try_from(1).unwrap(), 1 << 63),
            Err(CoreNameError::NoKnownNamesForVendor(_))
        ));

        assert_eq!(core_name_rv64(Vendor::NonCommercial, 1), Ok("Rocket"));
    }
}