1042 lines · 24.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * FRU (Field-Replaceable Unit) Memory Poison Manager * * Copyright (c) 2024, Advanced Micro Devices, Inc. * All Rights Reserved. * * Authors: *	Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com> *	Muralidhara M K <muralidhara.mk@amd.com> *	Yazen Ghannam <Yazen.Ghannam@amd.com> * * Implementation notes, assumptions, and limitations: * * - FRU memory poison section and memory poison descriptor definitions are not yet *   included in the UEFI specification. So they are defined here. Afterwards, they *   may be moved to linux/cper.h, if appropriate. * * - Platforms based on AMD MI300 systems will be the first to use these structures. *   There are a number of assumptions made here that will need to be generalized *   to support other platforms. * *   AMD MI300-based platform(s) assumptions: *   - Memory errors are reported through x86 MCA. *   - The entire DRAM row containing a memory error should be retired. *   - There will be (1) FRU memory poison section per CPER. *   - The FRU will be the CPU package (processor socket). *   - The default number of memory poison descriptor entries should be (8). *   - The platform will use ACPI ERST for persistent storage. *   - All FRU records should be saved to persistent storage. Module init will *     fail if any FRU record is not successfully written. * * - Boot time memory retirement may occur later than ideal due to dependencies *   on other libraries and drivers. This leaves a gap where bad memory may be *   accessed during early boot stages. * * - Enough memory should be pre-allocated for each FRU record to be able to hold *   the expected number of descriptor entries. This, mostly empty, record is *   written to storage during init time. Subsequent writes to the same record *   should allow the Platform to update the stored record in-place. Otherwise, *   if the record is extended, then the Platform may need to perform costly memory *   management operations on the storage. For example, the Platform may spend time *   in Firmware copying and invalidating memory on a relatively slow SPI ROM. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/cper.h>#include <linux/ras.h>#include <linux/cpu.h> #include <acpi/apei.h> #include <asm/cpu_device_id.h>#include <asm/cpuid/api.h>#include <asm/mce.h> #include "../debugfs.h" #include "atl/internal.h" #define INVALID_CPU			UINT_MAX /* Validation Bits */#define FMP_VALID_ARCH_TYPE		BIT_ULL(0)#define FMP_VALID_ARCH			BIT_ULL(1)#define FMP_VALID_ID_TYPE		BIT_ULL(2)#define FMP_VALID_ID			BIT_ULL(3)#define FMP_VALID_LIST_ENTRIES		BIT_ULL(4)#define FMP_VALID_LIST			BIT_ULL(5) /* FRU Architecture Types */#define FMP_ARCH_TYPE_X86_CPUID_1_EAX	0 /* FRU ID Types */#define FMP_ID_TYPE_X86_PPIN		0 /* FRU Memory Poison Section */struct cper_sec_fru_mem_poison {	u32 checksum;	u64 validation_bits;	u32 fru_arch_type;	u64 fru_arch;	u32 fru_id_type;	u64 fru_id;	u32 nr_entries;} __packed; /* FRU Descriptor ID Types */#define FPD_HW_ID_TYPE_MCA_IPID		0 /* FRU Descriptor Address Types */#define FPD_ADDR_TYPE_MCA_ADDR		0 /* Memory Poison Descriptor */struct cper_fru_poison_desc {	u64 timestamp;	u32 hw_id_type;	u64 hw_id;	u32 addr_type;	u64 addr;} __packed; /* Collection of headers and sections for easy pointer use. */struct fru_rec {	struct cper_record_header	hdr;	struct cper_section_descriptor	sec_desc;	struct cper_sec_fru_mem_poison	fmp;	struct cper_fru_poison_desc	entries[];} __packed; /* * Pointers to the complete CPER record of each FRU. * * Memory allocation will include padded space for descriptor entries. */static struct fru_rec **fru_records; /* system physical addresses array */static u64 *spa_entries; static struct dentry *fmpm_dfs_dir;static struct dentry *fmpm_dfs_entries; #define CPER_CREATOR_FMP						\	GUID_INIT(0xcd5c2993, 0xf4b2, 0x41b2, 0xb5, 0xd4, 0xf9, 0xc3,	\		  0xa0, 0x33, 0x08, 0x75) #define CPER_SECTION_TYPE_FMP						\	GUID_INIT(0x5e4706c1, 0x5356, 0x48c6, 0x93, 0x0b, 0x52, 0xf2,	\		  0x12, 0x0a, 0x44, 0x58) /** * DOC: max_nr_entries (byte) * Maximum number of descriptor entries possible for each FRU. * * Values between '1' and '255' are valid. * No input or '0' will default to FMPM_DEFAULT_MAX_NR_ENTRIES. */static u8 max_nr_entries;module_param(max_nr_entries, byte, 0644);MODULE_PARM_DESC(max_nr_entries,		 "Maximum number of memory poison descriptor entries per FRU"); #define FMPM_DEFAULT_MAX_NR_ENTRIES	8 /* Maximum number of FRUs in the system. */#define FMPM_MAX_NR_FRU			256static unsigned int max_nr_fru; /* Total length of record including headers and list of descriptor entries. */static size_t max_rec_len; #define FMPM_MAX_REC_LEN (sizeof(struct fru_rec) + (sizeof(struct cper_fru_poison_desc) * 255)) /* Total number of SPA entries across all FRUs. */static unsigned int spa_nr_entries; /* * Protect the local records cache in fru_records and prevent concurrent * writes to storage. This is only needed after init once notifier block * registration is done. * * The majority of a record is fixed at module init and will not change * during run time. The entries within a record will be updated as new * errors are reported. The mutex should be held whenever the entries are * accessed during run time. */static DEFINE_MUTEX(fmpm_update_mutex); #define for_each_fru(i, rec) \	for (i = 0; rec = fru_records[i], i < max_nr_fru; i++) static inline u32 get_fmp_len(struct fru_rec *rec){	return rec->sec_desc.section_length - sizeof(struct cper_section_descriptor);} static struct fru_rec *get_fru_record(u64 fru_id){	struct fru_rec *rec;	unsigned int i; 	for_each_fru(i, rec) {		if (rec->fmp.fru_id == fru_id)			return rec;	} 	pr_debug("Record not found for FRU 0x%016llx\n", fru_id); 	return NULL;} /* * Sum up all bytes within the FRU Memory Poison Section including the Memory * Poison Descriptor entries. * * Don't include the old checksum here. It's a u32 value, so summing each of its * bytes will give the wrong total. */static u32 do_fmp_checksum(struct cper_sec_fru_mem_poison *fmp, u32 len){	u32 checksum = 0;	u8 *buf, *end; 	/* Skip old checksum. */	buf = (u8 *)fmp + sizeof(u32);	end = buf + len; 	while (buf < end)		checksum += (u8)(*(buf++)); 	return checksum;} static int update_record_on_storage(struct fru_rec *rec){	u32 len, checksum;	int ret; 	/* Calculate a new checksum. */	len = get_fmp_len(rec); 	/* Get the current total. */	checksum = do_fmp_checksum(&rec->fmp, len); 	/* Use the complement value. */	rec->fmp.checksum = -checksum; 	pr_debug("Writing to storage\n"); 	ret = erst_write(&rec->hdr);	if (ret) {		pr_warn("Storage update failed for FRU 0x%016llx\n", rec->fmp.fru_id); 		if (ret == -ENOSPC)			pr_warn("Not enough space on storage\n");	} 	return ret;} static bool rec_has_valid_entries(struct fru_rec *rec){	if (!(rec->fmp.validation_bits & FMP_VALID_LIST_ENTRIES))		return false; 	if (!(rec->fmp.validation_bits & FMP_VALID_LIST))		return false; 	return true;} /* * Row retirement is done on MI300 systems, and some bits are 'don't * care' for comparing addresses with unique physical rows.  This * includes all column bits and the row[13] bit. */#define MASK_ADDR(addr)	((addr) & ~(MI300_UMC_MCA_ROW13 | MI300_UMC_MCA_COL)) static bool fpds_equal(struct cper_fru_poison_desc *old, struct cper_fru_poison_desc *new){	/*	 * Ignore timestamp field.	 * The same physical error may be reported multiple times due to stuck bits, etc.	 *	 * Also, order the checks from most->least likely to fail to shortcut the code.	 */	if (MASK_ADDR(old->addr) != MASK_ADDR(new->addr))		return false; 	if (old->hw_id != new->hw_id)		return false; 	if (old->addr_type != new->addr_type)		return false; 	if (old->hw_id_type != new->hw_id_type)		return false; 	return true;} static bool rec_has_fpd(struct fru_rec *rec, struct cper_fru_poison_desc *fpd){	unsigned int i; 	for (i = 0; i < rec->fmp.nr_entries; i++) {		struct cper_fru_poison_desc *fpd_i = &rec->entries[i]; 		if (fpds_equal(fpd_i, fpd)) {			pr_debug("Found duplicate record\n");			return true;		}	} 	return false;} static void save_spa(struct fru_rec *rec, unsigned int entry,		     u64 addr, u64 id, unsigned int cpu){	unsigned int i, fru_idx, spa_entry;	struct atl_err a_err;	unsigned long spa; 	if (entry >= max_nr_entries) {		pr_warn_once("FRU descriptor entry %d out-of-bounds (max: %d)\n",			     entry, max_nr_entries);		return;	} 	/* spa_nr_entries is always multiple of max_nr_entries */	for (i = 0; i < spa_nr_entries; i += max_nr_entries) {		fru_idx = i / max_nr_entries;		if (fru_records[fru_idx] == rec)			break;	} 	if (i >= spa_nr_entries) {		pr_warn_once("FRU record %d not found\n", i);		return;	} 	spa_entry = i + entry;	if (spa_entry >= spa_nr_entries) {		pr_warn_once("spa_entries[] index out-of-bounds\n");		return;	} 	memset(&a_err, 0, sizeof(struct atl_err)); 	a_err.addr = addr;	a_err.ipid = id;	a_err.cpu  = cpu; 	spa = amd_convert_umc_mca_addr_to_sys_addr(&a_err);	if (IS_ERR_VALUE(spa)) {		pr_debug("Failed to get system address\n");		return;	} 	spa_entries[spa_entry] = spa;	pr_debug("fru_idx: %u, entry: %u, spa_entry: %u, spa: 0x%016llx\n",		 fru_idx, entry, spa_entry, spa_entries[spa_entry]);} static void update_fru_record(struct fru_rec *rec, struct mce *m){	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;	struct cper_fru_poison_desc fpd, *fpd_dest;	u32 entry = 0; 	mutex_lock(&fmpm_update_mutex); 	memset(&fpd, 0, sizeof(struct cper_fru_poison_desc)); 	fpd.timestamp	= m->time;	fpd.hw_id_type = FPD_HW_ID_TYPE_MCA_IPID;	fpd.hw_id	= m->ipid;	fpd.addr_type	= FPD_ADDR_TYPE_MCA_ADDR;	fpd.addr	= m->addr; 	/* This is the first entry, so just save it. */	if (!rec_has_valid_entries(rec))		goto save_fpd; 	/* Ignore already recorded errors. */	if (rec_has_fpd(rec, &fpd))		goto out_unlock; 	if (rec->fmp.nr_entries >= max_nr_entries) {		pr_warn("Exceeded number of entries for FRU 0x%016llx\n", rec->fmp.fru_id);		goto out_unlock;	} 	entry  = fmp->nr_entries; save_fpd:	save_spa(rec, entry, m->addr, m->ipid, m->extcpu);	fpd_dest  = &rec->entries[entry];	memcpy(fpd_dest, &fpd, sizeof(struct cper_fru_poison_desc)); 	fmp->nr_entries		 = entry + 1;	fmp->validation_bits	|= FMP_VALID_LIST_ENTRIES;	fmp->validation_bits	|= FMP_VALID_LIST; 	pr_debug("Updated FRU 0x%016llx entry #%u\n", fmp->fru_id, entry); 	update_record_on_storage(rec); out_unlock:	mutex_unlock(&fmpm_update_mutex);} static void retire_dram_row(u64 addr, u64 id, u32 cpu){	struct atl_err a_err; 	memset(&a_err, 0, sizeof(struct atl_err)); 	a_err.addr = addr;	a_err.ipid = id;	a_err.cpu  = cpu; 	amd_retire_dram_row(&a_err);} static int fru_handle_mem_poison(struct notifier_block *nb, unsigned long val, void *data){	struct mce *m = (struct mce *)data;	struct fru_rec *rec; 	if (!mce_is_memory_error(m))		return NOTIFY_DONE; 	retire_dram_row(m->addr, m->ipid, m->extcpu); 	/*	 * An invalid FRU ID should not happen on real errors. But it	 * could happen from software error injection, etc.	 */	rec = get_fru_record(m->ppin);	if (!rec)		return NOTIFY_DONE; 	update_fru_record(rec, m); 	return NOTIFY_OK;} static struct notifier_block fru_mem_poison_nb = {	.notifier_call  = fru_handle_mem_poison,	.priority	= MCE_PRIO_LOWEST,}; static void retire_mem_fmp(struct fru_rec *rec){	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;	unsigned int i, cpu; 	for (i = 0; i < fmp->nr_entries; i++) {		struct cper_fru_poison_desc *fpd = &rec->entries[i];		unsigned int err_cpu = INVALID_CPU; 		if (fpd->hw_id_type != FPD_HW_ID_TYPE_MCA_IPID)			continue; 		if (fpd->addr_type != FPD_ADDR_TYPE_MCA_ADDR)			continue; 		cpus_read_lock();		for_each_online_cpu(cpu) {			if (topology_ppin(cpu) == fmp->fru_id) {				err_cpu = cpu;				break;			}		}		cpus_read_unlock(); 		if (err_cpu == INVALID_CPU)			continue; 		retire_dram_row(fpd->addr, fpd->hw_id, err_cpu);		save_spa(rec, i, fpd->addr, fpd->hw_id, err_cpu);	}} static void retire_mem_records(void){	struct fru_rec *rec;	unsigned int i; 	for_each_fru(i, rec) {		if (!rec_has_valid_entries(rec))			continue; 		retire_mem_fmp(rec);	}} /* Set the CPER Record Header and CPER Section Descriptor fields. */static void set_rec_fields(struct fru_rec *rec){	struct cper_section_descriptor	*sec_desc = &rec->sec_desc;	struct cper_record_header	*hdr	  = &rec->hdr; 	/*	 * This is a saved record created with fewer max_nr_entries.	 * Update the record lengths and keep everything else as-is.	 */	if (hdr->record_length && hdr->record_length < max_rec_len) {		pr_debug("Growing record 0x%016llx from %u to %zu bytes\n",			 hdr->record_id, hdr->record_length, max_rec_len);		goto update_lengths;	} 	memcpy(hdr->signature, CPER_SIG_RECORD, CPER_SIG_SIZE);	hdr->revision			= CPER_RECORD_REV;	hdr->signature_end		= CPER_SIG_END;

Lines 1–500 of 1042.