Linux/ECC

From Omnia
Jump to navigation Jump to search

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 3

The 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 logic

So 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()

keywords