1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Xilinx ZynqMP OCM ECC Driver |
4 | * |
5 | * Copyright (C) 2022 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/edac.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_platform.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | #include "edac_module.h" |
16 | |
17 | #define ZYNQMP_OCM_EDAC_MSG_SIZE 256 |
18 | |
19 | #define ZYNQMP_OCM_EDAC_STRING "zynqmp_ocm" |
20 | |
21 | /* Error/Interrupt registers */ |
22 | #define ERR_CTRL_OFST 0x0 |
23 | #define OCM_ISR_OFST 0x04 |
24 | #define OCM_IMR_OFST 0x08 |
25 | #define OCM_IEN_OFST 0x0C |
26 | #define OCM_IDS_OFST 0x10 |
27 | |
28 | /* ECC control register */ |
29 | #define ECC_CTRL_OFST 0x14 |
30 | |
31 | /* Correctable error info registers */ |
32 | #define CE_FFA_OFST 0x1C |
33 | #define CE_FFD0_OFST 0x20 |
34 | #define CE_FFD1_OFST 0x24 |
35 | #define CE_FFD2_OFST 0x28 |
36 | #define CE_FFD3_OFST 0x2C |
37 | #define CE_FFE_OFST 0x30 |
38 | |
39 | /* Uncorrectable error info registers */ |
40 | #define UE_FFA_OFST 0x34 |
41 | #define UE_FFD0_OFST 0x38 |
42 | #define UE_FFD1_OFST 0x3C |
43 | #define UE_FFD2_OFST 0x40 |
44 | #define UE_FFD3_OFST 0x44 |
45 | #define UE_FFE_OFST 0x48 |
46 | |
47 | /* ECC control register bit field definitions */ |
48 | #define ECC_CTRL_CLR_CE_ERR 0x40 |
49 | #define ECC_CTRL_CLR_UE_ERR 0x80 |
50 | |
51 | /* Fault injection data and count registers */ |
52 | #define OCM_FID0_OFST 0x4C |
53 | #define OCM_FID1_OFST 0x50 |
54 | #define OCM_FID2_OFST 0x54 |
55 | #define OCM_FID3_OFST 0x58 |
56 | #define OCM_FIC_OFST 0x74 |
57 | |
58 | #define UE_MAX_BITPOS_LOWER 31 |
59 | #define UE_MIN_BITPOS_UPPER 32 |
60 | #define UE_MAX_BITPOS_UPPER 63 |
61 | |
62 | /* Interrupt masks */ |
63 | #define OCM_CEINTR_MASK BIT(6) |
64 | #define OCM_UEINTR_MASK BIT(7) |
65 | #define OCM_ECC_ENABLE_MASK BIT(0) |
66 | |
67 | #define OCM_FICOUNT_MASK GENMASK(23, 0) |
68 | #define OCM_NUM_UE_BITPOS 2 |
69 | #define OCM_BASEVAL 0xFFFC0000 |
70 | #define EDAC_DEVICE "ZynqMP-OCM" |
71 | |
72 | /** |
73 | * struct ecc_error_info - ECC error log information |
74 | * @addr: Fault generated at this address |
75 | * @fault_lo: Generated fault data (lower 32-bit) |
76 | * @fault_hi: Generated fault data (upper 32-bit) |
77 | */ |
78 | struct ecc_error_info { |
79 | u32 addr; |
80 | u32 fault_lo; |
81 | u32 fault_hi; |
82 | }; |
83 | |
84 | /** |
85 | * struct ecc_status - ECC status information to report |
86 | * @ce_cnt: Correctable error count |
87 | * @ue_cnt: Uncorrectable error count |
88 | * @ceinfo: Correctable error log information |
89 | * @ueinfo: Uncorrectable error log information |
90 | */ |
91 | struct ecc_status { |
92 | u32 ce_cnt; |
93 | u32 ue_cnt; |
94 | struct ecc_error_info ceinfo; |
95 | struct ecc_error_info ueinfo; |
96 | }; |
97 | |
98 | /** |
99 | * struct edac_priv - OCM private instance data |
100 | * @baseaddr: Base address of the OCM |
101 | * @message: Buffer for framing the event specific info |
102 | * @stat: ECC status information |
103 | * @ce_cnt: Correctable Error count |
104 | * @ue_cnt: Uncorrectable Error count |
105 | * @debugfs_dir: Directory entry for debugfs |
106 | * @ce_bitpos: Bit position for Correctable Error |
107 | * @ue_bitpos: Array to store UnCorrectable Error bit positions |
108 | * @fault_injection_cnt: Fault Injection Counter value |
109 | */ |
110 | struct edac_priv { |
111 | void __iomem *baseaddr; |
112 | char message[ZYNQMP_OCM_EDAC_MSG_SIZE]; |
113 | struct ecc_status stat; |
114 | u32 ce_cnt; |
115 | u32 ue_cnt; |
116 | #ifdef CONFIG_EDAC_DEBUG |
117 | struct dentry *debugfs_dir; |
118 | u8 ce_bitpos; |
119 | u8 ue_bitpos[OCM_NUM_UE_BITPOS]; |
120 | u32 fault_injection_cnt; |
121 | #endif |
122 | }; |
123 | |
124 | /** |
125 | * get_error_info - Get the current ECC error info |
126 | * @base: Pointer to the base address of the OCM |
127 | * @p: Pointer to the OCM ECC status structure |
128 | * @mask: Status register mask value |
129 | * |
130 | * Determines there is any ECC error or not |
131 | * |
132 | */ |
133 | static void get_error_info(void __iomem *base, struct ecc_status *p, int mask) |
134 | { |
135 | if (mask & OCM_CEINTR_MASK) { |
136 | p->ce_cnt++; |
137 | p->ceinfo.fault_lo = readl(addr: base + CE_FFD0_OFST); |
138 | p->ceinfo.fault_hi = readl(addr: base + CE_FFD1_OFST); |
139 | p->ceinfo.addr = (OCM_BASEVAL | readl(addr: base + CE_FFA_OFST)); |
140 | writel(ECC_CTRL_CLR_CE_ERR, addr: base + OCM_ISR_OFST); |
141 | } else if (mask & OCM_UEINTR_MASK) { |
142 | p->ue_cnt++; |
143 | p->ueinfo.fault_lo = readl(addr: base + UE_FFD0_OFST); |
144 | p->ueinfo.fault_hi = readl(addr: base + UE_FFD1_OFST); |
145 | p->ueinfo.addr = (OCM_BASEVAL | readl(addr: base + UE_FFA_OFST)); |
146 | writel(ECC_CTRL_CLR_UE_ERR, addr: base + OCM_ISR_OFST); |
147 | } |
148 | } |
149 | |
150 | /** |
151 | * handle_error - Handle error types CE and UE |
152 | * @dci: Pointer to the EDAC device instance |
153 | * @p: Pointer to the OCM ECC status structure |
154 | * |
155 | * Handles correctable and uncorrectable errors. |
156 | */ |
157 | static void handle_error(struct edac_device_ctl_info *dci, struct ecc_status *p) |
158 | { |
159 | struct edac_priv *priv = dci->pvt_info; |
160 | struct ecc_error_info *pinf; |
161 | |
162 | if (p->ce_cnt) { |
163 | pinf = &p->ceinfo; |
164 | snprintf(buf: priv->message, ZYNQMP_OCM_EDAC_MSG_SIZE, |
165 | fmt: "\nOCM ECC error type :%s\nAddr: [0x%x]\nFault Data[0x%08x%08x]" , |
166 | "CE" , pinf->addr, pinf->fault_hi, pinf->fault_lo); |
167 | edac_device_handle_ce(edac_dev: dci, inst_nr: 0, block_nr: 0, msg: priv->message); |
168 | } |
169 | |
170 | if (p->ue_cnt) { |
171 | pinf = &p->ueinfo; |
172 | snprintf(buf: priv->message, ZYNQMP_OCM_EDAC_MSG_SIZE, |
173 | fmt: "\nOCM ECC error type :%s\nAddr: [0x%x]\nFault Data[0x%08x%08x]" , |
174 | "UE" , pinf->addr, pinf->fault_hi, pinf->fault_lo); |
175 | edac_device_handle_ue(edac_dev: dci, inst_nr: 0, block_nr: 0, msg: priv->message); |
176 | } |
177 | |
178 | memset(p, 0, sizeof(*p)); |
179 | } |
180 | |
181 | /** |
182 | * intr_handler - ISR routine |
183 | * @irq: irq number |
184 | * @dev_id: device id pointer |
185 | * |
186 | * Return: IRQ_NONE, if CE/UE interrupt not set or IRQ_HANDLED otherwise |
187 | */ |
188 | static irqreturn_t intr_handler(int irq, void *dev_id) |
189 | { |
190 | struct edac_device_ctl_info *dci = dev_id; |
191 | struct edac_priv *priv = dci->pvt_info; |
192 | int regval; |
193 | |
194 | regval = readl(addr: priv->baseaddr + OCM_ISR_OFST); |
195 | if (!(regval & (OCM_CEINTR_MASK | OCM_UEINTR_MASK))) { |
196 | WARN_ONCE(1, "Unhandled IRQ%d, ISR: 0x%x" , irq, regval); |
197 | return IRQ_NONE; |
198 | } |
199 | |
200 | get_error_info(base: priv->baseaddr, p: &priv->stat, mask: regval); |
201 | |
202 | priv->ce_cnt += priv->stat.ce_cnt; |
203 | priv->ue_cnt += priv->stat.ue_cnt; |
204 | handle_error(dci, p: &priv->stat); |
205 | |
206 | return IRQ_HANDLED; |
207 | } |
208 | |
209 | /** |
210 | * get_eccstate - Return the ECC status |
211 | * @base: Pointer to the OCM base address |
212 | * |
213 | * Get the ECC enable/disable status |
214 | * |
215 | * Return: ECC status 0/1. |
216 | */ |
217 | static bool get_eccstate(void __iomem *base) |
218 | { |
219 | return readl(addr: base + ECC_CTRL_OFST) & OCM_ECC_ENABLE_MASK; |
220 | } |
221 | |
222 | #ifdef CONFIG_EDAC_DEBUG |
223 | /** |
224 | * write_fault_count - write fault injection count |
225 | * @priv: Pointer to the EDAC private struct |
226 | * |
227 | * Update the fault injection count register, once the counter reaches |
228 | * zero, it injects errors |
229 | */ |
230 | static void write_fault_count(struct edac_priv *priv) |
231 | { |
232 | u32 ficount = priv->fault_injection_cnt; |
233 | |
234 | if (ficount & ~OCM_FICOUNT_MASK) { |
235 | ficount &= OCM_FICOUNT_MASK; |
236 | edac_printk(KERN_INFO, EDAC_DEVICE, |
237 | "Fault injection count value truncated to %d\n" , ficount); |
238 | } |
239 | |
240 | writel(val: ficount, addr: priv->baseaddr + OCM_FIC_OFST); |
241 | } |
242 | |
243 | /* |
244 | * To get the Correctable Error injected, the following steps are needed: |
245 | * - Setup the optional Fault Injection Count: |
246 | * echo <fault_count val> > /sys/kernel/debug/edac/ocm/inject_fault_count |
247 | * - Write the Correctable Error bit position value: |
248 | * echo <bit_pos val> > /sys/kernel/debug/edac/ocm/inject_ce_bitpos |
249 | */ |
250 | static ssize_t inject_ce_write(struct file *file, const char __user *data, |
251 | size_t count, loff_t *ppos) |
252 | { |
253 | struct edac_device_ctl_info *edac_dev = file->private_data; |
254 | struct edac_priv *priv = edac_dev->pvt_info; |
255 | int ret; |
256 | |
257 | if (!data) |
258 | return -EFAULT; |
259 | |
260 | ret = kstrtou8_from_user(s: data, count, base: 0, res: &priv->ce_bitpos); |
261 | if (ret) |
262 | return ret; |
263 | |
264 | if (priv->ce_bitpos > UE_MAX_BITPOS_UPPER) |
265 | return -EINVAL; |
266 | |
267 | if (priv->ce_bitpos <= UE_MAX_BITPOS_LOWER) { |
268 | writel(BIT(priv->ce_bitpos), addr: priv->baseaddr + OCM_FID0_OFST); |
269 | writel(val: 0, addr: priv->baseaddr + OCM_FID1_OFST); |
270 | } else { |
271 | writel(BIT(priv->ce_bitpos - UE_MIN_BITPOS_UPPER), |
272 | addr: priv->baseaddr + OCM_FID1_OFST); |
273 | writel(val: 0, addr: priv->baseaddr + OCM_FID0_OFST); |
274 | } |
275 | |
276 | write_fault_count(priv); |
277 | |
278 | return count; |
279 | } |
280 | |
281 | static const struct file_operations inject_ce_fops = { |
282 | .open = simple_open, |
283 | .write = inject_ce_write, |
284 | .llseek = generic_file_llseek, |
285 | }; |
286 | |
287 | /* |
288 | * To get the Uncorrectable Error injected, the following steps are needed: |
289 | * - Setup the optional Fault Injection Count: |
290 | * echo <fault_count val> > /sys/kernel/debug/edac/ocm/inject_fault_count |
291 | * - Write the Uncorrectable Error bit position values: |
292 | * echo <bit_pos0 val>,<bit_pos1 val> > /sys/kernel/debug/edac/ocm/inject_ue_bitpos |
293 | */ |
294 | static ssize_t inject_ue_write(struct file *file, const char __user *data, |
295 | size_t count, loff_t *ppos) |
296 | { |
297 | struct edac_device_ctl_info *edac_dev = file->private_data; |
298 | struct edac_priv *priv = edac_dev->pvt_info; |
299 | char buf[6], *pbuf, *token[2]; |
300 | u64 ue_bitpos; |
301 | int i, ret; |
302 | u8 len; |
303 | |
304 | if (!data) |
305 | return -EFAULT; |
306 | |
307 | len = min_t(size_t, count, sizeof(buf)); |
308 | if (copy_from_user(to: buf, from: data, n: len)) |
309 | return -EFAULT; |
310 | |
311 | buf[len] = '\0'; |
312 | pbuf = &buf[0]; |
313 | for (i = 0; i < OCM_NUM_UE_BITPOS; i++) |
314 | token[i] = strsep(&pbuf, "," ); |
315 | |
316 | ret = kstrtou8(s: token[0], base: 0, res: &priv->ue_bitpos[0]); |
317 | if (ret) |
318 | return ret; |
319 | |
320 | ret = kstrtou8(s: token[1], base: 0, res: &priv->ue_bitpos[1]); |
321 | if (ret) |
322 | return ret; |
323 | |
324 | if (priv->ue_bitpos[0] > UE_MAX_BITPOS_UPPER || |
325 | priv->ue_bitpos[1] > UE_MAX_BITPOS_UPPER) |
326 | return -EINVAL; |
327 | |
328 | if (priv->ue_bitpos[0] == priv->ue_bitpos[1]) { |
329 | edac_printk(KERN_ERR, EDAC_DEVICE, "Bit positions should not be equal\n" ); |
330 | return -EINVAL; |
331 | } |
332 | |
333 | ue_bitpos = BIT(priv->ue_bitpos[0]) | BIT(priv->ue_bitpos[1]); |
334 | |
335 | writel(val: (u32)ue_bitpos, addr: priv->baseaddr + OCM_FID0_OFST); |
336 | writel(val: (u32)(ue_bitpos >> 32), addr: priv->baseaddr + OCM_FID1_OFST); |
337 | |
338 | write_fault_count(priv); |
339 | |
340 | return count; |
341 | } |
342 | |
343 | static const struct file_operations inject_ue_fops = { |
344 | .open = simple_open, |
345 | .write = inject_ue_write, |
346 | .llseek = generic_file_llseek, |
347 | }; |
348 | |
349 | static void setup_debugfs(struct edac_device_ctl_info *edac_dev) |
350 | { |
351 | struct edac_priv *priv = edac_dev->pvt_info; |
352 | |
353 | priv->debugfs_dir = edac_debugfs_create_dir(dirname: "ocm" ); |
354 | if (!priv->debugfs_dir) |
355 | return; |
356 | |
357 | edac_debugfs_create_x32(name: "inject_fault_count" , mode: 0644, parent: priv->debugfs_dir, |
358 | value: &priv->fault_injection_cnt); |
359 | edac_debugfs_create_file(name: "inject_ue_bitpos" , mode: 0644, parent: priv->debugfs_dir, |
360 | data: edac_dev, fops: &inject_ue_fops); |
361 | edac_debugfs_create_file(name: "inject_ce_bitpos" , mode: 0644, parent: priv->debugfs_dir, |
362 | data: edac_dev, fops: &inject_ce_fops); |
363 | } |
364 | #endif |
365 | |
366 | static int edac_probe(struct platform_device *pdev) |
367 | { |
368 | struct edac_device_ctl_info *dci; |
369 | struct edac_priv *priv; |
370 | void __iomem *baseaddr; |
371 | struct resource *res; |
372 | int irq, ret; |
373 | |
374 | baseaddr = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
375 | if (IS_ERR(ptr: baseaddr)) |
376 | return PTR_ERR(ptr: baseaddr); |
377 | |
378 | if (!get_eccstate(base: baseaddr)) { |
379 | edac_printk(KERN_INFO, EDAC_DEVICE, "ECC not enabled\n" ); |
380 | return -ENXIO; |
381 | } |
382 | |
383 | dci = edac_device_alloc_ctl_info(sizeof_private: sizeof(*priv), ZYNQMP_OCM_EDAC_STRING, |
384 | nr_instances: 1, ZYNQMP_OCM_EDAC_STRING, nr_blocks: 1, offset_value: 0, NULL, nr_attribs: 0, |
385 | device_index: edac_device_alloc_index()); |
386 | if (!dci) |
387 | return -ENOMEM; |
388 | |
389 | priv = dci->pvt_info; |
390 | platform_set_drvdata(pdev, data: dci); |
391 | dci->dev = &pdev->dev; |
392 | priv->baseaddr = baseaddr; |
393 | dci->mod_name = pdev->dev.driver->name; |
394 | dci->ctl_name = ZYNQMP_OCM_EDAC_STRING; |
395 | dci->dev_name = dev_name(dev: &pdev->dev); |
396 | |
397 | irq = platform_get_irq(pdev, 0); |
398 | if (irq < 0) { |
399 | ret = irq; |
400 | goto free_dev_ctl; |
401 | } |
402 | |
403 | ret = devm_request_irq(dev: &pdev->dev, irq, handler: intr_handler, irqflags: 0, |
404 | devname: dev_name(dev: &pdev->dev), dev_id: dci); |
405 | if (ret) { |
406 | edac_printk(KERN_ERR, EDAC_DEVICE, "Failed to request Irq\n" ); |
407 | goto free_dev_ctl; |
408 | } |
409 | |
410 | /* Enable UE, CE interrupts */ |
411 | writel(val: (OCM_CEINTR_MASK | OCM_UEINTR_MASK), addr: priv->baseaddr + OCM_IEN_OFST); |
412 | |
413 | #ifdef CONFIG_EDAC_DEBUG |
414 | setup_debugfs(dci); |
415 | #endif |
416 | |
417 | ret = edac_device_add_device(edac_dev: dci); |
418 | if (ret) |
419 | goto free_dev_ctl; |
420 | |
421 | return 0; |
422 | |
423 | free_dev_ctl: |
424 | edac_device_free_ctl_info(ctl_info: dci); |
425 | |
426 | return ret; |
427 | } |
428 | |
429 | static void edac_remove(struct platform_device *pdev) |
430 | { |
431 | struct edac_device_ctl_info *dci = platform_get_drvdata(pdev); |
432 | struct edac_priv *priv = dci->pvt_info; |
433 | |
434 | /* Disable UE, CE interrupts */ |
435 | writel(val: (OCM_CEINTR_MASK | OCM_UEINTR_MASK), addr: priv->baseaddr + OCM_IDS_OFST); |
436 | |
437 | #ifdef CONFIG_EDAC_DEBUG |
438 | debugfs_remove_recursive(dentry: priv->debugfs_dir); |
439 | #endif |
440 | |
441 | edac_device_del_device(dev: &pdev->dev); |
442 | edac_device_free_ctl_info(ctl_info: dci); |
443 | } |
444 | |
445 | static const struct of_device_id zynqmp_ocm_edac_match[] = { |
446 | { .compatible = "xlnx,zynqmp-ocmc-1.0" }, |
447 | { /* end of table */ } |
448 | }; |
449 | |
450 | MODULE_DEVICE_TABLE(of, zynqmp_ocm_edac_match); |
451 | |
452 | static struct platform_driver zynqmp_ocm_edac_driver = { |
453 | .driver = { |
454 | .name = "zynqmp-ocm-edac" , |
455 | .of_match_table = zynqmp_ocm_edac_match, |
456 | }, |
457 | .probe = edac_probe, |
458 | .remove_new = edac_remove, |
459 | }; |
460 | |
461 | module_platform_driver(zynqmp_ocm_edac_driver); |
462 | |
463 | MODULE_AUTHOR("Advanced Micro Devices, Inc" ); |
464 | MODULE_DESCRIPTION("Xilinx ZynqMP OCM ECC driver" ); |
465 | MODULE_LICENSE("GPL" ); |
466 | |