Commit | Line | Data |
---|---|---|
d4dc89d0 QZ |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Driver for Intel(R) 10nm server memory controller. | |
4 | * Copyright (c) 2019, Intel Corporation. | |
5 | * | |
6 | */ | |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <asm/cpu_device_id.h> | |
10 | #include <asm/intel-family.h> | |
11 | #include <asm/mce.h> | |
12 | #include "edac_module.h" | |
13 | #include "skx_common.h" | |
14 | ||
15 | #define I10NM_REVISION "v0.0.3" | |
16 | #define EDAC_MOD_STR "i10nm_edac" | |
17 | ||
18 | /* Debug macros */ | |
19 | #define i10nm_printk(level, fmt, arg...) \ | |
20 | edac_printk(level, "i10nm", fmt, ##arg) | |
21 | ||
22 | #define I10NM_GET_SCK_BAR(d, reg) \ | |
23 | pci_read_config_dword((d)->uracu, 0xd0, &(reg)) | |
24 | #define I10NM_GET_IMC_BAR(d, i, reg) \ | |
25 | pci_read_config_dword((d)->uracu, 0xd8 + (i) * 4, &(reg)) | |
26 | #define I10NM_GET_DIMMMTR(m, i, j) \ | |
27 | (*(u32 *)((m)->mbase + 0x2080c + (i) * 0x4000 + (j) * 4)) | |
28 | #define I10NM_GET_MCDDRTCFG(m, i, j) \ | |
29 | (*(u32 *)((m)->mbase + 0x20970 + (i) * 0x4000 + (j) * 4)) | |
30 | ||
31 | #define I10NM_GET_SCK_MMIO_BASE(reg) (GET_BITFIELD(reg, 0, 28) << 23) | |
32 | #define I10NM_GET_IMC_MMIO_OFFSET(reg) (GET_BITFIELD(reg, 0, 10) << 12) | |
33 | #define I10NM_GET_IMC_MMIO_SIZE(reg) ((GET_BITFIELD(reg, 13, 23) - \ | |
34 | GET_BITFIELD(reg, 0, 10) + 1) << 12) | |
35 | ||
36 | static struct list_head *i10nm_edac_list; | |
37 | ||
38 | static struct pci_dev *pci_get_dev_wrapper(int dom, unsigned int bus, | |
39 | unsigned int dev, unsigned int fun) | |
40 | { | |
41 | struct pci_dev *pdev; | |
42 | ||
43 | pdev = pci_get_domain_bus_and_slot(dom, bus, PCI_DEVFN(dev, fun)); | |
44 | if (!pdev) { | |
45 | edac_dbg(2, "No device %02x:%02x.%x\n", | |
46 | bus, dev, fun); | |
47 | return NULL; | |
48 | } | |
49 | ||
50 | if (unlikely(pci_enable_device(pdev) < 0)) { | |
51 | edac_dbg(2, "Failed to enable device %02x:%02x.%x\n", | |
52 | bus, dev, fun); | |
53 | return NULL; | |
54 | } | |
55 | ||
56 | pci_dev_get(pdev); | |
57 | ||
58 | return pdev; | |
59 | } | |
60 | ||
61 | static int i10nm_get_all_munits(void) | |
62 | { | |
63 | struct pci_dev *mdev; | |
64 | void __iomem *mbase; | |
65 | unsigned long size; | |
66 | struct skx_dev *d; | |
67 | int i, j = 0; | |
68 | u32 reg, off; | |
69 | u64 base; | |
70 | ||
71 | list_for_each_entry(d, i10nm_edac_list, list) { | |
72 | d->util_all = pci_get_dev_wrapper(d->seg, d->bus[1], 29, 1); | |
73 | if (!d->util_all) | |
74 | return -ENODEV; | |
75 | ||
76 | d->uracu = pci_get_dev_wrapper(d->seg, d->bus[0], 0, 1); | |
77 | if (!d->uracu) | |
78 | return -ENODEV; | |
79 | ||
80 | if (I10NM_GET_SCK_BAR(d, reg)) { | |
81 | i10nm_printk(KERN_ERR, "Failed to socket bar\n"); | |
82 | return -ENODEV; | |
83 | } | |
84 | ||
85 | base = I10NM_GET_SCK_MMIO_BASE(reg); | |
86 | edac_dbg(2, "socket%d mmio base 0x%llx (reg 0x%x)\n", | |
87 | j++, base, reg); | |
88 | ||
89 | for (i = 0; i < I10NM_NUM_IMC; i++) { | |
90 | mdev = pci_get_dev_wrapper(d->seg, d->bus[0], | |
91 | 12 + i, 0); | |
92 | if (i == 0 && !mdev) { | |
93 | i10nm_printk(KERN_ERR, "No IMC found\n"); | |
94 | return -ENODEV; | |
95 | } | |
96 | if (!mdev) | |
97 | continue; | |
98 | ||
99 | d->imc[i].mdev = mdev; | |
100 | ||
101 | if (I10NM_GET_IMC_BAR(d, i, reg)) { | |
102 | i10nm_printk(KERN_ERR, "Failed to get mc bar\n"); | |
103 | return -ENODEV; | |
104 | } | |
105 | ||
106 | off = I10NM_GET_IMC_MMIO_OFFSET(reg); | |
107 | size = I10NM_GET_IMC_MMIO_SIZE(reg); | |
108 | edac_dbg(2, "mc%d mmio base 0x%llx size 0x%lx (reg 0x%x)\n", | |
109 | i, base + off, size, reg); | |
110 | ||
111 | mbase = ioremap(base + off, size); | |
112 | if (!mbase) { | |
113 | i10nm_printk(KERN_ERR, "Failed to ioremap 0x%llx\n", | |
114 | base + off); | |
115 | return -ENODEV; | |
116 | } | |
117 | ||
118 | d->imc[i].mbase = mbase; | |
119 | } | |
120 | } | |
121 | ||
122 | return 0; | |
123 | } | |
124 | ||
125 | static const struct x86_cpu_id i10nm_cpuids[] = { | |
126 | { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_TREMONT_X, 0, 0 }, | |
5c5d3ac2 QZ |
127 | { X86_VENDOR_INTEL, 6, INTEL_FAM6_ICELAKE_X, 0, 0 }, |
128 | { X86_VENDOR_INTEL, 6, INTEL_FAM6_ICELAKE_XEON_D, 0, 0 }, | |
d4dc89d0 QZ |
129 | { } |
130 | }; | |
131 | MODULE_DEVICE_TABLE(x86cpu, i10nm_cpuids); | |
132 | ||
133 | static bool i10nm_check_ecc(struct skx_imc *imc, int chan) | |
134 | { | |
135 | u32 mcmtr; | |
136 | ||
137 | mcmtr = *(u32 *)(imc->mbase + 0x20ef8 + chan * 0x4000); | |
138 | edac_dbg(1, "ch%d mcmtr reg %x\n", chan, mcmtr); | |
139 | ||
140 | return !!GET_BITFIELD(mcmtr, 2, 2); | |
141 | } | |
142 | ||
143 | static int i10nm_get_dimm_config(struct mem_ctl_info *mci) | |
144 | { | |
145 | struct skx_pvt *pvt = mci->pvt_info; | |
146 | struct skx_imc *imc = pvt->imc; | |
147 | struct dimm_info *dimm; | |
148 | u32 mtr, mcddrtcfg; | |
149 | int i, j, ndimms; | |
150 | ||
151 | for (i = 0; i < I10NM_NUM_CHANNELS; i++) { | |
152 | if (!imc->mbase) | |
153 | continue; | |
154 | ||
155 | ndimms = 0; | |
156 | for (j = 0; j < I10NM_NUM_DIMMS; j++) { | |
157 | dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, | |
158 | mci->n_layers, i, j, 0); | |
159 | mtr = I10NM_GET_DIMMMTR(imc, i, j); | |
160 | mcddrtcfg = I10NM_GET_MCDDRTCFG(imc, i, j); | |
161 | edac_dbg(1, "dimmmtr 0x%x mcddrtcfg 0x%x (mc%d ch%d dimm%d)\n", | |
162 | mtr, mcddrtcfg, imc->mc, i, j); | |
163 | ||
164 | if (IS_DIMM_PRESENT(mtr)) | |
165 | ndimms += skx_get_dimm_info(mtr, 0, dimm, | |
166 | imc, i, j); | |
167 | else if (IS_NVDIMM_PRESENT(mcddrtcfg, j)) | |
168 | ndimms += skx_get_nvdimm_info(dimm, imc, i, j, | |
169 | EDAC_MOD_STR); | |
170 | } | |
c4a1dd9e QZ |
171 | if (ndimms && !i10nm_check_ecc(imc, i)) { |
172 | i10nm_printk(KERN_ERR, "ECC is disabled on imc %d channel %d\n", | |
173 | imc->mc, i); | |
d4dc89d0 QZ |
174 | return -ENODEV; |
175 | } | |
176 | } | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | static struct notifier_block i10nm_mce_dec = { | |
182 | .notifier_call = skx_mce_check_error, | |
183 | .priority = MCE_PRIO_EDAC, | |
184 | }; | |
185 | ||
fe783516 QZ |
186 | #ifdef CONFIG_EDAC_DEBUG |
187 | /* | |
188 | * Debug feature. | |
189 | * Exercise the address decode logic by writing an address to | |
190 | * /sys/kernel/debug/edac/i10nm_test/addr. | |
191 | */ | |
192 | static struct dentry *i10nm_test; | |
193 | ||
194 | static int debugfs_u64_set(void *data, u64 val) | |
195 | { | |
196 | struct mce m; | |
197 | ||
198 | pr_warn_once("Fake error to 0x%llx injected via debugfs\n", val); | |
199 | ||
200 | memset(&m, 0, sizeof(m)); | |
201 | /* ADDRV + MemRd + Unknown channel */ | |
202 | m.status = MCI_STATUS_ADDRV + 0x90; | |
203 | /* One corrected error */ | |
204 | m.status |= BIT_ULL(MCI_STATUS_CEC_SHIFT); | |
205 | m.addr = val; | |
206 | skx_mce_check_error(NULL, 0, &m); | |
207 | ||
208 | return 0; | |
209 | } | |
210 | DEFINE_SIMPLE_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n"); | |
211 | ||
212 | static void setup_i10nm_debug(void) | |
213 | { | |
214 | i10nm_test = edac_debugfs_create_dir("i10nm_test"); | |
215 | if (!i10nm_test) | |
216 | return; | |
217 | ||
218 | if (!edac_debugfs_create_file("addr", 0200, i10nm_test, | |
219 | NULL, &fops_u64_wo)) { | |
220 | debugfs_remove(i10nm_test); | |
221 | i10nm_test = NULL; | |
222 | } | |
223 | } | |
224 | ||
225 | static void teardown_i10nm_debug(void) | |
226 | { | |
227 | debugfs_remove_recursive(i10nm_test); | |
228 | } | |
229 | #else | |
230 | static inline void setup_i10nm_debug(void) {} | |
231 | static inline void teardown_i10nm_debug(void) {} | |
232 | #endif /*CONFIG_EDAC_DEBUG*/ | |
233 | ||
d4dc89d0 QZ |
234 | static int __init i10nm_init(void) |
235 | { | |
236 | u8 mc = 0, src_id = 0, node_id = 0; | |
237 | const struct x86_cpu_id *id; | |
238 | const char *owner; | |
239 | struct skx_dev *d; | |
240 | int rc, i, off[3] = {0xd0, 0xc8, 0xcc}; | |
241 | u64 tolm, tohm; | |
242 | ||
243 | edac_dbg(2, "\n"); | |
244 | ||
245 | owner = edac_get_owner(); | |
246 | if (owner && strncmp(owner, EDAC_MOD_STR, sizeof(EDAC_MOD_STR))) | |
247 | return -EBUSY; | |
248 | ||
249 | id = x86_match_cpu(i10nm_cpuids); | |
250 | if (!id) | |
251 | return -ENODEV; | |
252 | ||
253 | rc = skx_get_hi_lo(0x09a2, off, &tolm, &tohm); | |
254 | if (rc) | |
255 | return rc; | |
256 | ||
257 | rc = skx_get_all_bus_mappings(0x3452, 0xcc, I10NM, &i10nm_edac_list); | |
258 | if (rc < 0) | |
259 | goto fail; | |
260 | if (rc == 0) { | |
261 | i10nm_printk(KERN_ERR, "No memory controllers found\n"); | |
262 | return -ENODEV; | |
263 | } | |
264 | ||
265 | rc = i10nm_get_all_munits(); | |
266 | if (rc < 0) | |
267 | goto fail; | |
268 | ||
269 | list_for_each_entry(d, i10nm_edac_list, list) { | |
1dc78f1f | 270 | rc = skx_get_src_id(d, 0xf8, &src_id); |
d4dc89d0 QZ |
271 | if (rc < 0) |
272 | goto fail; | |
273 | ||
274 | rc = skx_get_node_id(d, &node_id); | |
275 | if (rc < 0) | |
276 | goto fail; | |
277 | ||
278 | edac_dbg(2, "src_id = %d node_id = %d\n", src_id, node_id); | |
279 | for (i = 0; i < I10NM_NUM_IMC; i++) { | |
280 | if (!d->imc[i].mdev) | |
281 | continue; | |
282 | ||
283 | d->imc[i].mc = mc++; | |
284 | d->imc[i].lmc = i; | |
285 | d->imc[i].src_id = src_id; | |
286 | d->imc[i].node_id = node_id; | |
287 | ||
288 | rc = skx_register_mci(&d->imc[i], d->imc[i].mdev, | |
289 | "Intel_10nm Socket", EDAC_MOD_STR, | |
290 | i10nm_get_dimm_config); | |
291 | if (rc < 0) | |
292 | goto fail; | |
293 | } | |
294 | } | |
295 | ||
296 | rc = skx_adxl_get(); | |
297 | if (rc) | |
298 | goto fail; | |
299 | ||
300 | opstate_init(); | |
301 | mce_register_decode_chain(&i10nm_mce_dec); | |
fe783516 | 302 | setup_i10nm_debug(); |
d4dc89d0 QZ |
303 | |
304 | i10nm_printk(KERN_INFO, "%s\n", I10NM_REVISION); | |
305 | ||
306 | return 0; | |
307 | fail: | |
308 | skx_remove(); | |
309 | return rc; | |
310 | } | |
311 | ||
312 | static void __exit i10nm_exit(void) | |
313 | { | |
314 | edac_dbg(2, "\n"); | |
fe783516 | 315 | teardown_i10nm_debug(); |
d4dc89d0 QZ |
316 | mce_unregister_decode_chain(&i10nm_mce_dec); |
317 | skx_adxl_put(); | |
318 | skx_remove(); | |
319 | } | |
320 | ||
321 | module_init(i10nm_init); | |
322 | module_exit(i10nm_exit); | |
323 | ||
324 | MODULE_LICENSE("GPL v2"); | |
325 | MODULE_DESCRIPTION("MC Driver for Intel 10nm server processors"); |