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 }, | |
127 | { } | |
128 | }; | |
129 | MODULE_DEVICE_TABLE(x86cpu, i10nm_cpuids); | |
130 | ||
131 | static bool i10nm_check_ecc(struct skx_imc *imc, int chan) | |
132 | { | |
133 | u32 mcmtr; | |
134 | ||
135 | mcmtr = *(u32 *)(imc->mbase + 0x20ef8 + chan * 0x4000); | |
136 | edac_dbg(1, "ch%d mcmtr reg %x\n", chan, mcmtr); | |
137 | ||
138 | return !!GET_BITFIELD(mcmtr, 2, 2); | |
139 | } | |
140 | ||
141 | static int i10nm_get_dimm_config(struct mem_ctl_info *mci) | |
142 | { | |
143 | struct skx_pvt *pvt = mci->pvt_info; | |
144 | struct skx_imc *imc = pvt->imc; | |
145 | struct dimm_info *dimm; | |
146 | u32 mtr, mcddrtcfg; | |
147 | int i, j, ndimms; | |
148 | ||
149 | for (i = 0; i < I10NM_NUM_CHANNELS; i++) { | |
150 | if (!imc->mbase) | |
151 | continue; | |
152 | ||
153 | ndimms = 0; | |
154 | for (j = 0; j < I10NM_NUM_DIMMS; j++) { | |
155 | dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, | |
156 | mci->n_layers, i, j, 0); | |
157 | mtr = I10NM_GET_DIMMMTR(imc, i, j); | |
158 | mcddrtcfg = I10NM_GET_MCDDRTCFG(imc, i, j); | |
159 | edac_dbg(1, "dimmmtr 0x%x mcddrtcfg 0x%x (mc%d ch%d dimm%d)\n", | |
160 | mtr, mcddrtcfg, imc->mc, i, j); | |
161 | ||
162 | if (IS_DIMM_PRESENT(mtr)) | |
163 | ndimms += skx_get_dimm_info(mtr, 0, dimm, | |
164 | imc, i, j); | |
165 | else if (IS_NVDIMM_PRESENT(mcddrtcfg, j)) | |
166 | ndimms += skx_get_nvdimm_info(dimm, imc, i, j, | |
167 | EDAC_MOD_STR); | |
168 | } | |
169 | if (ndimms && !i10nm_check_ecc(imc, 0)) { | |
170 | i10nm_printk(KERN_ERR, "ECC is disabled on imc %d\n", | |
171 | imc->mc); | |
172 | return -ENODEV; | |
173 | } | |
174 | } | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | static struct notifier_block i10nm_mce_dec = { | |
180 | .notifier_call = skx_mce_check_error, | |
181 | .priority = MCE_PRIO_EDAC, | |
182 | }; | |
183 | ||
fe783516 QZ |
184 | #ifdef CONFIG_EDAC_DEBUG |
185 | /* | |
186 | * Debug feature. | |
187 | * Exercise the address decode logic by writing an address to | |
188 | * /sys/kernel/debug/edac/i10nm_test/addr. | |
189 | */ | |
190 | static struct dentry *i10nm_test; | |
191 | ||
192 | static int debugfs_u64_set(void *data, u64 val) | |
193 | { | |
194 | struct mce m; | |
195 | ||
196 | pr_warn_once("Fake error to 0x%llx injected via debugfs\n", val); | |
197 | ||
198 | memset(&m, 0, sizeof(m)); | |
199 | /* ADDRV + MemRd + Unknown channel */ | |
200 | m.status = MCI_STATUS_ADDRV + 0x90; | |
201 | /* One corrected error */ | |
202 | m.status |= BIT_ULL(MCI_STATUS_CEC_SHIFT); | |
203 | m.addr = val; | |
204 | skx_mce_check_error(NULL, 0, &m); | |
205 | ||
206 | return 0; | |
207 | } | |
208 | DEFINE_SIMPLE_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n"); | |
209 | ||
210 | static void setup_i10nm_debug(void) | |
211 | { | |
212 | i10nm_test = edac_debugfs_create_dir("i10nm_test"); | |
213 | if (!i10nm_test) | |
214 | return; | |
215 | ||
216 | if (!edac_debugfs_create_file("addr", 0200, i10nm_test, | |
217 | NULL, &fops_u64_wo)) { | |
218 | debugfs_remove(i10nm_test); | |
219 | i10nm_test = NULL; | |
220 | } | |
221 | } | |
222 | ||
223 | static void teardown_i10nm_debug(void) | |
224 | { | |
225 | debugfs_remove_recursive(i10nm_test); | |
226 | } | |
227 | #else | |
228 | static inline void setup_i10nm_debug(void) {} | |
229 | static inline void teardown_i10nm_debug(void) {} | |
230 | #endif /*CONFIG_EDAC_DEBUG*/ | |
231 | ||
d4dc89d0 QZ |
232 | static int __init i10nm_init(void) |
233 | { | |
234 | u8 mc = 0, src_id = 0, node_id = 0; | |
235 | const struct x86_cpu_id *id; | |
236 | const char *owner; | |
237 | struct skx_dev *d; | |
238 | int rc, i, off[3] = {0xd0, 0xc8, 0xcc}; | |
239 | u64 tolm, tohm; | |
240 | ||
241 | edac_dbg(2, "\n"); | |
242 | ||
243 | owner = edac_get_owner(); | |
244 | if (owner && strncmp(owner, EDAC_MOD_STR, sizeof(EDAC_MOD_STR))) | |
245 | return -EBUSY; | |
246 | ||
247 | id = x86_match_cpu(i10nm_cpuids); | |
248 | if (!id) | |
249 | return -ENODEV; | |
250 | ||
251 | rc = skx_get_hi_lo(0x09a2, off, &tolm, &tohm); | |
252 | if (rc) | |
253 | return rc; | |
254 | ||
255 | rc = skx_get_all_bus_mappings(0x3452, 0xcc, I10NM, &i10nm_edac_list); | |
256 | if (rc < 0) | |
257 | goto fail; | |
258 | if (rc == 0) { | |
259 | i10nm_printk(KERN_ERR, "No memory controllers found\n"); | |
260 | return -ENODEV; | |
261 | } | |
262 | ||
263 | rc = i10nm_get_all_munits(); | |
264 | if (rc < 0) | |
265 | goto fail; | |
266 | ||
267 | list_for_each_entry(d, i10nm_edac_list, list) { | |
268 | rc = skx_get_src_id(d, &src_id); | |
269 | if (rc < 0) | |
270 | goto fail; | |
271 | ||
272 | rc = skx_get_node_id(d, &node_id); | |
273 | if (rc < 0) | |
274 | goto fail; | |
275 | ||
276 | edac_dbg(2, "src_id = %d node_id = %d\n", src_id, node_id); | |
277 | for (i = 0; i < I10NM_NUM_IMC; i++) { | |
278 | if (!d->imc[i].mdev) | |
279 | continue; | |
280 | ||
281 | d->imc[i].mc = mc++; | |
282 | d->imc[i].lmc = i; | |
283 | d->imc[i].src_id = src_id; | |
284 | d->imc[i].node_id = node_id; | |
285 | ||
286 | rc = skx_register_mci(&d->imc[i], d->imc[i].mdev, | |
287 | "Intel_10nm Socket", EDAC_MOD_STR, | |
288 | i10nm_get_dimm_config); | |
289 | if (rc < 0) | |
290 | goto fail; | |
291 | } | |
292 | } | |
293 | ||
294 | rc = skx_adxl_get(); | |
295 | if (rc) | |
296 | goto fail; | |
297 | ||
298 | opstate_init(); | |
299 | mce_register_decode_chain(&i10nm_mce_dec); | |
fe783516 | 300 | setup_i10nm_debug(); |
d4dc89d0 QZ |
301 | |
302 | i10nm_printk(KERN_INFO, "%s\n", I10NM_REVISION); | |
303 | ||
304 | return 0; | |
305 | fail: | |
306 | skx_remove(); | |
307 | return rc; | |
308 | } | |
309 | ||
310 | static void __exit i10nm_exit(void) | |
311 | { | |
312 | edac_dbg(2, "\n"); | |
fe783516 | 313 | teardown_i10nm_debug(); |
d4dc89d0 QZ |
314 | mce_unregister_decode_chain(&i10nm_mce_dec); |
315 | skx_adxl_put(); | |
316 | skx_remove(); | |
317 | } | |
318 | ||
319 | module_init(i10nm_init); | |
320 | module_exit(i10nm_exit); | |
321 | ||
322 | MODULE_LICENSE("GPL v2"); | |
323 | MODULE_DESCRIPTION("MC Driver for Intel 10nm server processors"); |