1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * FRU (Field-Replaceable Unit) Memory Poison Manager
4 *
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 yet
16 * included in the UEFI specification. So they are defined here. Afterwards, they
17 * 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 generalized
21 * 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 will
31 * fail if any FRU record is not successfully written.
32 *
33 * - Boot time memory retirement may occur later than ideal due to dependencies
34 * on other libraries and drivers. This leaves a gap where bad memory may be
35 * accessed during early boot stages.
36 *
37 * - Enough memory should be pre-allocated for each FRU record to be able to hold
38 * the expected number of descriptor entries. This, mostly empty, record is
39 * written to storage during init time. Subsequent writes to the same record
40 * 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 memory
42 * management operations on the storage. For example, the Platform may spend time
43 * in Firmware copying and invalidating memory on a relatively slow SPI ROM.
44 */
45
46#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
47
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#define INVALID_CPU UINT_MAX
60
61/* Validation Bits */
62#define FMP_VALID_ARCH_TYPE BIT_ULL(0)
63#define FMP_VALID_ARCH BIT_ULL(1)
64#define FMP_VALID_ID_TYPE BIT_ULL(2)
65#define FMP_VALID_ID BIT_ULL(3)
66#define FMP_VALID_LIST_ENTRIES BIT_ULL(4)
67#define FMP_VALID_LIST BIT_ULL(5)
68
69/* FRU Architecture Types */
70#define FMP_ARCH_TYPE_X86_CPUID_1_EAX 0
71
72/* FRU ID Types */
73#define FMP_ID_TYPE_X86_PPIN 0
74
75/* FRU Memory Poison Section */
76struct cper_sec_fru_mem_poison {
77 u32 checksum;
78 u64 validation_bits;
79 u32 fru_arch_type;
80 u64 fru_arch;
81 u32 fru_id_type;
82 u64 fru_id;
83 u32 nr_entries;
84} __packed;
85
86/* FRU Descriptor ID Types */
87#define FPD_HW_ID_TYPE_MCA_IPID 0
88
89/* FRU Descriptor Address Types */
90#define FPD_ADDR_TYPE_MCA_ADDR 0
91
92/* Memory Poison Descriptor */
93struct cper_fru_poison_desc {
94 u64 timestamp;
95 u32 hw_id_type;
96 u64 hw_id;
97 u32 addr_type;
98 u64 addr;
99} __packed;
100
101/* Collection of headers and sections for easy pointer use. */
102struct fru_rec {
103 struct cper_record_header hdr;
104 struct cper_section_descriptor sec_desc;
105 struct cper_sec_fru_mem_poison fmp;
106 struct cper_fru_poison_desc entries[];
107} __packed;
108
109/*
110 * Pointers to the complete CPER record of each FRU.
111 *
112 * Memory allocation will include padded space for descriptor entries.
113 */
114static struct fru_rec **fru_records;
115
116/* system physical addresses array */
117static u64 *spa_entries;
118
119#define INVALID_SPA ~0ULL
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 8
145
146/* Maximum number of FRUs in the system. */
147#define FMPM_MAX_NR_FRU 256
148static 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 concurrent
160 * writes to storage. This is only needed after init once notifier block
161 * registration is done.
162 *
163 * The majority of a record is fixed at module init and will not change
164 * during run time. The entries within a record will be updated as new
165 * errors are reported. The mutex should be held whenever the entries are
166 * 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 Memory
195 * Poison Descriptor entries.
196 *
197 * Don't include the old checksum here. It's a u32 value, so summing each of its
198 * 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(fmp: &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(record: &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(old: fpd_i, new: 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(err: &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: &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, addr: m->addr, id: m->ipid, cpu: 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(lock: &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(err: &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(addr: m->addr, id: m->ipid, cpu: m->extcpu);
410
411 /*
412 * An invalid FRU ID should not happen on real errors. But it
413 * could happen from software error injection, etc.
414 */
415 rec = get_fru_record(fru_id: 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(addr: fpd->addr, id: fpd->hw_id, cpu: err_cpu);
457 save_spa(rec, entry: i, addr: fpd->addr, id: fpd->hw_id, cpu: 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 Poison
496 * 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(nr: i, addr: 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(record_id: 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(op: 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(rec: old)) {
634 pr_debug("Ignoring invalid record\n");
635 return NULL;
636 }
637
638 new = get_fru_record(fru_id: 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: &pos);
667 if (ret < 0)
668 goto out_end;
669
670 while (!erst_get_record_id_next(pos: &pos, record_id: &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 avoid
675 * leftover data from records of various sizes.
676 */
677 memset(old, 0, FMPM_MAX_REC_LEN);
678
679 len = erst_read_record(record_id, record: &old->hdr, FMPM_MAX_REC_LEN,
680 recordlen: sizeof(struct fru_rec), creatorid: &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(objp: 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(op: 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, cpu: 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(objp: rec);
806
807 kfree(objp: fru_records);
808 kfree(objp: spa_entries);
809}
810
811static int allocate_records(void)
812{
813 int i, ret = 0;
814
815 fru_records = kcalloc(n: max_nr_fru, size: 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(size: max_rec_len, GFP_KERNEL);
823 if (!fru_records[i]) {
824 ret = -ENOMEM;
825 goto out_free;
826 }
827 }
828
829 spa_entries = kcalloc(n: spa_nr_entries, size: 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(objp: fru_records[i]);
843
844 kfree(objp: 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 8
868#define U64_WIDTH 18
869#define TIMESTAMP_WIDTH 19
870#define LONG_WIDTH 24
871#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(m: f, fmt: "%-*s", SHORT_WIDTH, "fru_idx");
882 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "fru_id");
883 seq_printf(m: f, fmt: "%-*s", SHORT_WIDTH, "entry");
884 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "timestamp");
885 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "hw_id");
886 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "addr");
887 seq_printf(m: f, fmt: "%-*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(m: f, fmt: "%-*u", SHORT_WIDTH, fru_idx);
900 seq_printf(m: f, fmt: "0x%016llx%-*s", rec->fmp.fru_id, U64_PAD, "");
901 seq_printf(m: f, fmt: "%-*u", SHORT_WIDTH, entry);
902
903 mutex_lock(&fmpm_update_mutex);
904
905 if (entry >= rec->fmp.nr_entries) {
906 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "*");
907 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "*");
908 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "*");
909 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "*");
910 goto out_unlock;
911 }
912
913 fpd = &rec->entries[entry];
914
915 seq_printf(m: f, fmt: "%ptT%-*s", &fpd->timestamp, TS_PAD, "");
916 seq_printf(m: f, fmt: "0x%016llx%-*s", fpd->hw_id, U64_PAD, "");
917 seq_printf(m: f, fmt: "0x%016llx%-*s", fpd->addr, U64_PAD, "");
918
919 if (spa_entries[spa_entry] == INVALID_SPA)
920 seq_printf(m: f, fmt: "%-*s", LONG_WIDTH, "*");
921 else
922 seq_printf(m: f, fmt: "0x%016llx%-*s", spa_entries[spa_entry], U64_PAD, "");
923
924out_unlock:
925 mutex_unlock(lock: &fmpm_update_mutex);
926out_newline:
927 seq_putc(m: f, c: '\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(name: "fmpm", parent: dfs);
959 if (!fmpm_dfs_dir)
960 return;
961
962 fmpm_dfs_entries = debugfs_create_file(name: "entries", mode: 0400, parent: fmpm_dfs_dir, NULL, fops: &fmpm_fops);
963 if (!fmpm_dfs_entries)
964 debugfs_remove(dentry: 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(match: 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(nb: &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(nb: &fru_mem_poison_nb);
1026 debugfs_remove(dentry: 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

source code of linux/drivers/ras/amd/fmpm.c