1 // SPDX-License-Identifier: GPL-2.0 1 2 /* 3 * Page Deallocation Table (PDT) support 4 * 5 * The Page Deallocation Table (PDT) is mai 6 * list of memory addresses in which memory 7 * The list contains both single-bit (corre 8 * (uncorrectable) errors. 9 * 10 * Copyright 2017 by Helge Deller <deller@g 11 * 12 * possible future enhancements: 13 * - add userspace interface via procfs or 14 */ 15 16 #include <linux/memblock.h> 17 #include <linux/seq_file.h> 18 #include <linux/kthread.h> 19 #include <linux/proc_fs.h> 20 #include <linux/initrd.h> 21 #include <linux/pgtable.h> 22 #include <linux/mm.h> 23 24 #include <asm/pdc.h> 25 #include <asm/pdcpat.h> 26 #include <asm/sections.h> 27 #include <asm/pgtable.h> 28 29 enum pdt_access_type { 30 PDT_NONE, 31 PDT_PDC, 32 PDT_PAT_NEW, 33 PDT_PAT_CELL 34 }; 35 36 static enum pdt_access_type pdt_type; 37 38 /* PDT poll interval: 1 minute if errors, 5 mi 39 #define PDT_POLL_INTERVAL_DEFAULT (5*60* 40 #define PDT_POLL_INTERVAL_SHORT (1*60* 41 static unsigned long pdt_poll_interval = PDT_P 42 43 /* global PDT status information */ 44 static struct pdc_mem_retinfo pdt_status; 45 46 #define MAX_PDT_TABLE_SIZE PAGE_SIZE 47 #define MAX_PDT_ENTRIES (MAX_PDT_TABLE 48 static unsigned long pdt_entry[MAX_PDT_ENTRIES 49 50 /* 51 * Constants for the pdt_entry format: 52 * A pdt_entry holds the physical address in b 53 * reserved, bit 62 is the perm bit and bit 63 54 * The perm bit indicates whether the error ha 55 * error (value of 1) or has not been verified 56 * of 0). The error_type bit indicates whether 57 * (value of 1) or a multiple bit error. 58 * On non-PAT machines phys_addr is encoded in 59 * 63. Those machines don't provide the perm b 60 */ 61 62 #define PDT_ADDR_PHYS_MASK (pdt_type != P 63 #define PDT_ADDR_PERM_ERR (pdt_type != P 64 #define PDT_ADDR_SINGLE_ERR 1UL 65 66 /* report PDT entries via /proc/meminfo */ 67 void arch_report_meminfo(struct seq_file *m) 68 { 69 if (pdt_type == PDT_NONE) 70 return; 71 72 seq_printf(m, "PDT_max_entries: %7lu\n 73 pdt_status.pdt_size); 74 seq_printf(m, "PDT_cur_entries: %7lu\n 75 pdt_status.pdt_entries 76 } 77 78 static int get_info_pat_new(void) 79 { 80 struct pdc_pat_mem_retinfo pat_rinfo; 81 int ret; 82 83 /* newer PAT machines like C8000 repor 84 if (is_pdc_pat()) 85 ret = pdc_pat_mem_pdt_info(&pa 86 else 87 return PDC_BAD_PROC; 88 89 pdt_status.pdt_size = pat_rinfo.max_pd 90 pdt_status.pdt_entries = pat_rinfo.cur 91 pdt_status.pdt_status = 0; 92 pdt_status.first_dbe_loc = pat_rinfo.f 93 pdt_status.good_mem = pat_rinfo.good_m 94 95 return ret; 96 } 97 98 static int get_info_pat_cell(void) 99 { 100 struct pdc_pat_mem_cell_pdt_retinfo ce 101 int ret; 102 103 /* older PAT machines like rp5470 repo 104 if (is_pdc_pat()) 105 ret = pdc_pat_mem_pdt_cell_inf 106 else 107 return PDC_BAD_PROC; 108 109 pdt_status.pdt_size = cell_rinfo.max_p 110 pdt_status.pdt_entries = cell_rinfo.cu 111 pdt_status.pdt_status = 0; 112 pdt_status.first_dbe_loc = cell_rinfo. 113 pdt_status.good_mem = cell_rinfo.good_ 114 115 return ret; 116 } 117 118 static void report_mem_err(unsigned long pde) 119 { 120 struct pdc_pat_mem_phys_mem_location l 121 unsigned long addr; 122 char dimm_txt[32]; 123 124 addr = pde & PDT_ADDR_PHYS_MASK; 125 126 /* show DIMM slot description on PAT m 127 if (is_pdc_pat()) { 128 pdc_pat_mem_get_dimm_phys_loca 129 sprintf(dimm_txt, "DIMM slot % 130 } else 131 dimm_txt[0] = 0; 132 133 pr_warn("PDT: BAD MEMORY at 0x%08lx, % 134 addr, dimm_txt, 135 pde & PDT_ADDR_PERM_ERR ? "per 136 pde & PDT_ADDR_SINGLE_ERR ? "s 137 } 138 139 140 /* 141 * pdc_pdt_init() 142 * 143 * Initialize kernel PDT structures, read init 144 * report all current PDT entries and mark bad 145 * to avoid that the kernel will use broken me 146 * 147 */ 148 void __init pdc_pdt_init(void) 149 { 150 int ret, i; 151 unsigned long entries; 152 struct pdc_mem_read_pdt pdt_read_ret; 153 154 pdt_type = PDT_PAT_NEW; 155 ret = get_info_pat_new(); 156 157 if (ret != PDC_OK) { 158 pdt_type = PDT_PAT_CELL; 159 ret = get_info_pat_cell(); 160 } 161 162 if (ret != PDC_OK) { 163 pdt_type = PDT_PDC; 164 /* non-PAT machines provide th 165 ret = pdc_mem_pdt_info(&pdt_st 166 } 167 168 if (ret != PDC_OK) { 169 pdt_type = PDT_NONE; 170 pr_info("PDT: Firmware does no 171 " information.\n"); 172 return; 173 } 174 175 entries = pdt_status.pdt_entries; 176 if (WARN_ON(entries > MAX_PDT_ENTRIES) 177 entries = pdt_status.pdt_entri 178 179 pr_info("PDT: type %s, size %lu, entri 180 " good_mem %lu MB\n", 181 pdt_type == PDT_PDC ? 182 pdt_type == PDT_PAT_CE 183 184 pdt_status.pdt_size, p 185 pdt_status.pdt_status, 186 pdt_status.good_mem / 187 188 if (entries == 0) { 189 pr_info("PDT: Firmware reports 190 return; 191 } 192 193 if (pdt_status.first_dbe_loc && 194 pdt_status.first_dbe_loc <= __ 195 pr_crit("CRITICAL: Bad memory 196 197 pr_warn("PDT: Firmware reports %lu ent 198 entries); 199 200 if (pdt_type == PDT_PDC) 201 ret = pdc_mem_pdt_read_entries 202 else { 203 #ifdef CONFIG_64BIT 204 struct pdc_pat_mem_read_pd_ret 205 206 if (pdt_type == PDT_PAT_CELL) 207 ret = pdc_pat_mem_read 208 MAX_PDT_ENTRIE 209 else 210 ret = pdc_pat_mem_read 211 MAX_PDT_TABLE_ 212 #else 213 ret = PDC_BAD_PROC; 214 #endif 215 } 216 217 if (ret != PDC_OK) { 218 pdt_type = PDT_NONE; 219 pr_warn("PDT: Get PDT entries 220 return; 221 } 222 223 for (i = 0; i < pdt_status.pdt_entries 224 unsigned long addr; 225 226 report_mem_err(pdt_entry[i]); 227 228 addr = pdt_entry[i] & PDT_ADDR 229 if (IS_ENABLED(CONFIG_BLK_DEV_ 230 addr >= initrd_start & 231 pr_crit("CRITICAL: ini 232 "due to bad me 233 234 /* mark memory page bad */ 235 memblock_reserve(pdt_entry[i] 236 num_poisoned_pages_inc(addr >> 237 } 238 } 239 240 241 /* 242 * This is the PDT kernel thread main loop. 243 */ 244 245 static int pdt_mainloop(void *unused) 246 { 247 struct pdc_mem_read_pdt pdt_read_ret; 248 struct pdc_pat_mem_read_pd_retinfo pat 249 unsigned long old_num_entries; 250 unsigned long *bad_mem_ptr; 251 int num, ret; 252 253 for (;;) { 254 set_current_state(TASK_INTERRU 255 256 old_num_entries = pdt_status.p 257 258 schedule_timeout(pdt_poll_inte 259 if (kthread_should_stop()) 260 break; 261 262 /* Do we have new PDT entries? 263 switch (pdt_type) { 264 case PDT_PAT_NEW: 265 ret = get_info_pat_new 266 break; 267 case PDT_PAT_CELL: 268 ret = get_info_pat_cel 269 break; 270 default: 271 ret = pdc_mem_pdt_info 272 break; 273 } 274 275 if (ret != PDC_OK) { 276 pr_warn("PDT: unexpect 277 return -EINVAL; 278 } 279 280 /* if no new PDT entries, just 281 num = pdt_status.pdt_entries - 282 if (num <= 0) 283 continue; 284 285 /* decrease poll interval in c 286 if (pdt_status.pdt_entries && 287 pdt_poll_interval == P 288 pdt_poll_interval = PD 289 290 /* limit entries to get */ 291 if (num > MAX_PDT_ENTRIES) { 292 num = MAX_PDT_ENTRIES; 293 pdt_status.pdt_entries 294 } 295 296 /* get new entries */ 297 switch (pdt_type) { 298 #ifdef CONFIG_64BIT 299 case PDT_PAT_CELL: 300 if (pdt_status.pdt_ent 301 pr_crit("PDT: 302 return -ENOMEM 303 } 304 ret = pdc_pat_mem_read 305 MAX_PDT_ENTRIE 306 bad_mem_ptr = &pdt_ent 307 break; 308 case PDT_PAT_NEW: 309 ret = pdc_pat_mem_read 310 pdt_entry, 311 num * sizeof(u 312 old_num_entrie 313 bad_mem_ptr = &pdt_ent 314 break; 315 #endif 316 default: 317 ret = pdc_mem_pdt_read 318 pdt_entry); 319 bad_mem_ptr = &pdt_ent 320 break; 321 } 322 323 /* report and mark memory brok 324 while (num--) { 325 unsigned long pde = *b 326 327 report_mem_err(pde); 328 329 #ifdef CONFIG_MEMORY_FAILURE 330 if ((pde & PDT_ADDR_PE 331 ((pde & PDT_ADDR_S 332 memory_failure 333 else 334 soft_offline_p 335 #else 336 pr_crit("PDT: memory e 337 "Rebuild kerne 338 "for real hand 339 pde & PDT_ADDR 340 #endif 341 342 } 343 } 344 345 return 0; 346 } 347 348 349 static int __init pdt_initcall(void) 350 { 351 struct task_struct *kpdtd_task; 352 353 if (pdt_type == PDT_NONE) 354 return -ENODEV; 355 356 kpdtd_task = kthread_run(pdt_mainloop, 357 358 return PTR_ERR_OR_ZERO(kpdtd_task); 359 } 360 361 late_initcall(pdt_initcall); 362
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.