Linux/ECC: Difference between revisions
| Line 9: | Line 9: | ||
Check if ECC is supported: <ref>https://www.cyberciti.biz/faq/ecc-memory-modules/</ref> | Check if ECC is supported: <ref>https://www.cyberciti.biz/faq/ecc-memory-modules/</ref> | ||
# rough checks for ecc | |||
sudo dmidecode | grep -i ecc | |||
sudo lshw | grep -i ecc | |||
sudo dmesg | grep -i ecc | |||
# specific memory checks: | |||
sudo dmidecode -t memory | grep -i ecc | sudo dmidecode -t memory | grep -i ecc | ||
lshw -C memory | grep -i ecc | sudo lshw -C memory | grep -i ecc | ||
sudo dmesg | grep -i EDAC | sudo dmesg | grep -i EDAC | ||
Revision as of 22:06, 28 September 2026
Error-Correcting Code Random Access Memory
ECC RAM (Error-Correcting Code Random Access Memory) is a specialized type of computer memory that can detect and automatically correct single-bit data corruption in real time.
ECC needs to BOTH be Supported and Enabled
You can have ECC RAM installed, but not enabled. So you need to both check to see if the system supports it, and if it is enabled in both BIOS and OS.
Check if ECC is supported: [1]
# rough checks for ecc sudo dmidecode | grep -i ecc sudo lshw | grep -i ecc sudo dmesg | grep -i ecc
# specific memory checks: sudo dmidecode -t memory | grep -i ecc sudo lshw -C memory | grep -i ecc sudo dmesg | grep -i EDAC
Check if ECC is enabled in the OS: [2] [3]
For dmidecode, you must look for the “Error Correction Type” field in the output. For example, If ECC RAM is installed, you should see '“Error Correction Type: ECC” or “Error Correction Type: Single-bit ECC” or “Error Correction Type: Multi-bit ECC” for the corresponding memory module(s) installed in the system.
for dmesg edac, look for "ecc RAM"
Python Check Script
Well, I was going to wait until tomorrow to do this, but when I get an itch, I have to scratch it. :-D I successfully ported the ecc_check C program to python, and it works perfectly. Here's what it gives me on my FreeNAS system:
5004-5007h: 20 0 62 3 5008-500Bh: 20 0 62 3The last byte can be decoded as such (according to this thread)
According to Intel's datasheet: 0: ECC disabled 1: ECC is active in I/O; ECC logic is not active In this case. 2: ECC is disabled in I/O, but ECC logic is enabled. 3: ECC active in both I/O and ECC logicSo it seems I am running with ECC.
You can grab the python code here: http://pastebin.com/sxd7QcYj
If somebody doesn't beat me to it, I'll make another thread dedicated to the python version of this code tomorrow, so it's not buried here.
Now off to bed for me.
Thanks, Steve
check_ecc.py
My Python3 modified version that also supports AMD:
check_ecc.py ::
$ cat check_ecc.py
#!/usr/bin/env python3
"""
This code is based on a discussion here:
http://hardforum.com/showthread.php?t=1693051
and code pasted here:
http://pastebin.com/URExZi29
AMD register definitions follow the Linux kernel's amd64_edac driver
(drivers/edac/amd64_edac.[ch]) and arch/x86/kernel/amd_node.c.
"""
import argparse
import mmap
import os
import re
import struct
MEM_START = 0xFED10000
FILESIZE = 32*1024
# ECC mode byte (5007h/500Bh) of MAD_DIMM_CH0/CH1, per Intel's datasheet
ECC_MODES = {
0: "ECC disabled",
1: "ECC is active in I/O; ECC logic is not active",
2: "ECC is disabled in I/O, but ECC logic is enabled",
3: "ECC active in both I/O and ECC logic",
}
PCI_DEVICES = "/sys/bus/pci/devices"
AMD_VENDOR_ID = 0x1022
# Zen: UMC (memory channel) registers sit behind the root complex's SMN index/data pair
SMN_INDEX = 0x60
SMN_DATA = 0x64
UMC_BASE = 0x50000
UMC_SDP_CTRL = 0x104
UMC_CAP_HI = 0xDF4
UMC_SDP_INIT = 1 << 31
UMC_ECC_ENABLED = 1 << 30
# Pre-Zen: D18F3x44 NB Configuration, bit 22 DramEccEn
NB_CFG = 0x44
NB_CFG_ECC_ENABLE = 1 << 22
def decode_ecc(value):
return ECC_MODES.get(value, "Unknown ECC mode %#x (expected 0-3)" % value)
def decode_umc(sdp_ctrl, cap_hi):
if 0xFFFFFFFF in (sdp_ctrl, cap_hi):
return "Unknown (register read returned all ones)"
if not sdp_ctrl & UMC_SDP_INIT:
return "Not in use (no DIMMs)"
return "ECC enabled" if cap_hi & UMC_ECC_ENABLED else "ECC disabled"
def decode_nb_cfg(nb_cfg):
if nb_cfg == 0xFFFFFFFF:
return "Unknown (register read returned all ones)"
return "ECC enabled" if nb_cfg & NB_CFG_ECC_ENABLE else "ECC disabled"
def read_cpu():
info = {}
with open("/proc/cpuinfo") as f:
for line in f:
if not line.strip():
break
key, _, value = line.partition(":")
info[key.strip()] = value.strip()
return info["vendor_id"], int(info["cpu family"]), int(info["model"])
def kernel_version():
return tuple(int(n) for n in re.findall(r"\d+", os.uname().release)[:2])
def read_dword(fd, offset):
return struct.unpack("<I", os.pread(fd, 4, offset))[0]
def pci_config(bdf):
return os.path.join(PCI_DEVICES, "0000:" + bdf, "config")
def is_amd_host_bridge(fd):
return read_dword(fd, 0x00) & 0xFFFF == AMD_VENDOR_ID and read_dword(fd, 0x08) >> 16 == 0x0600
def amd_nodes(function):
nodes = []
for slot in range(0x18, 0x20):
path = pci_config("00:%02x.%d" % (slot, function))
if os.path.exists(path):
with open(path, "rb") as f:
if is_amd_host_bridge(f.fileno()):
nodes.append((slot - 0x18, path))
if not nodes:
print("No AMD node devices at PCI 00:18-00:1f (virtual machine?)")
return nodes
def umc_count(family, model):
# UMCs per node, from amd64_edac's per_family_init()
if family == 0x17 and 0x30 <= model <= 0x3F:
return 8
if family == 0x19:
if model <= 0x0F or 0x30 <= model <= 0x3F:
return 8
if 0x10 <= model <= 0x1F or 0xA0 <= model <= 0xAF:
return 12
if 0x40 <= model <= 0x4F or 0x70 <= model <= 0x7F:
return 4
if family == 0x1A:
if model <= 0x1F:
return 12
if 0x50 <= model <= 0x57 or 0xC0 <= model <= 0xC7:
return 16
if 0x90 <= model <= 0xAF:
return 8
return 2
def smn_root(df_f0):
with open(df_f0, "rb") as f:
# DF::CfgAddressCntl holds the root bus; it moved to 0xC04 in Data Fabric v4
offset = 0xC04 if read_dword(f.fileno(), 0x40) & 0x0F000000 else 0x84
bus = read_dword(f.fileno(), offset) & 0xFF
return pci_config("%02x:00.0" % bus)
def smn_read(fd, address):
for _ in range(10):
os.pwrite(fd, struct.pack("<I", address), SMN_INDEX)
value = read_dword(fd, SMN_DATA)
# Kernel SMN users don't lock against user space; retry if one moved the index
if read_dword(fd, SMN_INDEX) == address:
return value
raise RuntimeError("SMN index %#x kept changing under concurrent access" % address)
def check_intel():
f = open("/dev/mem", "r+b")
mem = mmap.mmap(f.fileno(), FILESIZE, prot=mmap.PROT_READ, offset=MEM_START)
print("5004-5007h:", end=" ")
for i in range(0x5004, 0x5004+4):
print("%x" % mem[i], end=" ")
print()
print(" Channel 0: %s" % decode_ecc(mem[0x5007]))
print("5008-500Bh:", end=" ")
for i in range(0x5008, 0x5008+4):
print("%x" % mem[i], end=" ")
print()
print(" Channel 1: %s" % decode_ecc(mem[0x500B]))
mem.close()
def check_amd_zen(family, model, allow_taint):
if kernel_version() >= (6, 15) and not allow_taint:
print("Linux %s reserves AMD SMN access; reading it from user space taints the kernel." % os.uname().release)
print("Re-run with --allow-taint to read the memory controller anyway.")
return
for node, df_f0 in amd_nodes(0):
root = smn_root(df_f0)
with open(root, "r+b", buffering=0) as f:
if not is_amd_host_bridge(f.fileno()):
print("Node %d: %s is not an AMD root complex; skipped" % (node, root))
continue
for umc in range(umc_count(family, model)):
base = UMC_BASE + (umc << 20)
sdp_ctrl = smn_read(f.fileno(), base + UMC_SDP_CTRL)
cap_hi = smn_read(f.fileno(), base + UMC_CAP_HI)
print("Node %d UMC%d: SdpCtrl=%08x UmcCapHi=%08x" % (node, umc, sdp_ctrl, cap_hi))
print(" Channel %d: %s" % (umc, decode_umc(sdp_ctrl, cap_hi)))
def check_amd_legacy():
for node, f3 in amd_nodes(3):
with open(f3, "rb") as f:
nb_cfg = read_dword(f.fileno(), NB_CFG)
print("D%XF3x44: %08x" % (0x18 + node, nb_cfg))
print(" Node %d: %s" % (node, decode_nb_cfg(nb_cfg)))
def main():
parser = argparse.ArgumentParser(description="Report whether DRAM ECC is enabled.")
parser.add_argument("--allow-taint", action="store_true",
help="AMD Zen on Linux 6.15+: read the memory controller even though it taints the kernel")
args = parser.parse_args()
# Script must be run as root
if os.geteuid() != 0:
print("You must be root or root-like to run this script.")
return
vendor, family, model = read_cpu()
if vendor == "GenuineIntel":
check_intel()
elif vendor == "AuthenticAMD" and family in (0x17, 0x19, 0x1A):
check_amd_zen(family, model, args.allow_taint)
elif vendor == "AuthenticAMD" and family in (0x0F, 0x10, 0x15, 0x16):
check_amd_legacy()
else:
print("Unsupported CPU: %s family %#x model %#x" % (vendor, family, model))
if __name__ == "__main__":
main()