# vim: set ts=4 sw=4 sts=4 et:
# Check whether the CPU is known to not perform speculative execution
# Returns: 0 if the CPU is speculation-free, 1 otherwise
is_cpu_specex_free() {
    # source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n882
    # { X86_VENDOR_INTEL,	6, INTEL_FAM6_ATOM_SALTWELL,	X86_FEATURE_ANY },
    # { X86_VENDOR_INTEL,	6, INTEL_FAM6_ATOM_SALTWELL_TABLET,	X86_FEATURE_ANY },
    # { X86_VENDOR_INTEL,	6, INTEL_FAM6_ATOM_BONNELL_MID,	X86_FEATURE_ANY },
    # { X86_VENDOR_INTEL,	6, INTEL_FAM6_ATOM_SALTWELL_MID,	X86_FEATURE_ANY },
    # { X86_VENDOR_INTEL,	6, INTEL_FAM6_ATOM_BONNELL,	X86_FEATURE_ANY },
    # { X86_VENDOR_CENTAUR,   5 },
    # { X86_VENDOR_INTEL,     5 },
    # { X86_VENDOR_NSC,       5 },
    # { X86_VENDOR_ANY,       4 },

    parse_cpu_details
    if is_intel; then
        if [ "$cpu_family" = 6 ]; then
            if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ]; then
                return 0
            fi
        elif [ "$cpu_family" = 5 ]; then
            return 0
        fi
    fi
    # Centaur family 5 and NSC family 5 are also non-speculative
    if [ "$cpu_vendor" = "CentaurHauls" ] && [ "$cpu_family" = 5 ]; then
        return 0
    fi
    if [ "$cpu_vendor" = "Geode by NSC" ] && [ "$cpu_family" = 5 ]; then
        return 0
    fi
    [ "$cpu_family" = 4 ] && return 0
    return 1
}

# Check whether the CPU is known to be unaffected by microarchitectural data sampling (MDS)
# Returns: 0 if MDS-free, 1 if affected or unknown
is_cpu_mds_free() {
    # source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c
    #VULNWL_INTEL(ATOM_GOLDMONT,             NO_MDS | NO_L1TF),
    #VULNWL_INTEL(ATOM_GOLDMONT_X,           NO_MDS | NO_L1TF),
    #VULNWL_INTEL(ATOM_GOLDMONT_PLUS,        NO_MDS | NO_L1TF),

    #/* AMD Family 0xf - 0x12 */
    #VULNWL_AMD(0x0f,        NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS),
    #VULNWL_AMD(0x10,        NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS),
    #VULNWL_AMD(0x11,        NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS),
    #VULNWL_AMD(0x12,        NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS),

    #/* FAMILY_ANY must be last, otherwise 0x0f - 0x12 matches won't work */
    #VULNWL_AMD(X86_FAMILY_ANY,      NO_MELTDOWN | NO_L1TF | NO_MDS),
    #VULNWL_HYGON(X86_FAMILY_ANY,    NO_MELTDOWN | NO_L1TF | NO_MDS),
    parse_cpu_details
    if is_intel; then
        if [ "$cpu_family" = 6 ]; then
            if [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_D" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ]; then
                return 0
            fi
        fi
        [ "$cap_mds_no" = 1 ] && return 0
    fi

    # official statement from AMD says none of their CPUs are affected
    # https://www.amd.com/en/corporate/product-security
    # https://www.amd.com/system/files/documents/security-whitepaper.pdf
    if is_amd; then
        return 0
    elif is_hygon; then
        return 0
    elif [ "$cpu_vendor" = CAVIUM ]; then
        return 0
    elif [ "$cpu_vendor" = PHYTIUM ]; then
        return 0
    elif [ "$cpu_vendor" = ARM ]; then
        return 0
    fi

    return 1
}

# Check whether the CPU is known to be affected by MSBDS only (not MFBDS/MLPDS/MDSUM)
# These CPUs have a different microarchitecture that is only susceptible to
# Microarchitectural Store Buffer Data Sampling, not the other MDS variants.
# Returns: 0 if MSBDS-only, 1 otherwise
is_cpu_msbds_only() {
    # source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c
    #VULNWL_INTEL(ATOM_SILVERMONT,       MSBDS_ONLY),
    #VULNWL_INTEL(ATOM_SILVERMONT_D,     MSBDS_ONLY),
    #VULNWL_INTEL(ATOM_SILVERMONT_MID,   MSBDS_ONLY),
    #VULNWL_INTEL(ATOM_SILVERMONT_MID2,  MSBDS_ONLY),
    #VULNWL_INTEL(ATOM_AIRMONT,          MSBDS_ONLY),
    #VULNWL_INTEL(XEON_PHI_KNL,         MSBDS_ONLY),
    #VULNWL_INTEL(XEON_PHI_KNM,         MSBDS_ONLY),
    parse_cpu_details
    if is_intel; then
        if [ "$cpu_family" = 6 ]; then
            if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID2" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then
                return 0
            fi
        fi
    fi

    return 1
}

# Check whether the CPU is known to be unaffected by TSX Asynchronous Abort (TAA)
# Returns: 0 if TAA-free, 1 if affected or unknown
is_cpu_taa_free() {

    if ! is_intel; then
        return 0
    # is intel
    elif [ "$cap_taa_no" = 1 ] || [ "$cap_rtm" = 0 ]; then
        return 0
    fi

    return 1
}

# Check whether the CPU is known to be unaffected by Special Register Buffer Data Sampling (SRBDS)
# Returns: 0 if SRBDS-free, 1 if affected or unknown
is_cpu_srbds_free() {
    # source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c
    #
    # A processor is affected by SRBDS if its Family_Model and stepping is in the
    # following list, with the exception of the listed processors
    # exporting MDS_NO while Intel TSX is available yet not enabled. The
    # latter class of processors are only affected when Intel TSX is enabled
    # by software using TSX_CTRL_MSR otherwise they are not affected.
    #
    # =============  ============  ========
    # common name    Family_Model  Stepping
    # =============  ============  ========
    # IvyBridge      06_3AH        All              (INTEL_FAM6_IVYBRIDGE)
    #
    # Haswell        06_3CH        All              (INTEL_FAM6_HASWELL)
    # Haswell_L      06_45H        All              (INTEL_FAM6_HASWELL_L)
    # Haswell_G      06_46H        All              (INTEL_FAM6_HASWELL_G)
    #
    # Broadwell_G    06_47H        All              (INTEL_FAM6_BROADWELL_G)
    # Broadwell      06_3DH        All              (INTEL_FAM6_BROADWELL)
    #
    # Skylake_L      06_4EH        All              (INTEL_FAM6_SKYLAKE_L)
    # Skylake        06_5EH        All              (INTEL_FAM6_SKYLAKE)
    #
    # Kabylake_L     06_8EH        <=0xC (MDS_NO)   (INTEL_FAM6_KABYLAKE_L)
    #
    # Kabylake       06_9EH        <=0xD (MDS_NO)   (INTEL_FAM6_KABYLAKE)
    # =============  ============  ========
    parse_cpu_details
    if is_intel; then
        if [ "$cpu_family" = 6 ]; then
            if [ "$cpu_model" = "$INTEL_FAM6_IVYBRIDGE" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_HASWELL" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_HASWELL_L" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_HASWELL_G" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_BROADWELL_G" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_BROADWELL" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE_L" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE" ]; then
                return 1
            elif [ "$cpu_model" = "$INTEL_FAM6_KABYLAKE_L" ] && [ "$cpu_stepping" -le 12 ] ||
                [ "$cpu_model" = "$INTEL_FAM6_KABYLAKE" ] && [ "$cpu_stepping" -le 13 ]; then
                if [ "$cap_mds_no" -eq 1 ] && { [ "$cap_rtm" -eq 0 ] || [ "$cap_tsx_ctrl_rtm_disable" -eq 1 ] || [ "$cap_tsx_force_abort_rtm_disable" -eq 1 ]; }; then
                    return 0
                else
                    return 1
                fi
            fi
        fi
    fi

    return 0

}

# Check whether the CPU is architecturally immune to MMIO Stale Data
# Mirrors the kernel's arch_cap_mmio_immune() helper: ALL THREE ARCH_CAP bits must be set:
#   ARCH_CAP_SBDR_SSDP_NO (bit 13), ARCH_CAP_FBSDP_NO (bit 14), ARCH_CAP_PSDP_NO (bit 15)
# Returns: 0 if immune, 1 otherwise
is_arch_cap_mmio_immune() {
    [ "$cap_sbdr_ssdp_no" = 1 ] && [ "$cap_fbsdp_no" = 1 ] && [ "$cap_psdp_no" = 1 ]
}

# Whether the MMIO arch-cap immunity bits are undetermined because the
# IA32_ARCH_CAPABILITIES MSR couldn't be read (msr module unavailable or kernel
# lockdown).
# Returns: 0 if undetermined, 1 otherwise
is_arch_cap_mmio_undetermined() {
    [ "$cap_sbdr_ssdp_no" = -1 ] || [ "$cap_fbsdp_no" = -1 ] || [ "$cap_psdp_no" = -1 ]
}

# Check whether the CPU is known to be unaffected by MMIO Stale Data (CVE-2022-21123/21125/21166)
# Matches the kernel's NO_MMIO whitelist plus arch_cap_mmio_immune().
# Model inventory and kernel-commit history are documented in check_mmio_linux().
# Returns: 0 if MMIO-free, 1 if affected or unknown
is_cpu_mmio_free() {
    parse_cpu_details
    is_arch_cap_mmio_immune && return 0
    # Non-Intel x86 vendors the kernel unconditionally whitelists (AMD/Hygon all
    # families; Centaur/Zhaoxin fam 7 only).
    if is_amd || is_hygon; then
        return 0
    fi
    if { [ "$cpu_vendor" = "CentaurHauls" ] || [ "$cpu_vendor" = "Shanghai" ]; } && [ "$cpu_family" = 7 ]; then
        return 0
    fi
    # Intel NO_MMIO whitelist
    if is_intel && [ "$cpu_family" = 6 ]; then
        if [ "$cpu_model" = "$INTEL_FAM6_TIGERLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_TIGERLAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ALDERLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ALDERLAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_D" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ]; then
            return 0
        fi
    fi
    return 1
}

# Check whether the CPU's MMIO Stale Data status is unknown ("out of servicing period")
# Matches the kernel's X86_BUG_MMIO_UNKNOWN: Intel CPU not MMIO-free and not in the
# MMIO blacklist. The kernel reports "Unknown: No mitigations" for such CPUs.
# Callers: check_mmio_linux, check_mmio_bsd
# Returns: 0 if unknown, 1 if known (either affected or not affected)
is_cpu_mmio_unknown() {
    parse_cpu_details
    # Only Intel can reach the unknown bucket — other x86 vendors are whitelisted by vendor-id.
    is_intel || return 1
    is_cpu_mmio_free && return 1
    if [ "$cpu_family" = 6 ]; then
        if [ "$cpu_model" = "$INTEL_FAM6_HASWELL_X" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_BROADWELL_D" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_BROADWELL_X" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE_X" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_SKYLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_KABYLAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_KABYLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ICELAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ICELAKE_D" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ICELAKE_X" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_COMETLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_COMETLAKE_L" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_LAKEFIELD" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ROCKETLAKE" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_TREMONT" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_TREMONT_D" ] ||
            [ "$cpu_model" = "$INTEL_FAM6_ATOM_TREMONT_L" ]; then
            return 1
        fi
    fi
    return 0
}

# Check whether the CPU is known to be unaffected by Speculative Store Bypass (SSB)
# Returns: 0 if SSB-free, 1 if affected or unknown
is_cpu_ssb_free() {
    # source1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n945
    # source2: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/tree/arch/x86/kernel/cpu/common.c
    # Only list CPUs that speculate but are immune, to avoid duplication of cpus listed in is_cpu_specex_free()
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_ATOM_SILVERMONT	},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_ATOM_AIRMONT		},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_ATOM_SILVERMONT_X	},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_ATOM_SILVERMONT_MID	},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_CORE_YONAH		},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_XEON_PHI_KNL		},
    #{ X86_VENDOR_INTEL,	6,	INTEL_FAM6_XEON_PHI_KNM		},
    #{ X86_VENDOR_AMD,	0x12,					},
    #{ X86_VENDOR_AMD,	0x11,					},
    #{ X86_VENDOR_AMD,	0x10,					},
    #{ X86_VENDOR_AMD,	0xf,					},
    parse_cpu_details
    if is_intel; then
        if [ "$cpu_family" = 6 ]; then
            if [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_D" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ]; then
                return 0
            elif [ "$cpu_model" = "$INTEL_FAM6_CORE_YONAH" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] ||
                [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then
                return 0
            fi
        fi
    fi
    if is_amd; then
        if [ "$cpu_family" = "18" ] ||
            [ "$cpu_family" = "17" ] ||
            [ "$cpu_family" = "16" ] ||
            [ "$cpu_family" = "15" ]; then
            return 0
        fi
    fi
    if is_hygon; then
        return 1
    fi
    [ "$cpu_family" = 4 ] && return 0
    return 1
}
