lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// LCPFS: AArch64 (ARM64) Platform Implementation
// PURPOSE: Hardware entropy and timestamp for ARM64 CPUs
// INSTRUCTIONS: MRS RNDR/RNDRRS, CNTVCT_EL0, SVC

use super::ArchError;

/// Fill buffer with hardware entropy using RNDR instruction (ARMv8.5-RNG).
///
/// # Arguments
/// * `buf` - Buffer to fill with random bytes
///
/// # Returns
/// * `Ok(())` - Buffer filled successfully
/// * `Err(ArchError::HardwareUnavailable)` - RNDR not supported or failed
///
/// # Note
/// RNDR is only available on ARMv8.5+ CPUs with RNG extension.
/// Falls back to error if unavailable.
pub fn fill_hardware_entropy(buf: &mut [u8]) -> Result<(), ArchError> {
    for chunk in buf.chunks_mut(8) {
        let mut value: u64 = 0;
        let mut success: bool = false;

        // Retry up to 10 times
        for _ in 0..10 {
            // SAFETY INVARIANTS:
            // 1. MRS RNDR reads from RNDR system register (EL0 accessible)
            // 2. NZCV flags indicate success (Z=0 means success)
            // 3. nomem: instruction does not access memory
            // 4. nostack: instruction does not touch stack
            // 5. Instruction may fail if entropy unavailable
            //
            // VERIFICATION: RNDR available on ARMv8.5+ with FEAT_RNG
            //
            // JUSTIFICATION:
            // Hardware RNG provides high-quality entropy for cryptographic seeding.
            unsafe {
                let nzcv: u64;
                core::arch::asm!(
                    "mrs {0}, s3_3_c2_c4_0",  // RNDR register
                    "mrs {1}, nzcv",
                    out(reg) value,
                    out(reg) nzcv,
                    options(nomem, nostack),
                );
                // Z flag (bit 30) = 0 means success
                success = (nzcv & (1 << 30)) == 0;
            }

            if success {
                let bytes = value.to_le_bytes();
                let len = chunk.len().min(8);
                chunk[..len].copy_from_slice(&bytes[..len]);
                break;
            }

            // Small pause
            core::hint::spin_loop();
        }

        if !success {
            return Err(ArchError::HardwareUnavailable);
        }
    }

    Ok(())
}

/// Fill buffer with reseeding entropy using RNDRRS instruction.
///
/// RNDRRS reseeds the RNG before returning, providing higher-quality
/// entropy at the cost of potential delays.
pub fn fill_seed_entropy(buf: &mut [u8]) -> Result<(), ArchError> {
    for chunk in buf.chunks_mut(8) {
        let mut value: u64 = 0;
        let mut success: bool = false;

        // More retries - RNDRRS can take longer
        for _ in 0..100 {
            // SAFETY: Same as RNDR but uses RNDRRS register
            unsafe {
                let nzcv: u64;
                core::arch::asm!(
                    "mrs {0}, s3_3_c2_c4_1",  // RNDRRS register
                    "mrs {1}, nzcv",
                    out(reg) value,
                    out(reg) nzcv,
                    options(nomem, nostack),
                );
                success = (nzcv & (1 << 30)) == 0;
            }

            if success {
                let bytes = value.to_le_bytes();
                let len = chunk.len().min(8);
                chunk[..len].copy_from_slice(&bytes[..len]);
                break;
            }

            core::hint::spin_loop();
        }

        if !success {
            return Err(ArchError::HardwareUnavailable);
        }
    }

    Ok(())
}

/// Get virtual counter timestamp (CNTVCT_EL0).
///
/// Returns a monotonically increasing counter from the ARM generic timer.
/// Typically increments at a fixed frequency (often 1-100 MHz).
#[inline]
pub fn get_timestamp() -> u64 {
    let value: u64;

    // SAFETY INVARIANTS:
    // 1. CNTVCT_EL0 is EL0-accessible (user mode allowed)
    // 2. Returns 64-bit virtual counter value
    // 3. nomem: instruction does not access memory
    // 4. nostack: instruction does not touch stack
    //
    // VERIFICATION: CNTVCT_EL0 available on all ARMv8+ CPUs
    //
    // JUSTIFICATION:
    // High-resolution timestamp for entropy mixing and timing.
    unsafe {
        core::arch::asm!(
            "mrs {0}, cntvct_el0",
            out(reg) value,
            options(nomem, nostack, preserves_flags),
        );
    }

    value
}

/// Perform a Linux syscall with 3 arguments.
///
/// # Safety
/// Caller must ensure:
/// - `num` is a valid syscall number
/// - Arguments are valid for the specific syscall
/// - This is only called on Linux
#[cfg(target_os = "linux")]
pub unsafe fn syscall3(num: i64, arg1: usize, arg2: usize, arg3: usize) -> i64 {
    let ret: i64;

    // SAFETY INVARIANTS:
    // 1. Linux AArch64 syscall ABI: num->x8, arg1->x0, arg2->x1, arg3->x2
    // 2. SVC #0 triggers syscall
    // 3. Return value in x0
    // 4. x0-x7 are caller-saved
    //
    // VERIFICATION: Linux AArch64 syscall ABI documented in syscall(2)
    //
    // JUSTIFICATION:
    // Direct syscall required for getrandom(2) in no_std environment.
    core::arch::asm!(
        "svc #0",
        in("x8") num,
        inout("x0") arg1 => ret,
        in("x1") arg2,
        in("x2") arg3,
        options(nostack),
    );

    ret as i64
}

/// Check if RNDR instruction is available.
///
/// Checks ID_AA64ISAR0_EL1 for RNG feature.
#[inline]
pub fn has_rdrand() -> bool {
    // On AArch64, we check for RNDR availability
    // ID_AA64ISAR0_EL1 bits [63:60] = RNDR field
    // However, this register is only readable from EL1+
    // In userspace, we try the instruction and catch failure
    let mut test_val: u64 = 0;
    let nzcv: u64;

    // SAFETY: Try RNDR once to check availability
    unsafe {
        core::arch::asm!(
            "mrs {0}, s3_3_c2_c4_0",
            "mrs {1}, nzcv",
            out(reg) test_val,
            out(reg) nzcv,
            options(nomem, nostack),
        );
    }

    // If we got here without SIGILL, instruction exists
    // Z=0 means success, Z=1 means temporarily unavailable (but instruction exists)
    let _ = test_val; // Silence unused warning
    true // If we reach here, instruction didn't trap
}

/// Check if RNDRRS instruction is available.
///
/// Same availability as RNDR (FEAT_RNG).
#[inline]
pub fn has_rdseed() -> bool {
    has_rdrand() // Same feature flag
}

/// Check if AES instructions are available (FEAT_AES).
///
/// Checks for ARMv8 Cryptographic Extension AES support.
#[inline]
pub fn has_aesni() -> bool {
    // On AArch64, AES is part of the Cryptographic Extension
    // Most ARMv8 CPUs support this, but we check the feature
    // ID_AA64ISAR0_EL1 bits [7:4] = AES field
    // 0b0001 = AESE/AESD/AESMC/AESIMC
    // 0b0010 = PMULL/PMULL2 (also available)
    //
    // In practice, if we're running on ARMv8, AES is almost always available
    // We use a compile-time check here for simplicity
    cfg!(target_feature = "aes")
}

/// Check if PMULL instructions are available (for GCM GHASH).
///
/// PMULL provides polynomial multiplication for efficient GCM.
#[inline]
pub fn has_pclmulqdq() -> bool {
    // PMULL is the ARM equivalent of Intel's PCLMULQDQ
    cfg!(target_feature = "aes") // PMULL is part of the crypto extension
}

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

    #[test]
    fn test_timestamp() {
        let t1 = get_timestamp();
        let t2 = get_timestamp();
        assert!(t2 >= t1); // Monotonic
    }

    #[test]
    fn test_entropy_if_available() {
        // Only test if RNDR is available
        let mut buf = [0u8; 32];
        if fill_hardware_entropy(&mut buf).is_ok() {
            assert_ne!(buf, [0u8; 32]);
        }
    }
}