]>
Commit | Line | Data |
---|---|---|
c9c2877d HD |
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 | ||
f520e552 HD |
115 | /* try old obsolete PAT firmware function first */ |
116 | pdt_type = PDT_PAT_OLD; | |
c9c2877d HD |
117 | ret = pdc_pat_mem_read_cell_pdt(&pat_pret, pdt_entry, |
118 | MAX_PDT_ENTRIES); | |
119 | if (ret != PDC_OK) { | |
f520e552 | 120 | pdt_type = PDT_PAT_NEW; |
c9c2877d HD |
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++) { | |
c46bafc4 HD |
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); | |
c9c2877d HD |
150 | |
151 | /* mark memory page bad */ | |
152 | memblock_reserve(pdt_entry[i] & PAGE_MASK, PAGE_SIZE); | |
153 | } | |
154 | } |