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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
|
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2026 Intel Corporation. */
/*
* Intel driver to reset bitfix filters when they overflow.
*
* Each bitfix filter has limited slots to track corrected errors.
* These slots can be filled by transient corrected errors (e.g.,
* bit flips from particle strikes) that don't represent permanent
* hardware defects.
*
* When the filter overflows (yellow status), reset it to reclaim
* slots occupied by transient corrected errors. If the overflow
* repeats frequently after reset, it indicates persistent hardware
* defects that need attention: the system should be scheduled for
* servicing.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cacheinfo.h>
#include <linux/cleanup.h>
#include <linux/cpufeature.h>
#include <linux/cpuhplock.h>
#include <linux/device-id/x86_cpu.h>
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/topology.h>
#include <linux/types.h>
#include <linux/xarray.h>
#include <asm/cpu_device_id.h>
#include <asm/cpufeatures.h>
#include <asm/intel-family.h>
#include <asm/mce.h>
#include <asm/msr.h>
#include <asm/msr-index.h>
/*
* A 10-minute observation period helps distinguish between:
*
* - A long-term accumulation of transient corrected errors
* (filter stays clear after reset).
*
* - Permanent defects (filter overflows again quickly).
*/
#define BFF_OVERFLOW_INTERVAL secs_to_jiffies(10 * 60)
/* Intel bitfix filter control register defines */
#define MSR_MC0_BFF_CTL 0x000006c0
#define MSR_MCx_BFF_CTL(x) (MSR_MC0_BFF_CTL + (x))
#define MCI_BFF_RESET BIT_ULL(0)
/* Machine check bank hardware unit types */
enum bff_bank_type {
BFF_BANK_NONE = 0,
BFF_BANK_DCU,
BFF_BANK_DTLB,
BFF_BANK_MLC,
BFF_BANK_CCF,
BFF_BANK_HSF,
BFF_BANK_IOCACHE,
};
/*
* Mapping from machine check bank numbers on Diamond Rapids CPU, that are
* supported by a bitfix filter, to hardware unit type.
*/
static const enum bff_bank_type dmr_mcbanks[MAX_NR_BANKS] = {
[1] = BFF_BANK_DCU,
[2] = BFF_BANK_DTLB,
[3] = BFF_BANK_MLC,
[6] = BFF_BANK_CCF,
[13] = BFF_BANK_HSF,
[17] = BFF_BANK_IOCACHE
};
static const struct x86_cpu_id bff_cpu_ids[] __initconst = {
X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, dmr_mcbanks),
{}
};
MODULE_DEVICE_TABLE(x86cpu, bff_cpu_ids);
static const enum bff_bank_type *bff_bank_types;
static DEFINE_XARRAY(bff_bank_xa);
/* Diamond Rapids maps APICID[2] to the IMH instance within a socket. */
#define APICID_IMH_NUM GENMASK(2, 2)
#define IMH_NUM(apicid) FIELD_GET(APICID_IMH_NUM, apicid)
#define NUM_IMH_PER_SOCKET 2
static void bff_set_imh_id(struct mce *mce, unsigned long *id)
{
int imh_num;
imh_num = NUM_IMH_PER_SOCKET * topology_physical_package_id(mce->extcpu) +
IMH_NUM(mce->apicid);
*id |= imh_num;
}
static bool bff_set_cache_id(int cpu, int level, unsigned long *id)
{
int cacheid;
guard(cpus_read_lock)();
cacheid = get_cpu_cacheinfo_id(cpu, level);
if (cacheid == -1) {
pr_warn("Could not get L%d cache id for CPU %d\n", level, cpu);
return false;
}
*id |= cacheid;
return true;
}
/*
* Cache IDs are only unique within a cache level.
* Include the MCA bank number so each BFF-capable hardware
* resource has a unique tracking ID.
*/
#define BFF_ID_BANK_FIELD GENMASK(63, 32)
static unsigned long bff_get_id(struct mce *mce)
{
unsigned long id = FIELD_PREP(BFF_ID_BANK_FIELD, mce->bank);
switch (bff_bank_types[mce->bank]) {
case BFF_BANK_DCU:
case BFF_BANK_DTLB:
if (!bff_set_cache_id(mce->extcpu, 1, &id))
return ULONG_MAX;
break;
case BFF_BANK_MLC:
if (!bff_set_cache_id(mce->extcpu, 2, &id))
return ULONG_MAX;
break;
case BFF_BANK_CCF:
if (!bff_set_cache_id(mce->extcpu, 3, &id))
return ULONG_MAX;
break;
case BFF_BANK_HSF:
case BFF_BANK_IOCACHE:
bff_set_imh_id(mce, &id);
break;
default:
return ULONG_MAX;
}
return id;
}
/*
* Save current timestamp for bff_id. Return true if it is within
* BFF_OVERFLOW_INTERVAL of previous timestamp for this bff_id.
*/
static bool bff_overflow_is_frequent(unsigned long bff_id)
{
unsigned long now = jiffies, interval_end;
unsigned long *ts;
if (bff_id == ULONG_MAX)
return false;
ts = xa_load(&bff_bank_xa, bff_id);
if (!ts) {
ts = kzalloc_obj(*ts);
if (!ts) {
pr_warn("Failed to allocate timestamp for bitfix filter 0x%lx\n", bff_id);
return false;
}
if (xa_is_err(xa_store(&bff_bank_xa, bff_id, ts, GFP_KERNEL))) {
kfree(ts);
pr_warn("Failed to record timestamp for bitfix filter 0x%lx\n", bff_id);
return false;
}
*ts = now;
return false;
}
interval_end = *ts + BFF_OVERFLOW_INTERVAL;
*ts = now;
return time_before(now, interval_end);
}
static void bff_reset_and_report(struct mce *mce)
{
/* Reset bitfix filter using the CPU that logged the yellow status */
if (wrmsrq_on_cpu(mce->extcpu, MSR_MCx_BFF_CTL(mce->bank), MCI_BFF_RESET))
pr_warn("Failed to reset bitfix filter for CPU %d Bank %d\n",
mce->extcpu, mce->bank);
/*
* Use the unique id for the bitfix filter instance that overflowed and
* check if this is a repeat within the BFF_OVERFLOW_INTERVAL. If it
* is, then report at WARN severity as the user may want to take action.
*/
if (bff_overflow_is_frequent(bff_get_id(mce))) {
pr_warn_ratelimited(HW_ERR "Socket %d CPU %d Bank %d bitfix filter overflowed frequently\n",
mce->socketid, mce->extcpu, mce->bank);
} else {
pr_notice_ratelimited(HW_ERR "Socket %d CPU %d Bank %d bitfix filter overflowed\n",
mce->socketid, mce->extcpu, mce->bank);
}
}
static int bff_mce_notify(struct notifier_block *nb, unsigned long val, void *data)
{
struct mce *mce = data;
/* TES is undefined for uncorrected errors. */
if (mce->status & MCI_STATUS_UC)
return NOTIFY_DONE;
if (MCI_STATUS_TES(mce->status) != MCI_STATUS_TES_YELLOW)
return NOTIFY_DONE;
if (mce->bank >= MAX_NR_BANKS || bff_bank_types[mce->bank] == BFF_BANK_NONE)
return NOTIFY_DONE;
bff_reset_and_report(mce);
return NOTIFY_DONE;
}
static struct notifier_block bff_notifier = {
.notifier_call = bff_mce_notify,
.priority = MCE_PRIO_EDAC,
};
static int __init bff_init(void)
{
const struct x86_cpu_id *m;
u64 core_caps, mcg_cap;
if (!cpu_feature_enabled(X86_FEATURE_MCA))
return -ENODEV;
rdmsrq(MSR_IA32_MCG_CAP, mcg_cap);
if (!(mcg_cap & MCG_TES_P))
return -ENODEV;
if (!cpu_feature_enabled(X86_FEATURE_CORE_CAPABILITIES))
return -ENODEV;
rdmsrq(MSR_IA32_CORE_CAPS, core_caps);
if (!(core_caps & MSR_IA32_CORE_CAPS_BFF_RESET))
return -ENODEV;
m = x86_match_cpu(bff_cpu_ids);
if (!m) {
pr_info("CPU model not supported by the bitfix filter reset driver\n");
return -ENODEV;
}
bff_bank_types = (const enum bff_bank_type *)m->driver_data;
mce_register_decode_chain(&bff_notifier);
return 0;
}
static void __exit bff_exit(void)
{
unsigned long bff_id;
unsigned long *ts;
mce_unregister_decode_chain(&bff_notifier);
xa_for_each(&bff_bank_xa, bff_id, ts)
kfree(ts);
xa_destroy(&bff_bank_xa);
}
module_init(bff_init);
module_exit(bff_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tony Luck");
MODULE_DESCRIPTION("Intel bitfix filter reset driver");
|