123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Page Deallocation Table (PDT) support
- *
- * The Page Deallocation Table (PDT) is maintained by firmware and holds a
- * list of memory addresses in which memory errors were detected.
- * The list contains both single-bit (correctable) and double-bit
- * (uncorrectable) errors.
- *
- * Copyright 2017 by Helge Deller <deller@gmx.de>
- *
- * possible future enhancements:
- * - add userspace interface via procfs or sysfs to clear PDT
- */
- #include <linux/memblock.h>
- #include <linux/seq_file.h>
- #include <linux/kthread.h>
- #include <linux/initrd.h>
- #include <asm/pdc.h>
- #include <asm/pdcpat.h>
- #include <asm/sections.h>
- #include <asm/pgtable.h>
- enum pdt_access_type {
- PDT_NONE,
- PDT_PDC,
- PDT_PAT_NEW,
- PDT_PAT_CELL
- };
- static enum pdt_access_type pdt_type;
- /* PDT poll interval: 1 minute if errors, 5 minutes if everything OK. */
- #define PDT_POLL_INTERVAL_DEFAULT (5*60*HZ)
- #define PDT_POLL_INTERVAL_SHORT (1*60*HZ)
- static unsigned long pdt_poll_interval = PDT_POLL_INTERVAL_DEFAULT;
- /* global PDT status information */
- static struct pdc_mem_retinfo pdt_status;
- #define MAX_PDT_TABLE_SIZE PAGE_SIZE
- #define MAX_PDT_ENTRIES (MAX_PDT_TABLE_SIZE / sizeof(unsigned long))
- static unsigned long pdt_entry[MAX_PDT_ENTRIES] __page_aligned_bss;
- /*
- * Constants for the pdt_entry format:
- * A pdt_entry holds the physical address in bits 0-57, bits 58-61 are
- * reserved, bit 62 is the perm bit and bit 63 is the error_type bit.
- * The perm bit indicates whether the error have been verified as a permanent
- * error (value of 1) or has not been verified, and may be transient (value
- * of 0). The error_type bit indicates whether the error is a single bit error
- * (value of 1) or a multiple bit error.
- * On non-PAT machines phys_addr is encoded in bits 0-59 and error_type in bit
- * 63. Those machines don't provide the perm bit.
- */
- #define PDT_ADDR_PHYS_MASK (pdt_type != PDT_PDC ? ~0x3f : ~0x0f)
- #define PDT_ADDR_PERM_ERR (pdt_type != PDT_PDC ? 2UL : 0UL)
- #define PDT_ADDR_SINGLE_ERR 1UL
- /* report PDT entries via /proc/meminfo */
- void arch_report_meminfo(struct seq_file *m)
- {
- if (pdt_type == PDT_NONE)
- return;
- seq_printf(m, "PDT_max_entries: %7lu\n",
- pdt_status.pdt_size);
- seq_printf(m, "PDT_cur_entries: %7lu\n",
- pdt_status.pdt_entries);
- }
- static int get_info_pat_new(void)
- {
- struct pdc_pat_mem_retinfo pat_rinfo;
- int ret;
- /* newer PAT machines like C8000 report info for all cells */
- if (is_pdc_pat())
- ret = pdc_pat_mem_pdt_info(&pat_rinfo);
- else
- return PDC_BAD_PROC;
- pdt_status.pdt_size = pat_rinfo.max_pdt_entries;
- pdt_status.pdt_entries = pat_rinfo.current_pdt_entries;
- pdt_status.pdt_status = 0;
- pdt_status.first_dbe_loc = pat_rinfo.first_dbe_loc;
- pdt_status.good_mem = pat_rinfo.good_mem;
- return ret;
- }
- static int get_info_pat_cell(void)
- {
- struct pdc_pat_mem_cell_pdt_retinfo cell_rinfo;
- int ret;
- /* older PAT machines like rp5470 report cell info only */
- if (is_pdc_pat())
- ret = pdc_pat_mem_pdt_cell_info(&cell_rinfo, parisc_cell_num);
- else
- return PDC_BAD_PROC;
- pdt_status.pdt_size = cell_rinfo.max_pdt_entries;
- pdt_status.pdt_entries = cell_rinfo.current_pdt_entries;
- pdt_status.pdt_status = 0;
- pdt_status.first_dbe_loc = cell_rinfo.first_dbe_loc;
- pdt_status.good_mem = cell_rinfo.good_mem;
- return ret;
- }
- static void report_mem_err(unsigned long pde)
- {
- struct pdc_pat_mem_phys_mem_location loc;
- unsigned long addr;
- char dimm_txt[32];
- addr = pde & PDT_ADDR_PHYS_MASK;
- /* show DIMM slot description on PAT machines */
- if (is_pdc_pat()) {
- pdc_pat_mem_get_dimm_phys_location(&loc, addr);
- sprintf(dimm_txt, "DIMM slot %02x, ", loc.dimm_slot);
- } else
- dimm_txt[0] = 0;
- pr_warn("PDT: BAD MEMORY at 0x%08lx, %s%s%s-bit error.\n",
- addr, dimm_txt,
- pde & PDT_ADDR_PERM_ERR ? "permanent ":"",
- pde & PDT_ADDR_SINGLE_ERR ? "single":"multi");
- }
- /*
- * pdc_pdt_init()
- *
- * Initialize kernel PDT structures, read initial PDT table from firmware,
- * report all current PDT entries and mark bad memory with memblock_reserve()
- * to avoid that the kernel will use broken memory areas.
- *
- */
- void __init pdc_pdt_init(void)
- {
- int ret, i;
- unsigned long entries;
- struct pdc_mem_read_pdt pdt_read_ret;
- pdt_type = PDT_PAT_NEW;
- ret = get_info_pat_new();
- if (ret != PDC_OK) {
- pdt_type = PDT_PAT_CELL;
- ret = get_info_pat_cell();
- }
- if (ret != PDC_OK) {
- pdt_type = PDT_PDC;
- /* non-PAT machines provide the standard PDC call */
- ret = pdc_mem_pdt_info(&pdt_status);
- }
- if (ret != PDC_OK) {
- pdt_type = PDT_NONE;
- pr_info("PDT: Firmware does not provide any page deallocation"
- " information.\n");
- return;
- }
- entries = pdt_status.pdt_entries;
- if (WARN_ON(entries > MAX_PDT_ENTRIES))
- entries = pdt_status.pdt_entries = MAX_PDT_ENTRIES;
- pr_info("PDT: type %s, size %lu, entries %lu, status %lu, dbe_loc 0x%lx,"
- " good_mem %lu MB\n",
- pdt_type == PDT_PDC ? __stringify(PDT_PDC) :
- pdt_type == PDT_PAT_CELL ? __stringify(PDT_PAT_CELL)
- : __stringify(PDT_PAT_NEW),
- pdt_status.pdt_size, pdt_status.pdt_entries,
- pdt_status.pdt_status, pdt_status.first_dbe_loc,
- pdt_status.good_mem / 1024 / 1024);
- if (entries == 0) {
- pr_info("PDT: Firmware reports all memory OK.\n");
- return;
- }
- if (pdt_status.first_dbe_loc &&
- pdt_status.first_dbe_loc <= __pa((unsigned long)&_end))
- pr_crit("CRITICAL: Bad memory inside kernel image memory area!\n");
- pr_warn("PDT: Firmware reports %lu entries of faulty memory:\n",
- entries);
- if (pdt_type == PDT_PDC)
- ret = pdc_mem_pdt_read_entries(&pdt_read_ret, pdt_entry);
- else {
- #ifdef CONFIG_64BIT
- struct pdc_pat_mem_read_pd_retinfo pat_pret;
- if (pdt_type == PDT_PAT_CELL)
- ret = pdc_pat_mem_read_cell_pdt(&pat_pret, pdt_entry,
- MAX_PDT_ENTRIES);
- else
- ret = pdc_pat_mem_read_pd_pdt(&pat_pret, pdt_entry,
- MAX_PDT_TABLE_SIZE, 0);
- #else
- ret = PDC_BAD_PROC;
- #endif
- }
- if (ret != PDC_OK) {
- pdt_type = PDT_NONE;
- pr_warn("PDT: Get PDT entries failed with %d\n", ret);
- return;
- }
- for (i = 0; i < pdt_status.pdt_entries; i++) {
- unsigned long addr;
- report_mem_err(pdt_entry[i]);
- addr = pdt_entry[i] & PDT_ADDR_PHYS_MASK;
- if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) &&
- addr >= initrd_start && addr < initrd_end)
- pr_crit("CRITICAL: initrd possibly broken "
- "due to bad memory!\n");
- /* mark memory page bad */
- memblock_reserve(pdt_entry[i] & PAGE_MASK, PAGE_SIZE);
- }
- }
- /*
- * This is the PDT kernel thread main loop.
- */
- static int pdt_mainloop(void *unused)
- {
- struct pdc_mem_read_pdt pdt_read_ret;
- struct pdc_pat_mem_read_pd_retinfo pat_pret __maybe_unused;
- unsigned long old_num_entries;
- unsigned long *bad_mem_ptr;
- int num, ret;
- for (;;) {
- set_current_state(TASK_INTERRUPTIBLE);
- old_num_entries = pdt_status.pdt_entries;
- schedule_timeout(pdt_poll_interval);
- if (kthread_should_stop())
- break;
- /* Do we have new PDT entries? */
- switch (pdt_type) {
- case PDT_PAT_NEW:
- ret = get_info_pat_new();
- break;
- case PDT_PAT_CELL:
- ret = get_info_pat_cell();
- break;
- default:
- ret = pdc_mem_pdt_info(&pdt_status);
- break;
- }
- if (ret != PDC_OK) {
- pr_warn("PDT: unexpected failure %d\n", ret);
- return -EINVAL;
- }
- /* if no new PDT entries, just wait again */
- num = pdt_status.pdt_entries - old_num_entries;
- if (num <= 0)
- continue;
- /* decrease poll interval in case we found memory errors */
- if (pdt_status.pdt_entries &&
- pdt_poll_interval == PDT_POLL_INTERVAL_DEFAULT)
- pdt_poll_interval = PDT_POLL_INTERVAL_SHORT;
- /* limit entries to get */
- if (num > MAX_PDT_ENTRIES) {
- num = MAX_PDT_ENTRIES;
- pdt_status.pdt_entries = old_num_entries + num;
- }
- /* get new entries */
- switch (pdt_type) {
- #ifdef CONFIG_64BIT
- case PDT_PAT_CELL:
- if (pdt_status.pdt_entries > MAX_PDT_ENTRIES) {
- pr_crit("PDT: too many entries.\n");
- return -ENOMEM;
- }
- ret = pdc_pat_mem_read_cell_pdt(&pat_pret, pdt_entry,
- MAX_PDT_ENTRIES);
- bad_mem_ptr = &pdt_entry[old_num_entries];
- break;
- case PDT_PAT_NEW:
- ret = pdc_pat_mem_read_pd_pdt(&pat_pret,
- pdt_entry,
- num * sizeof(unsigned long),
- old_num_entries * sizeof(unsigned long));
- bad_mem_ptr = &pdt_entry[0];
- break;
- #endif
- default:
- ret = pdc_mem_pdt_read_entries(&pdt_read_ret,
- pdt_entry);
- bad_mem_ptr = &pdt_entry[old_num_entries];
- break;
- }
- /* report and mark memory broken */
- while (num--) {
- unsigned long pde = *bad_mem_ptr++;
- report_mem_err(pde);
- #ifdef CONFIG_MEMORY_FAILURE
- if ((pde & PDT_ADDR_PERM_ERR) ||
- ((pde & PDT_ADDR_SINGLE_ERR) == 0))
- memory_failure(pde >> PAGE_SHIFT, 0, 0);
- else
- soft_offline_page(
- pfn_to_page(pde >> PAGE_SHIFT), 0);
- #else
- pr_crit("PDT: memory error at 0x%lx ignored.\n"
- "Rebuild kernel with CONFIG_MEMORY_FAILURE=y "
- "for real handling.\n",
- pde & PDT_ADDR_PHYS_MASK);
- #endif
- }
- }
- return 0;
- }
- static int __init pdt_initcall(void)
- {
- struct task_struct *kpdtd_task;
- if (pdt_type == PDT_NONE)
- return -ENODEV;
- kpdtd_task = kthread_create(pdt_mainloop, NULL, "kpdtd");
- if (IS_ERR(kpdtd_task))
- return PTR_ERR(kpdtd_task);
- wake_up_process(kpdtd_task);
- return 0;
- }
- late_initcall(pdt_initcall);
|