brintos

brintos / linux-shallow public Read only

0
0
Text · 23.9 KiB · 90de737 Raw
1035 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * FRU (Field-Replaceable Unit) Memory Poison Manager4 *5 * Copyright (c) 2024, Advanced Micro Devices, Inc.6 * All Rights Reserved.7 *8 * Authors:9 *	Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>10 *	Muralidhara M K <muralidhara.mk@amd.com>11 *	Yazen Ghannam <Yazen.Ghannam@amd.com>12 *13 * Implementation notes, assumptions, and limitations:14 *15 * - FRU memory poison section and memory poison descriptor definitions are not yet16 *   included in the UEFI specification. So they are defined here. Afterwards, they17 *   may be moved to linux/cper.h, if appropriate.18 *19 * - Platforms based on AMD MI300 systems will be the first to use these structures.20 *   There are a number of assumptions made here that will need to be generalized21 *   to support other platforms.22 *23 *   AMD MI300-based platform(s) assumptions:24 *   - Memory errors are reported through x86 MCA.25 *   - The entire DRAM row containing a memory error should be retired.26 *   - There will be (1) FRU memory poison section per CPER.27 *   - The FRU will be the CPU package (processor socket).28 *   - The default number of memory poison descriptor entries should be (8).29 *   - The platform will use ACPI ERST for persistent storage.30 *   - All FRU records should be saved to persistent storage. Module init will31 *     fail if any FRU record is not successfully written.32 *33 * - Boot time memory retirement may occur later than ideal due to dependencies34 *   on other libraries and drivers. This leaves a gap where bad memory may be35 *   accessed during early boot stages.36 *37 * - Enough memory should be pre-allocated for each FRU record to be able to hold38 *   the expected number of descriptor entries. This, mostly empty, record is39 *   written to storage during init time. Subsequent writes to the same record40 *   should allow the Platform to update the stored record in-place. Otherwise,41 *   if the record is extended, then the Platform may need to perform costly memory42 *   management operations on the storage. For example, the Platform may spend time43 *   in Firmware copying and invalidating memory on a relatively slow SPI ROM.44 */45 46#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt47 48#include <linux/cper.h>49#include <linux/ras.h>50#include <linux/cpu.h>51 52#include <acpi/apei.h>53 54#include <asm/cpu_device_id.h>55#include <asm/mce.h>56 57#include "../debugfs.h"58 59#include "atl/internal.h"60 61#define INVALID_CPU			UINT_MAX62 63/* Validation Bits */64#define FMP_VALID_ARCH_TYPE		BIT_ULL(0)65#define FMP_VALID_ARCH			BIT_ULL(1)66#define FMP_VALID_ID_TYPE		BIT_ULL(2)67#define FMP_VALID_ID			BIT_ULL(3)68#define FMP_VALID_LIST_ENTRIES		BIT_ULL(4)69#define FMP_VALID_LIST			BIT_ULL(5)70 71/* FRU Architecture Types */72#define FMP_ARCH_TYPE_X86_CPUID_1_EAX	073 74/* FRU ID Types */75#define FMP_ID_TYPE_X86_PPIN		076 77/* FRU Memory Poison Section */78struct cper_sec_fru_mem_poison {79	u32 checksum;80	u64 validation_bits;81	u32 fru_arch_type;82	u64 fru_arch;83	u32 fru_id_type;84	u64 fru_id;85	u32 nr_entries;86} __packed;87 88/* FRU Descriptor ID Types */89#define FPD_HW_ID_TYPE_MCA_IPID		090 91/* FRU Descriptor Address Types */92#define FPD_ADDR_TYPE_MCA_ADDR		093 94/* Memory Poison Descriptor */95struct cper_fru_poison_desc {96	u64 timestamp;97	u32 hw_id_type;98	u64 hw_id;99	u32 addr_type;100	u64 addr;101} __packed;102 103/* Collection of headers and sections for easy pointer use. */104struct fru_rec {105	struct cper_record_header	hdr;106	struct cper_section_descriptor	sec_desc;107	struct cper_sec_fru_mem_poison	fmp;108	struct cper_fru_poison_desc	entries[];109} __packed;110 111/*112 * Pointers to the complete CPER record of each FRU.113 *114 * Memory allocation will include padded space for descriptor entries.115 */116static struct fru_rec **fru_records;117 118/* system physical addresses array */119static u64 *spa_entries;120 121static struct dentry *fmpm_dfs_dir;122static struct dentry *fmpm_dfs_entries;123 124#define CPER_CREATOR_FMP						\125	GUID_INIT(0xcd5c2993, 0xf4b2, 0x41b2, 0xb5, 0xd4, 0xf9, 0xc3,	\126		  0xa0, 0x33, 0x08, 0x75)127 128#define CPER_SECTION_TYPE_FMP						\129	GUID_INIT(0x5e4706c1, 0x5356, 0x48c6, 0x93, 0x0b, 0x52, 0xf2,	\130		  0x12, 0x0a, 0x44, 0x58)131 132/**133 * DOC: max_nr_entries (byte)134 * Maximum number of descriptor entries possible for each FRU.135 *136 * Values between '1' and '255' are valid.137 * No input or '0' will default to FMPM_DEFAULT_MAX_NR_ENTRIES.138 */139static u8 max_nr_entries;140module_param(max_nr_entries, byte, 0644);141MODULE_PARM_DESC(max_nr_entries,142		 "Maximum number of memory poison descriptor entries per FRU");143 144#define FMPM_DEFAULT_MAX_NR_ENTRIES	8145 146/* Maximum number of FRUs in the system. */147#define FMPM_MAX_NR_FRU			256148static unsigned int max_nr_fru;149 150/* Total length of record including headers and list of descriptor entries. */151static size_t max_rec_len;152 153#define FMPM_MAX_REC_LEN (sizeof(struct fru_rec) + (sizeof(struct cper_fru_poison_desc) * 255))154 155/* Total number of SPA entries across all FRUs. */156static unsigned int spa_nr_entries;157 158/*159 * Protect the local records cache in fru_records and prevent concurrent160 * writes to storage. This is only needed after init once notifier block161 * registration is done.162 *163 * The majority of a record is fixed at module init and will not change164 * during run time. The entries within a record will be updated as new165 * errors are reported. The mutex should be held whenever the entries are166 * accessed during run time.167 */168static DEFINE_MUTEX(fmpm_update_mutex);169 170#define for_each_fru(i, rec) \171	for (i = 0; rec = fru_records[i], i < max_nr_fru; i++)172 173static inline u32 get_fmp_len(struct fru_rec *rec)174{175	return rec->sec_desc.section_length - sizeof(struct cper_section_descriptor);176}177 178static struct fru_rec *get_fru_record(u64 fru_id)179{180	struct fru_rec *rec;181	unsigned int i;182 183	for_each_fru(i, rec) {184		if (rec->fmp.fru_id == fru_id)185			return rec;186	}187 188	pr_debug("Record not found for FRU 0x%016llx\n", fru_id);189 190	return NULL;191}192 193/*194 * Sum up all bytes within the FRU Memory Poison Section including the Memory195 * Poison Descriptor entries.196 *197 * Don't include the old checksum here. It's a u32 value, so summing each of its198 * bytes will give the wrong total.199 */200static u32 do_fmp_checksum(struct cper_sec_fru_mem_poison *fmp, u32 len)201{202	u32 checksum = 0;203	u8 *buf, *end;204 205	/* Skip old checksum. */206	buf = (u8 *)fmp + sizeof(u32);207	end = buf + len;208 209	while (buf < end)210		checksum += (u8)(*(buf++));211 212	return checksum;213}214 215static int update_record_on_storage(struct fru_rec *rec)216{217	u32 len, checksum;218	int ret;219 220	/* Calculate a new checksum. */221	len = get_fmp_len(rec);222 223	/* Get the current total. */224	checksum = do_fmp_checksum(&rec->fmp, len);225 226	/* Use the complement value. */227	rec->fmp.checksum = -checksum;228 229	pr_debug("Writing to storage\n");230 231	ret = erst_write(&rec->hdr);232	if (ret) {233		pr_warn("Storage update failed for FRU 0x%016llx\n", rec->fmp.fru_id);234 235		if (ret == -ENOSPC)236			pr_warn("Not enough space on storage\n");237	}238 239	return ret;240}241 242static bool rec_has_valid_entries(struct fru_rec *rec)243{244	if (!(rec->fmp.validation_bits & FMP_VALID_LIST_ENTRIES))245		return false;246 247	if (!(rec->fmp.validation_bits & FMP_VALID_LIST))248		return false;249 250	return true;251}252 253static bool fpds_equal(struct cper_fru_poison_desc *old, struct cper_fru_poison_desc *new)254{255	/*256	 * Ignore timestamp field.257	 * The same physical error may be reported multiple times due to stuck bits, etc.258	 *259	 * Also, order the checks from most->least likely to fail to shortcut the code.260	 */261	if (old->addr != new->addr)262		return false;263 264	if (old->hw_id != new->hw_id)265		return false;266 267	if (old->addr_type != new->addr_type)268		return false;269 270	if (old->hw_id_type != new->hw_id_type)271		return false;272 273	return true;274}275 276static bool rec_has_fpd(struct fru_rec *rec, struct cper_fru_poison_desc *fpd)277{278	unsigned int i;279 280	for (i = 0; i < rec->fmp.nr_entries; i++) {281		struct cper_fru_poison_desc *fpd_i = &rec->entries[i];282 283		if (fpds_equal(fpd_i, fpd)) {284			pr_debug("Found duplicate record\n");285			return true;286		}287	}288 289	return false;290}291 292static void save_spa(struct fru_rec *rec, unsigned int entry,293		     u64 addr, u64 id, unsigned int cpu)294{295	unsigned int i, fru_idx, spa_entry;296	struct atl_err a_err;297	unsigned long spa;298 299	if (entry >= max_nr_entries) {300		pr_warn_once("FRU descriptor entry %d out-of-bounds (max: %d)\n",301			     entry, max_nr_entries);302		return;303	}304 305	/* spa_nr_entries is always multiple of max_nr_entries */306	for (i = 0; i < spa_nr_entries; i += max_nr_entries) {307		fru_idx = i / max_nr_entries;308		if (fru_records[fru_idx] == rec)309			break;310	}311 312	if (i >= spa_nr_entries) {313		pr_warn_once("FRU record %d not found\n", i);314		return;315	}316 317	spa_entry = i + entry;318	if (spa_entry >= spa_nr_entries) {319		pr_warn_once("spa_entries[] index out-of-bounds\n");320		return;321	}322 323	memset(&a_err, 0, sizeof(struct atl_err));324 325	a_err.addr = addr;326	a_err.ipid = id;327	a_err.cpu  = cpu;328 329	spa = amd_convert_umc_mca_addr_to_sys_addr(&a_err);330	if (IS_ERR_VALUE(spa)) {331		pr_debug("Failed to get system address\n");332		return;333	}334 335	spa_entries[spa_entry] = spa;336	pr_debug("fru_idx: %u, entry: %u, spa_entry: %u, spa: 0x%016llx\n",337		 fru_idx, entry, spa_entry, spa_entries[spa_entry]);338}339 340static void update_fru_record(struct fru_rec *rec, struct mce *m)341{342	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;343	struct cper_fru_poison_desc fpd, *fpd_dest;344	u32 entry = 0;345 346	mutex_lock(&fmpm_update_mutex);347 348	memset(&fpd, 0, sizeof(struct cper_fru_poison_desc));349 350	fpd.timestamp	= m->time;351	fpd.hw_id_type = FPD_HW_ID_TYPE_MCA_IPID;352	fpd.hw_id	= m->ipid;353	fpd.addr_type	= FPD_ADDR_TYPE_MCA_ADDR;354	fpd.addr	= m->addr;355 356	/* This is the first entry, so just save it. */357	if (!rec_has_valid_entries(rec))358		goto save_fpd;359 360	/* Ignore already recorded errors. */361	if (rec_has_fpd(rec, &fpd))362		goto out_unlock;363 364	if (rec->fmp.nr_entries >= max_nr_entries) {365		pr_warn("Exceeded number of entries for FRU 0x%016llx\n", rec->fmp.fru_id);366		goto out_unlock;367	}368 369	entry  = fmp->nr_entries;370 371save_fpd:372	save_spa(rec, entry, m->addr, m->ipid, m->extcpu);373	fpd_dest  = &rec->entries[entry];374	memcpy(fpd_dest, &fpd, sizeof(struct cper_fru_poison_desc));375 376	fmp->nr_entries		 = entry + 1;377	fmp->validation_bits	|= FMP_VALID_LIST_ENTRIES;378	fmp->validation_bits	|= FMP_VALID_LIST;379 380	pr_debug("Updated FRU 0x%016llx entry #%u\n", fmp->fru_id, entry);381 382	update_record_on_storage(rec);383 384out_unlock:385	mutex_unlock(&fmpm_update_mutex);386}387 388static void retire_dram_row(u64 addr, u64 id, u32 cpu)389{390	struct atl_err a_err;391 392	memset(&a_err, 0, sizeof(struct atl_err));393 394	a_err.addr = addr;395	a_err.ipid = id;396	a_err.cpu  = cpu;397 398	amd_retire_dram_row(&a_err);399}400 401static int fru_handle_mem_poison(struct notifier_block *nb, unsigned long val, void *data)402{403	struct mce *m = (struct mce *)data;404	struct fru_rec *rec;405 406	if (!mce_is_memory_error(m))407		return NOTIFY_DONE;408 409	retire_dram_row(m->addr, m->ipid, m->extcpu);410 411	/*412	 * An invalid FRU ID should not happen on real errors. But it413	 * could happen from software error injection, etc.414	 */415	rec = get_fru_record(m->ppin);416	if (!rec)417		return NOTIFY_DONE;418 419	update_fru_record(rec, m);420 421	return NOTIFY_OK;422}423 424static struct notifier_block fru_mem_poison_nb = {425	.notifier_call  = fru_handle_mem_poison,426	.priority	= MCE_PRIO_LOWEST,427};428 429static void retire_mem_fmp(struct fru_rec *rec)430{431	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;432	unsigned int i, cpu;433 434	for (i = 0; i < fmp->nr_entries; i++) {435		struct cper_fru_poison_desc *fpd = &rec->entries[i];436		unsigned int err_cpu = INVALID_CPU;437 438		if (fpd->hw_id_type != FPD_HW_ID_TYPE_MCA_IPID)439			continue;440 441		if (fpd->addr_type != FPD_ADDR_TYPE_MCA_ADDR)442			continue;443 444		cpus_read_lock();445		for_each_online_cpu(cpu) {446			if (topology_ppin(cpu) == fmp->fru_id) {447				err_cpu = cpu;448				break;449			}450		}451		cpus_read_unlock();452 453		if (err_cpu == INVALID_CPU)454			continue;455 456		retire_dram_row(fpd->addr, fpd->hw_id, err_cpu);457		save_spa(rec, i, fpd->addr, fpd->hw_id, err_cpu);458	}459}460 461static void retire_mem_records(void)462{463	struct fru_rec *rec;464	unsigned int i;465 466	for_each_fru(i, rec) {467		if (!rec_has_valid_entries(rec))468			continue;469 470		retire_mem_fmp(rec);471	}472}473 474/* Set the CPER Record Header and CPER Section Descriptor fields. */475static void set_rec_fields(struct fru_rec *rec)476{477	struct cper_section_descriptor	*sec_desc = &rec->sec_desc;478	struct cper_record_header	*hdr	  = &rec->hdr;479 480	/*481	 * This is a saved record created with fewer max_nr_entries.482	 * Update the record lengths and keep everything else as-is.483	 */484	if (hdr->record_length && hdr->record_length < max_rec_len) {485		pr_debug("Growing record 0x%016llx from %u to %zu bytes\n",486			 hdr->record_id, hdr->record_length, max_rec_len);487		goto update_lengths;488	}489 490	memcpy(hdr->signature, CPER_SIG_RECORD, CPER_SIG_SIZE);491	hdr->revision			= CPER_RECORD_REV;492	hdr->signature_end		= CPER_SIG_END;493 494	/*495	 * Currently, it is assumed that there is one FRU Memory Poison496	 * section per CPER. But this may change for other implementations.497	 */498	hdr->section_count		= 1;499 500	/* The logged errors are recoverable. Otherwise, they'd never make it here. */501	hdr->error_severity		= CPER_SEV_RECOVERABLE;502 503	hdr->validation_bits		= 0;504	hdr->creator_id			= CPER_CREATOR_FMP;505	hdr->notification_type		= CPER_NOTIFY_MCE;506	hdr->record_id			= cper_next_record_id();507	hdr->flags			= CPER_HW_ERROR_FLAGS_PREVERR;508 509	sec_desc->section_offset	= sizeof(struct cper_record_header);510	sec_desc->revision		= CPER_SEC_REV;511	sec_desc->validation_bits	= 0;512	sec_desc->flags			= CPER_SEC_PRIMARY;513	sec_desc->section_type		= CPER_SECTION_TYPE_FMP;514	sec_desc->section_severity	= CPER_SEV_RECOVERABLE;515 516update_lengths:517	hdr->record_length		= max_rec_len;518	sec_desc->section_length	= max_rec_len - sizeof(struct cper_record_header);519}520 521static int save_new_records(void)522{523	DECLARE_BITMAP(new_records, FMPM_MAX_NR_FRU);524	struct fru_rec *rec;525	unsigned int i;526	int ret = 0;527 528	for_each_fru(i, rec) {529		/* No need to update saved records that match the current record size. */530		if (rec->hdr.record_length == max_rec_len)531			continue;532 533		if (!rec->hdr.record_length)534			set_bit(i, new_records);535 536		set_rec_fields(rec);537 538		ret = update_record_on_storage(rec);539		if (ret)540			goto out_clear;541	}542 543	return ret;544 545out_clear:546	for_each_fru(i, rec) {547		if (!test_bit(i, new_records))548			continue;549 550		erst_clear(rec->hdr.record_id);551	}552 553	return ret;554}555 556/* Check that the record matches expected types for the current system.*/557static bool fmp_is_usable(struct fru_rec *rec)558{559	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;560	u64 cpuid;561 562	pr_debug("Validation bits: 0x%016llx\n", fmp->validation_bits);563 564	if (!(fmp->validation_bits & FMP_VALID_ARCH_TYPE)) {565		pr_debug("Arch type unknown\n");566		return false;567	}568 569	if (fmp->fru_arch_type != FMP_ARCH_TYPE_X86_CPUID_1_EAX) {570		pr_debug("Arch type not 'x86 Family/Model/Stepping'\n");571		return false;572	}573 574	if (!(fmp->validation_bits & FMP_VALID_ARCH)) {575		pr_debug("Arch value unknown\n");576		return false;577	}578 579	cpuid = cpuid_eax(1);580	if (fmp->fru_arch != cpuid) {581		pr_debug("Arch value mismatch: record = 0x%016llx, system = 0x%016llx\n",582			 fmp->fru_arch, cpuid);583		return false;584	}585 586	if (!(fmp->validation_bits & FMP_VALID_ID_TYPE)) {587		pr_debug("FRU ID type unknown\n");588		return false;589	}590 591	if (fmp->fru_id_type != FMP_ID_TYPE_X86_PPIN) {592		pr_debug("FRU ID type is not 'x86 PPIN'\n");593		return false;594	}595 596	if (!(fmp->validation_bits & FMP_VALID_ID)) {597		pr_debug("FRU ID value unknown\n");598		return false;599	}600 601	return true;602}603 604static bool fmp_is_valid(struct fru_rec *rec)605{606	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;607	u32 checksum, len;608 609	len = get_fmp_len(rec);610	if (len < sizeof(struct cper_sec_fru_mem_poison)) {611		pr_debug("fmp length is too small\n");612		return false;613	}614 615	/* Checksum must sum to zero for the entire section. */616	checksum = do_fmp_checksum(fmp, len) + fmp->checksum;617	if (checksum) {618		pr_debug("fmp checksum failed: sum = 0x%x\n", checksum);619		print_hex_dump_debug("fmp record: ", DUMP_PREFIX_NONE, 16, 1, fmp, len, false);620		return false;621	}622 623	if (!fmp_is_usable(rec))624		return false;625 626	return true;627}628 629static struct fru_rec *get_valid_record(struct fru_rec *old)630{631	struct fru_rec *new;632 633	if (!fmp_is_valid(old)) {634		pr_debug("Ignoring invalid record\n");635		return NULL;636	}637 638	new = get_fru_record(old->fmp.fru_id);639	if (!new)640		pr_debug("Ignoring record for absent FRU\n");641 642	return new;643}644 645/*646 * Fetch saved records from persistent storage.647 *648 * For each found record:649 * - If it was not created by this module, then ignore it.650 * - If it is valid, then copy its data to the local cache.651 * - If it is not valid, then erase it.652 */653static int get_saved_records(void)654{655	struct fru_rec *old, *new;656	u64 record_id;657	int ret, pos;658	ssize_t len;659 660	old = kmalloc(FMPM_MAX_REC_LEN, GFP_KERNEL);661	if (!old) {662		ret = -ENOMEM;663		goto out;664	}665 666	ret = erst_get_record_id_begin(&pos);667	if (ret < 0)668		goto out_end;669 670	while (!erst_get_record_id_next(&pos, &record_id)) {671		if (record_id == APEI_ERST_INVALID_RECORD_ID)672			goto out_end;673		/*674		 * Make sure to clear temporary buffer between reads to avoid675		 * leftover data from records of various sizes.676		 */677		memset(old, 0, FMPM_MAX_REC_LEN);678 679		len = erst_read_record(record_id, &old->hdr, FMPM_MAX_REC_LEN,680				       sizeof(struct fru_rec), &CPER_CREATOR_FMP);681		if (len < 0)682			continue;683 684		new = get_valid_record(old);685		if (!new) {686			erst_clear(record_id);687			continue;688		}689 690		if (len > max_rec_len) {691			unsigned int saved_nr_entries;692 693			saved_nr_entries  = len - sizeof(struct fru_rec);694			saved_nr_entries /= sizeof(struct cper_fru_poison_desc);695 696			pr_warn("Saved record found with %u entries.\n", saved_nr_entries);697			pr_warn("Please increase max_nr_entries to %u.\n", saved_nr_entries);698 699			ret = -EINVAL;700			goto out_end;701		}702 703		/* Restore the record */704		memcpy(new, old, len);705	}706 707out_end:708	erst_get_record_id_end();709	kfree(old);710out:711	return ret;712}713 714static void set_fmp_fields(struct fru_rec *rec, unsigned int cpu)715{716	struct cper_sec_fru_mem_poison *fmp = &rec->fmp;717 718	fmp->fru_arch_type    = FMP_ARCH_TYPE_X86_CPUID_1_EAX;719	fmp->validation_bits |= FMP_VALID_ARCH_TYPE;720 721	/* Assume all CPUs in the system have the same value for now. */722	fmp->fru_arch	      = cpuid_eax(1);723	fmp->validation_bits |= FMP_VALID_ARCH;724 725	fmp->fru_id_type      = FMP_ID_TYPE_X86_PPIN;726	fmp->validation_bits |= FMP_VALID_ID_TYPE;727 728	fmp->fru_id	      = topology_ppin(cpu);729	fmp->validation_bits |= FMP_VALID_ID;730}731 732static int init_fmps(void)733{734	struct fru_rec *rec;735	unsigned int i, cpu;736	int ret = 0;737 738	for_each_fru(i, rec) {739		unsigned int fru_cpu = INVALID_CPU;740 741		cpus_read_lock();742		for_each_online_cpu(cpu) {743			if (topology_physical_package_id(cpu) == i) {744				fru_cpu = cpu;745				break;746			}747		}748		cpus_read_unlock();749 750		if (fru_cpu == INVALID_CPU) {751			pr_debug("Failed to find matching CPU for FRU #%u\n", i);752			ret = -ENODEV;753			break;754		}755 756		set_fmp_fields(rec, fru_cpu);757	}758 759	return ret;760}761 762static int get_system_info(void)763{764	/* Only load on MI300A systems for now. */765	if (!(boot_cpu_data.x86_model >= 0x90 &&766	      boot_cpu_data.x86_model <= 0x9f))767		return -ENODEV;768 769	if (!cpu_feature_enabled(X86_FEATURE_AMD_PPIN)) {770		pr_debug("PPIN feature not available\n");771		return -ENODEV;772	}773 774	/* Use CPU socket as FRU for MI300 systems. */775	max_nr_fru = topology_max_packages();776	if (!max_nr_fru)777		return -ENODEV;778 779	if (max_nr_fru > FMPM_MAX_NR_FRU) {780		pr_warn("Too many FRUs to manage: found: %u, max: %u\n",781			max_nr_fru, FMPM_MAX_NR_FRU);782		return -ENODEV;783	}784 785	if (!max_nr_entries)786		max_nr_entries = FMPM_DEFAULT_MAX_NR_ENTRIES;787 788	spa_nr_entries = max_nr_fru * max_nr_entries;789 790	max_rec_len  = sizeof(struct fru_rec);791	max_rec_len += sizeof(struct cper_fru_poison_desc) * max_nr_entries;792 793	pr_info("max FRUs: %u, max entries: %u, max record length: %lu\n",794		 max_nr_fru, max_nr_entries, max_rec_len);795 796	return 0;797}798 799static void free_records(void)800{801	struct fru_rec *rec;802	int i;803 804	for_each_fru(i, rec)805		kfree(rec);806 807	kfree(fru_records);808	kfree(spa_entries);809}810 811static int allocate_records(void)812{813	int i, ret = 0;814 815	fru_records = kcalloc(max_nr_fru, sizeof(struct fru_rec *), GFP_KERNEL);816	if (!fru_records) {817		ret = -ENOMEM;818		goto out;819	}820 821	for (i = 0; i < max_nr_fru; i++) {822		fru_records[i] = kzalloc(max_rec_len, GFP_KERNEL);823		if (!fru_records[i]) {824			ret = -ENOMEM;825			goto out_free;826		}827	}828 829	spa_entries = kcalloc(spa_nr_entries, sizeof(u64), GFP_KERNEL);830	if (!spa_entries) {831		ret = -ENOMEM;832		goto out_free;833	}834 835	for (i = 0; i < spa_nr_entries; i++)836		spa_entries[i] = INVALID_SPA;837 838	return ret;839 840out_free:841	while (--i >= 0)842		kfree(fru_records[i]);843 844	kfree(fru_records);845out:846	return ret;847}848 849static void *fmpm_start(struct seq_file *f, loff_t *pos)850{851	if (*pos >= (spa_nr_entries + 1))852		return NULL;853	return pos;854}855 856static void *fmpm_next(struct seq_file *f, void *data, loff_t *pos)857{858	if (++(*pos) >= (spa_nr_entries + 1))859		return NULL;860	return pos;861}862 863static void fmpm_stop(struct seq_file *f, void *data)864{865}866 867#define SHORT_WIDTH	8868#define U64_WIDTH	18869#define TIMESTAMP_WIDTH	19870#define LONG_WIDTH	24871#define U64_PAD		(LONG_WIDTH - U64_WIDTH)872#define TS_PAD		(LONG_WIDTH - TIMESTAMP_WIDTH)873static int fmpm_show(struct seq_file *f, void *data)874{875	unsigned int fru_idx, entry, spa_entry, line;876	struct cper_fru_poison_desc *fpd;877	struct fru_rec *rec;878 879	line = *(loff_t *)data;880	if (line == 0) {881		seq_printf(f, "%-*s", SHORT_WIDTH, "fru_idx");882		seq_printf(f, "%-*s", LONG_WIDTH,  "fru_id");883		seq_printf(f, "%-*s", SHORT_WIDTH, "entry");884		seq_printf(f, "%-*s", LONG_WIDTH,  "timestamp");885		seq_printf(f, "%-*s", LONG_WIDTH,  "hw_id");886		seq_printf(f, "%-*s", LONG_WIDTH,  "addr");887		seq_printf(f, "%-*s", LONG_WIDTH,  "spa");888		goto out_newline;889	}890 891	spa_entry = line - 1;892	fru_idx	  = spa_entry / max_nr_entries;893	entry	  = spa_entry % max_nr_entries;894 895	rec = fru_records[fru_idx];896	if (!rec)897		goto out;898 899	seq_printf(f, "%-*u",		SHORT_WIDTH, fru_idx);900	seq_printf(f, "0x%016llx%-*s",	rec->fmp.fru_id, U64_PAD, "");901	seq_printf(f, "%-*u",		SHORT_WIDTH, entry);902 903	mutex_lock(&fmpm_update_mutex);904 905	if (entry >= rec->fmp.nr_entries) {906		seq_printf(f, "%-*s", LONG_WIDTH, "*");907		seq_printf(f, "%-*s", LONG_WIDTH, "*");908		seq_printf(f, "%-*s", LONG_WIDTH, "*");909		seq_printf(f, "%-*s", LONG_WIDTH, "*");910		goto out_unlock;911	}912 913	fpd = &rec->entries[entry];914 915	seq_printf(f, "%ptT%-*s",	&fpd->timestamp, TS_PAD,  "");916	seq_printf(f, "0x%016llx%-*s",	fpd->hw_id,	 U64_PAD, "");917	seq_printf(f, "0x%016llx%-*s",	fpd->addr,	 U64_PAD, "");918 919	if (spa_entries[spa_entry] == INVALID_SPA)920		seq_printf(f, "%-*s", LONG_WIDTH, "*");921	else922		seq_printf(f, "0x%016llx%-*s", spa_entries[spa_entry], U64_PAD, "");923 924out_unlock:925	mutex_unlock(&fmpm_update_mutex);926out_newline:927	seq_putc(f, '\n');928out:929	return 0;930}931 932static const struct seq_operations fmpm_seq_ops = {933	.start	= fmpm_start,934	.next	= fmpm_next,935	.stop	= fmpm_stop,936	.show	= fmpm_show,937};938 939static int fmpm_open(struct inode *inode, struct file *file)940{941	return seq_open(file, &fmpm_seq_ops);942}943 944static const struct file_operations fmpm_fops = {945	.open		= fmpm_open,946	.release	= seq_release,947	.read		= seq_read,948	.llseek		= seq_lseek,949};950 951static void setup_debugfs(void)952{953	struct dentry *dfs = ras_get_debugfs_root();954 955	if (!dfs)956		return;957 958	fmpm_dfs_dir = debugfs_create_dir("fmpm", dfs);959	if (!fmpm_dfs_dir)960		return;961 962	fmpm_dfs_entries = debugfs_create_file("entries", 0400, fmpm_dfs_dir, NULL, &fmpm_fops);963	if (!fmpm_dfs_entries)964		debugfs_remove(fmpm_dfs_dir);965}966 967static const struct x86_cpu_id fmpm_cpuids[] = {968	X86_MATCH_VENDOR_FAM(AMD, 0x19, NULL),969	{ }970};971MODULE_DEVICE_TABLE(x86cpu, fmpm_cpuids);972 973static int __init fru_mem_poison_init(void)974{975	int ret;976 977	if (!x86_match_cpu(fmpm_cpuids)) {978		ret = -ENODEV;979		goto out;980	}981 982	if (erst_disable) {983		pr_debug("ERST not available\n");984		ret = -ENODEV;985		goto out;986	}987 988	ret = get_system_info();989	if (ret)990		goto out;991 992	ret = allocate_records();993	if (ret)994		goto out;995 996	ret = init_fmps();997	if (ret)998		goto out_free;999 1000	ret = get_saved_records();1001	if (ret)1002		goto out_free;1003 1004	ret = save_new_records();1005	if (ret)1006		goto out_free;1007 1008	setup_debugfs();1009 1010	retire_mem_records();1011 1012	mce_register_decode_chain(&fru_mem_poison_nb);1013 1014	pr_info("FRU Memory Poison Manager initialized\n");1015	return 0;1016 1017out_free:1018	free_records();1019out:1020	return ret;1021}1022 1023static void __exit fru_mem_poison_exit(void)1024{1025	mce_unregister_decode_chain(&fru_mem_poison_nb);1026	debugfs_remove(fmpm_dfs_dir);1027	free_records();1028}1029 1030module_init(fru_mem_poison_init);1031module_exit(fru_mem_poison_exit);1032 1033MODULE_LICENSE("GPL");1034MODULE_DESCRIPTION("FRU Memory Poison Manager");1035