summaryrefslogtreecommitdiffstats
path: root/drivers/edac/octeon_edac-pc.c
blob: 9d13061744e49a2e1654c13740b85f5355b16edc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) 2009 Wind River Systems,
 *   written by Ralf Baechle <ralf@linux-mips.org>
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/edac.h>

#include "edac_core.h"
#include "edac_module.h"

#include <asm/octeon/cvmx.h>
#include <asm/mipsregs.h>

#define EDAC_MOD_STR "octeon"

extern int register_co_cache_error_notifier(struct notifier_block *nb);
extern int unregister_co_cache_error_notifier(struct notifier_block *nb);

extern unsigned long long cache_err_dcache[NR_CPUS];

static struct edac_device_ctl_info *ed_cavium;

/*
 * EDAC CPU cache error callback
 *
 */

static int  co_cache_error_event(struct notifier_block *this,
	unsigned long event, void *ptr)
{
	unsigned int core = cvmx_get_core_num();
	unsigned int cpu = smp_processor_id();
	uint64_t icache_err = read_octeon_c0_icacheerr();
	struct edac_device_ctl_info *ed = ed_cavium;

	edac_device_printk(ed, KERN_ERR,
			   "Cache error exception on core %d / processor %d:\n",
			   core, cpu);
	edac_device_printk(ed, KERN_ERR,
			   "cp0_errorepc == %lx\n", read_c0_errorepc());
	if (icache_err & 1) {
		edac_device_printk(ed, KERN_ERR, "CacheErr (Icache) == %llx\n",
				   (unsigned long long)icache_err);
		write_octeon_c0_icacheerr(0);
		edac_device_handle_ce(ed, 0, 0, ed->ctl_name);
	}
	if (cache_err_dcache[core] & 1) {
		edac_device_printk(ed, KERN_ERR, "CacheErr (Dcache) == %llx\n",
				   (unsigned long long)cache_err_dcache[core]);
		cache_err_dcache[core] = 0;
		edac_device_handle_ue(ed, 0, 0, ed->ctl_name);
	}

	return NOTIFY_DONE;
}

static struct notifier_block co_cache_error_notifier = {
	.notifier_call = co_cache_error_event,
};

static int __devinit co_cache_error_probe(struct platform_device *pdev)
{
	struct edac_device_ctl_info *ed;
	int res = 0;

	ed = edac_device_alloc_ctl_info(0, "cpu", 1, NULL, 0, 0, NULL, 0,
					edac_device_alloc_index());

	ed->dev = &pdev->dev;
	platform_set_drvdata(pdev, ed);
	ed->dev_name = dev_name(&pdev->dev);

	ed->mod_name = "octeon-cpu";
	ed->ctl_name = "co_cpu_err";

	if (edac_device_add_device(ed) > 0) {
		pr_err("%s: edac_device_add_device() failed\n", __func__);
		goto err;
	}

	register_co_cache_error_notifier(&co_cache_error_notifier);
	ed_cavium = ed;

	return 0;

err:
	edac_device_free_ctl_info(ed);

	return res;
}

static int co_cache_error_remove(struct platform_device *pdev)
{
	struct edac_device_ctl_info *ed = platform_get_drvdata(pdev);

	unregister_co_cache_error_notifier(&co_cache_error_notifier);
	ed_cavium = NULL;
	edac_device_del_device(&pdev->dev);
	edac_device_free_ctl_info(ed);

	return 0;
}

static struct platform_driver co_cache_error_driver = {
	.probe = co_cache_error_probe,
	.remove = co_cache_error_remove,
	.driver = {
		   .name = "co_pc_edac",
	}
};

static int __init co_edac_init(void)
{
	int ret;

	ret = platform_driver_register(&co_cache_error_driver);
	if (ret)
		pr_warning(EDAC_MOD_STR "CPU err failed to register\n");

	return ret;
}

static void __exit co_edac_exit(void)
{
	platform_driver_unregister(&co_cache_error_driver);
}

module_init(co_edac_init);
module_exit(co_edac_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>");