// 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.
*/
/* 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.
*/ staticstruct fru_rec **fru_records;
/* system physical addresses array */ static u64 *spa_entries;
/** * 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 256 staticunsignedint max_nr_fru;
/* Total length of record including headers and list of descriptor entries. */ static size_t max_rec_len;
/* Total number of SPA entries across all FRUs. */ staticunsignedint 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++)
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;
}
staticint 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;
}
staticbool rec_has_valid_entries(struct fru_rec *rec)
{ if (!(rec->fmp.validation_bits & FMP_VALID_LIST_ENTRIES)) returnfalse;
if (!(rec->fmp.validation_bits & FMP_VALID_LIST)) returnfalse;
returntrue;
}
/* * 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))
staticbool 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)) returnfalse;
if (old->hw_id != new->hw_id) returnfalse;
if (old->addr_type != new->addr_type) returnfalse;
if (old->hw_id_type != new->hw_id_type) returnfalse;
/* 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;
}
/* * 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;
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. */ staticvoid 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;
}
/* * Currently, it is assumed that there is one FRU Memory Poison * section per CPER. But this may change for other implementations.
*/
hdr->section_count = 1;
/* The logged errors are recoverable. Otherwise, they'd never make it here. */
hdr->error_severity = CPER_SEV_RECOVERABLE;
staticint save_new_records(void)
{
DECLARE_BITMAP(new_records, FMPM_MAX_NR_FRU); struct fru_rec *rec; unsignedint i; int ret = 0;
for_each_fru(i, rec) { /* No need to update saved records that match the current record size. */ if (rec->hdr.record_length == max_rec_len) continue;
if (!rec->hdr.record_length)
set_bit(i, new_records);
set_rec_fields(rec);
ret = update_record_on_storage(rec); if (ret) goto out_clear;
}
return ret;
out_clear:
for_each_fru(i, rec) { if (!test_bit(i, new_records)) continue;
erst_clear(rec->hdr.record_id);
}
return ret;
}
/* Check that the record matches expected types for the current system.*/ staticbool fmp_is_usable(struct fru_rec *rec)
{ struct cper_sec_fru_mem_poison *fmp = &rec->fmp;
u64 cpuid;
len = get_fmp_len(rec); if (len < sizeof(struct cper_sec_fru_mem_poison)) {
pr_debug("fmp length is too small\n"); returnfalse;
}
/* Checksum must sum to zero for the entire section. */
checksum = do_fmp_checksum(fmp, len) + fmp->checksum; if (checksum) {
pr_debug("fmp checksum failed: sum = 0x%x\n", checksum);
print_hex_dump_debug("fmp record: ", DUMP_PREFIX_NONE, 16, 1, fmp, len, false); returnfalse;
}
if (!fmp_is_valid(old)) {
pr_debug("Ignoring invalid record\n"); return NULL;
}
new = get_fru_record(old->fmp.fru_id); if (!new)
pr_debug("Ignoring record for absent FRU\n");
returnnew;
}
/* * Fetch saved records from persistent storage. * * For each found record: * - If it was not created by this module, then ignore it. * - If it is valid, then copy its data to the local cache. * - If it is not valid, then erase it.
*/ staticint get_saved_records(void)
{ struct fru_rec *old, *new;
u64 record_id; int ret, pos;
ssize_t len;
old = kmalloc(FMPM_MAX_REC_LEN, GFP_KERNEL); if (!old) {
ret = -ENOMEM; goto out;
}
ret = erst_get_record_id_begin(&pos); if (ret < 0) goto out_end;
while (!erst_get_record_id_next(&pos, &record_id)) { if (record_id == APEI_ERST_INVALID_RECORD_ID) goto out_end; /* * Make sure to clear temporary buffer between reads to avoid * leftover data from records of various sizes.
*/
memset(old, 0, FMPM_MAX_REC_LEN);
len = erst_read_record(record_id, &old->hdr, FMPM_MAX_REC_LEN, sizeof(struct fru_rec), &CPER_CREATOR_FMP); if (len < 0) continue;
new = get_valid_record(old); if (!new) {
erst_clear(record_id); continue;
}
if (len > max_rec_len) { unsignedint saved_nr_entries;
saved_nr_entries = len - sizeof(struct fru_rec);
saved_nr_entries /= sizeof(struct cper_fru_poison_desc);
pr_warn("Saved record found with %u entries.\n", saved_nr_entries);
pr_warn("Please increase max_nr_entries to %u.\n", saved_nr_entries);
if (fru_cpu == INVALID_CPU) {
pr_debug("Failed to find matching CPU for FRU #%u\n", i);
ret = -ENODEV; break;
}
set_fmp_fields(rec, fru_cpu);
}
return ret;
}
staticint get_system_info(void)
{ /* Only load on MI300A systems for now. */ if (!(boot_cpu_data.x86_model >= 0x90 &&
boot_cpu_data.x86_model <= 0x9f)) return -ENODEV;
if (!cpu_feature_enabled(X86_FEATURE_AMD_PPIN)) {
pr_debug("PPIN feature not available\n"); return -ENODEV;
}
/* Use CPU socket as FRU for MI300 systems. */
max_nr_fru = topology_max_packages(); if (!max_nr_fru) return -ENODEV;
if (max_nr_fru > FMPM_MAX_NR_FRU) {
pr_warn("Too many FRUs to manage: found: %u, max: %u\n",
max_nr_fru, FMPM_MAX_NR_FRU); return -ENODEV;
}
if (!max_nr_entries)
max_nr_entries = FMPM_DEFAULT_MAX_NR_ENTRIES;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.