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
|
// SPDX-License-Identifier: GPL-2.0
/*
* arm64 SDEI-based cross-CPU NMI service.
*
* Delivering an "NMI-shaped" event to an EL1 context that has locally
* masked interrupts, on silicon without FEAT_NMI, can be done two ways:
*
* - pseudo-NMI: mask "interrupts" via the GIC priority register
* (ICC_PMR_EL1) instead of PSTATE.DAIF, leaving a high-priority band
* deliverable. Functionally this works -- but it reimplements every
* local_irq_disable()/enable() and exception entry/exit as a PMR
* write plus synchronisation, a cost paid on that hot path forever,
* whether or not an NMI is ever delivered.
*
* - SDEI: leave interrupt masking as the cheap PSTATE.DAIF operation
* and have the firmware bounce an EL3-routed Group-0 SGI back to
* NS-EL1 as an event callback. The cost is a firmware round-trip,
* but only at the rare moment delivery is actually needed.
*
* This driver takes the second path: it keeps the IRQ-mask hot path
* free and pays only when it fires, which is what makes cross-CPU NMI
* affordable on hardware where the pseudo-NMI tax isn't, until FEAT_NMI
* makes NMI masking cheap in the architecture itself.
*
* Capabilities provided:
*
* - sdei_nmi_trigger_cpumask_backtrace() — override for arm64's
* arch_trigger_cpumask_backtrace(), so sysrq-l, RCU stall dumps,
* hardlockup_all_cpu_backtrace, soft-lockup/hung-task secondary
* dumps all reach interrupt-masked CPUs.
*
* - sdei_nmi_stop_cpus() — the last rung of smp_send_stop()'s
* escalation (reboot/halt and the panic/kdump crash stop alike),
* reaching CPUs that ignored the stop IPIs; on the kdump path the
* wedged context is captured into the vmcore before the CPU parks.
*
* Delivery uses the standard SDEI software-signalled event (event 0) and
* SDEI_EVENT_SIGNAL. We register a handler for event 0, enable it, and
* poke a target CPU with sdei_event_signal(0, mpidr): firmware makes
* event 0 pending on that PE and dispatches the handler NMI-like,
* regardless of the target's DAIF.
* Availability is simply whether event 0 registers and enables -- if SDEI
* and its software-signalled event are present we use it, otherwise the
* driver stays inert.
*/
#define pr_fmt(fmt) "sdei_nmi: " fmt
#include <linux/arm_sdei.h>
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/nmi.h>
#include <linux/printk.h>
#include <linux/ptrace.h>
#include <linux/smp.h>
#include <linux/types.h>
#include <asm/nmi.h>
#include <asm/smp_plat.h>
static bool sdei_nmi_available;
#define SDEI_NMI_EVENT 0
/*
* Backtrace and stop both ride SDEI event 0. That is not a chosen economy:
* event 0 is the only architecturally software-signalled event -- the sole
* event SDEI_EVENT_SIGNAL can target at an arbitrary PE. Every other event
* number is a firmware/platform interrupt-bound event, not something the
* kernel can raise cross-CPU, so a dedicated "stop" event would need
* firmware to define and bind it -- exactly the firmware dependency this
* driver sets out to avoid.
*
* Sharing one event means the handler must tell a stop apart from a
* backtrace. A stop is terminal and system-wide -- sdei_nmi_stop_cpus() is
* only reached from smp_send_stop() (reboot/halt/panic/kdump), which never
* returns -- so once a stop is requested, every later event-0 fire is a
* stop too. A single write-once flag therefore carries as much as a
* per-CPU mask would: sdei_nmi_stop_cpus() sets it before signalling, and
* the handler reads a set flag as "stop this CPU" and a clear flag as
* "backtrace" (handled by nmi_cpu_backtrace(), which self-gates on the
* framework's backtrace mask). A backtrace fire that races in after a stop
* has begun just stops that CPU instead -- harmless, it is going down.
*/
static bool sdei_nmi_stopping;
static int sdei_nmi_handler(u32 event, struct pt_regs *regs, void *arg)
{
/*
* No smp_rmb() pairing sdei_nmi_stop_cpus()'s dsb(ishst): the flag is
* the only shared value, and this handler runs only because firmware
* delivered the event -- a round-trip past that store -- so the read
* cannot be stale and there is no second load for a barrier to order.
*/
if (READ_ONCE(sdei_nmi_stopping)) {
/*
* Never returns, and deliberately never completes the SDEI
* event: SDEI_EVENT_COMPLETE has firmware restore the
* interrupted context, which would land the CPU back in
* the wedged loop (or in do_idle, which BUGs at
* cpuhp_report_idle_dead once it sees itself offline).
* Returning a modified pt_regs doesn't help --
* arch/arm64/kernel/sdei.c::do_sdei_event only honours a PC
* override via its IRQ-state heuristic and otherwise hands
* EL3 its own saved-context slot back.
*
* Trade-off: EL3 retains ~one saved-context slot per parked
* CPU until the next hardware reset (~hundreds of bytes per
* CPU). Recoverability is unchanged versus an IPI-stopped
* CPU: neither comes back without a reset.
*/
arm64_nmi_cpu_stop(regs, false);
/* unreachable */
}
/*
* nmi_cpu_backtrace() no-ops unless this CPU's bit is set in the
* global backtrace mask (driven by nmi_trigger_cpumask_backtrace()),
* so a fire that reaches a CPU not being backtraced is harmless.
*/
nmi_cpu_backtrace(regs);
return SDEI_EV_HANDLED;
}
NOKPROBE_SYMBOL(sdei_nmi_handler);
static void sdei_nmi_fire(unsigned int target_cpu)
{
int err = sdei_event_signal(SDEI_NMI_EVENT, cpu_logical_map(target_cpu));
if (err)
pr_warn("SDEI_EVENT_SIGNAL to CPU %u failed: %d\n",
target_cpu, err);
}
/*
* Raise callback for nmi_trigger_cpumask_backtrace(): signal event 0
* at every CPU still pending in @mask. The framework excludes the local
* CPU from @mask before calling us.
*/
static void sdei_nmi_raise_backtrace(cpumask_t *mask)
{
unsigned int cpu;
/*
* Publish backtrace_mask (set by nmi_trigger_cpumask_backtrace())
* before signalling. As in the stop path, the SMC is not a memory
* store, so dsb(ishst) is needed for the target to observe the mask.
*/
dsb(ishst);
for_each_cpu(cpu, mask)
sdei_nmi_fire(cpu);
}
/*
* Override hook for arch_trigger_cpumask_backtrace() (see
* arch/arm64/kernel/smp.c). Returns true when SDEI handled the request,
* which is the case whenever SDEI is active; on a false return the arch
* falls back to its regular-IRQ (or pseudo-NMI, if enabled) IPI.
*
* On a kernel built without paying the pseudo-NMI hot-path cost (the
* usual case for this driver's target), the IPI can't reach a CPU that
* has interrupts masked -- so the backtrace of the one CPU you care
* about comes back empty. SDEI is dispatched out of EL3 and lands
* regardless of the target's DAIF, without taxing the IRQ-mask path.
*/
bool sdei_nmi_trigger_cpumask_backtrace(const cpumask_t *mask, int exclude_cpu)
{
if (!sdei_nmi_available)
return false;
nmi_trigger_cpumask_backtrace(mask, exclude_cpu,
sdei_nmi_raise_backtrace);
return true;
}
bool sdei_nmi_active(void)
{
return sdei_nmi_available;
}
/*
* Last rung of the stop escalation in smp_send_stop() (see
* arch/arm64/kernel/smp.c). The caller runs the regular stop IPI (and
* the pseudo-NMI stop IPI, where available) first; @mask holds whatever
* stayed online through those -- typically CPUs wedged with interrupts
* masked, unreachable by an IPI. Mark the stop in progress and signal
* event 0 at each target; a target acks by marking itself offline, which
* the caller polls for. The caller has already confirmed sdei_nmi_active().
*/
void sdei_nmi_stop_cpus(const cpumask_t *mask)
{
unsigned int cpu;
WRITE_ONCE(sdei_nmi_stopping, true);
/*
* Publish the flag before signalling. The signal goes out via an SMC
* to firmware, not a memory store, so smp_wmb() ordering is not
* enough: use dsb(ishst) to make the store globally visible before the
* SMC executes, as gic_ipi_send_mask() does for its SGI. The SDEI spec
* does not require the dispatch to order the caller's prior stores.
*/
dsb(ishst);
for_each_cpu(cpu, mask)
sdei_nmi_fire(cpu);
}
/*
* device_initcall (after arch_initcall(sdei_init), so the SDEI subsystem
* is up): probe the firmware, register the event, and turn on the
* cross-CPU service. If the probe fails the driver stays inert and the
* override hooks decline, leaving the arch's own paths in place.
*/
static int __init sdei_nmi_init(void)
{
int err;
if (!sdei_is_present())
return 0;
err = sdei_event_register(SDEI_NMI_EVENT, sdei_nmi_handler, NULL);
if (err) {
pr_err("sdei_event_register(%u) failed: %d\n",
SDEI_NMI_EVENT, err);
return 0;
}
err = sdei_event_enable(SDEI_NMI_EVENT);
if (err) {
pr_err("sdei_event_enable(%u) failed: %d\n",
SDEI_NMI_EVENT, err);
sdei_event_unregister(SDEI_NMI_EVENT);
return 0;
}
sdei_nmi_available = true;
pr_info("using SDEI cross-CPU NMI (SDEI_EVENT_SIGNAL, event %u)\n",
SDEI_NMI_EVENT);
return 0;
}
device_initcall(sdei_nmi_init);
|