1 /* 2 * Page Deallocation Table (PDT) support 3 * 4 * The Page Deallocation Table (PDT) holds a table with pointers to bad 5 * memory (broken RAM modules) which is maintained by firmware. 6 * 7 * Copyright 2017 by Helge Deller <deller@gmx.de> 8 * 9 * TODO: 10 * - check regularily for new bad memory 11 * - add userspace interface with procfs or sysfs 12 * - increase number of PDT entries dynamically 13 */ 14 15 #include <linux/memblock.h> 16 #include <linux/seq_file.h> 17 18 #include <asm/pdc.h> 19 #include <asm/pdcpat.h> 20 #include <asm/sections.h> 21 #include <asm/pgtable.h> 22 23 enum pdt_access_type { 24 PDT_NONE, 25 PDT_PDC, 26 PDT_PAT_NEW, 27 PDT_PAT_OLD 28 }; 29 30 static enum pdt_access_type pdt_type; 31 32 /* global PDT status information */ 33 static struct pdc_mem_retinfo pdt_status; 34 35 #define MAX_PDT_TABLE_SIZE PAGE_SIZE 36 #define MAX_PDT_ENTRIES (MAX_PDT_TABLE_SIZE / sizeof(unsigned long)) 37 static unsigned long pdt_entry[MAX_PDT_ENTRIES] __page_aligned_bss; 38 39 40 /* report PDT entries via /proc/meminfo */ 41 void arch_report_meminfo(struct seq_file *m) 42 { 43 if (pdt_type == PDT_NONE) 44 return; 45 46 seq_printf(m, "PDT_max_entries: %7lu\n", 47 pdt_status.pdt_size); 48 seq_printf(m, "PDT_cur_entries: %7lu\n", 49 pdt_status.pdt_entries); 50 } 51 52 /* 53 * pdc_pdt_init() 54 * 55 * Initialize kernel PDT structures, read initial PDT table from firmware, 56 * report all current PDT entries and mark bad memory with memblock_reserve() 57 * to avoid that the kernel will use broken memory areas. 58 * 59 */ 60 void __init pdc_pdt_init(void) 61 { 62 int ret, i; 63 unsigned long entries; 64 struct pdc_mem_read_pdt pdt_read_ret; 65 66 if (is_pdc_pat()) { 67 struct pdc_pat_mem_retinfo pat_rinfo; 68 69 pdt_type = PDT_PAT_NEW; 70 ret = pdc_pat_mem_pdt_info(&pat_rinfo); 71 pdt_status.pdt_size = pat_rinfo.max_pdt_entries; 72 pdt_status.pdt_entries = pat_rinfo.current_pdt_entries; 73 pdt_status.pdt_status = 0; 74 pdt_status.first_dbe_loc = pat_rinfo.first_dbe_loc; 75 pdt_status.good_mem = pat_rinfo.good_mem; 76 } else { 77 pdt_type = PDT_PDC; 78 ret = pdc_mem_pdt_info(&pdt_status); 79 } 80 81 if (ret != PDC_OK) { 82 pdt_type = PDT_NONE; 83 pr_info("PDT: Firmware does not provide any page deallocation" 84 " information.\n"); 85 return; 86 } 87 88 entries = pdt_status.pdt_entries; 89 WARN_ON(entries > MAX_PDT_ENTRIES); 90 91 pr_info("PDT: size %lu, entries %lu, status %lu, dbe_loc 0x%lx," 92 " good_mem %lu\n", 93 pdt_status.pdt_size, pdt_status.pdt_entries, 94 pdt_status.pdt_status, pdt_status.first_dbe_loc, 95 pdt_status.good_mem); 96 97 if (entries == 0) { 98 pr_info("PDT: Firmware reports all memory OK.\n"); 99 return; 100 } 101 102 if (pdt_status.first_dbe_loc && 103 pdt_status.first_dbe_loc <= __pa((unsigned long)&_end)) 104 pr_crit("CRITICAL: Bad memory inside kernel image memory area!\n"); 105 106 pr_warn("PDT: Firmware reports %lu entries of faulty memory:\n", 107 entries); 108 109 if (pdt_type == PDT_PDC) 110 ret = pdc_mem_pdt_read_entries(&pdt_read_ret, pdt_entry); 111 else { 112 #ifdef CONFIG_64BIT 113 struct pdc_pat_mem_read_pd_retinfo pat_pret; 114 115 /* try old obsolete PAT firmware function first */ 116 pdt_type = PDT_PAT_OLD; 117 ret = pdc_pat_mem_read_cell_pdt(&pat_pret, pdt_entry, 118 MAX_PDT_ENTRIES); 119 if (ret != PDC_OK) { 120 pdt_type = PDT_PAT_NEW; 121 ret = pdc_pat_mem_read_pd_pdt(&pat_pret, pdt_entry, 122 MAX_PDT_TABLE_SIZE, 0); 123 } 124 #else 125 ret = PDC_BAD_PROC; 126 #endif 127 } 128 129 if (ret != PDC_OK) { 130 pdt_type = PDT_NONE; 131 pr_debug("PDT type %d, retval = %d\n", pdt_type, ret); 132 return; 133 } 134 135 for (i = 0; i < pdt_status.pdt_entries; i++) { 136 struct pdc_pat_mem_phys_mem_location loc; 137 138 /* get DIMM slot number */ 139 loc.dimm_slot = 0xff; 140 #ifdef CONFIG_64BIT 141 pdc_pat_mem_get_dimm_phys_location(&loc, pdt_entry[i]); 142 #endif 143 144 pr_warn("PDT: BAD PAGE #%d at 0x%08lx, " 145 "DIMM slot %02x (error_type = %lu)\n", 146 i, 147 pdt_entry[i] & PAGE_MASK, 148 loc.dimm_slot, 149 pdt_entry[i] & 1); 150 151 /* mark memory page bad */ 152 memblock_reserve(pdt_entry[i] & PAGE_MASK, PAGE_SIZE); 153 } 154 } 155