1 |
3 |
xianfeng |
Using RCU to Protect Dynamic NMI Handlers
|
2 |
|
|
|
3 |
|
|
|
4 |
|
|
Although RCU is usually used to protect read-mostly data structures,
|
5 |
|
|
it is possible to use RCU to provide dynamic non-maskable interrupt
|
6 |
|
|
handlers, as well as dynamic irq handlers. This document describes
|
7 |
|
|
how to do this, drawing loosely from Zwane Mwaikambo's NMI-timer
|
8 |
|
|
work in "arch/i386/oprofile/nmi_timer_int.c" and in
|
9 |
|
|
"arch/i386/kernel/traps.c".
|
10 |
|
|
|
11 |
|
|
The relevant pieces of code are listed below, each followed by a
|
12 |
|
|
brief explanation.
|
13 |
|
|
|
14 |
|
|
static int dummy_nmi_callback(struct pt_regs *regs, int cpu)
|
15 |
|
|
{
|
16 |
|
|
return 0;
|
17 |
|
|
}
|
18 |
|
|
|
19 |
|
|
The dummy_nmi_callback() function is a "dummy" NMI handler that does
|
20 |
|
|
nothing, but returns zero, thus saying that it did nothing, allowing
|
21 |
|
|
the NMI handler to take the default machine-specific action.
|
22 |
|
|
|
23 |
|
|
static nmi_callback_t nmi_callback = dummy_nmi_callback;
|
24 |
|
|
|
25 |
|
|
This nmi_callback variable is a global function pointer to the current
|
26 |
|
|
NMI handler.
|
27 |
|
|
|
28 |
|
|
fastcall void do_nmi(struct pt_regs * regs, long error_code)
|
29 |
|
|
{
|
30 |
|
|
int cpu;
|
31 |
|
|
|
32 |
|
|
nmi_enter();
|
33 |
|
|
|
34 |
|
|
cpu = smp_processor_id();
|
35 |
|
|
++nmi_count(cpu);
|
36 |
|
|
|
37 |
|
|
if (!rcu_dereference(nmi_callback)(regs, cpu))
|
38 |
|
|
default_do_nmi(regs);
|
39 |
|
|
|
40 |
|
|
nmi_exit();
|
41 |
|
|
}
|
42 |
|
|
|
43 |
|
|
The do_nmi() function processes each NMI. It first disables preemption
|
44 |
|
|
in the same way that a hardware irq would, then increments the per-CPU
|
45 |
|
|
count of NMIs. It then invokes the NMI handler stored in the nmi_callback
|
46 |
|
|
function pointer. If this handler returns zero, do_nmi() invokes the
|
47 |
|
|
default_do_nmi() function to handle a machine-specific NMI. Finally,
|
48 |
|
|
preemption is restored.
|
49 |
|
|
|
50 |
|
|
Strictly speaking, rcu_dereference() is not needed, since this code runs
|
51 |
|
|
only on i386, which does not need rcu_dereference() anyway. However,
|
52 |
|
|
it is a good documentation aid, particularly for anyone attempting to
|
53 |
|
|
do something similar on Alpha.
|
54 |
|
|
|
55 |
|
|
Quick Quiz: Why might the rcu_dereference() be necessary on Alpha,
|
56 |
|
|
given that the code referenced by the pointer is read-only?
|
57 |
|
|
|
58 |
|
|
|
59 |
|
|
Back to the discussion of NMI and RCU...
|
60 |
|
|
|
61 |
|
|
void set_nmi_callback(nmi_callback_t callback)
|
62 |
|
|
{
|
63 |
|
|
rcu_assign_pointer(nmi_callback, callback);
|
64 |
|
|
}
|
65 |
|
|
|
66 |
|
|
The set_nmi_callback() function registers an NMI handler. Note that any
|
67 |
|
|
data that is to be used by the callback must be initialized up -before-
|
68 |
|
|
the call to set_nmi_callback(). On architectures that do not order
|
69 |
|
|
writes, the rcu_assign_pointer() ensures that the NMI handler sees the
|
70 |
|
|
initialized values.
|
71 |
|
|
|
72 |
|
|
void unset_nmi_callback(void)
|
73 |
|
|
{
|
74 |
|
|
rcu_assign_pointer(nmi_callback, dummy_nmi_callback);
|
75 |
|
|
}
|
76 |
|
|
|
77 |
|
|
This function unregisters an NMI handler, restoring the original
|
78 |
|
|
dummy_nmi_handler(). However, there may well be an NMI handler
|
79 |
|
|
currently executing on some other CPU. We therefore cannot free
|
80 |
|
|
up any data structures used by the old NMI handler until execution
|
81 |
|
|
of it completes on all other CPUs.
|
82 |
|
|
|
83 |
|
|
One way to accomplish this is via synchronize_sched(), perhaps as
|
84 |
|
|
follows:
|
85 |
|
|
|
86 |
|
|
unset_nmi_callback();
|
87 |
|
|
synchronize_sched();
|
88 |
|
|
kfree(my_nmi_data);
|
89 |
|
|
|
90 |
|
|
This works because synchronize_sched() blocks until all CPUs complete
|
91 |
|
|
any preemption-disabled segments of code that they were executing.
|
92 |
|
|
Since NMI handlers disable preemption, synchronize_sched() is guaranteed
|
93 |
|
|
not to return until all ongoing NMI handlers exit. It is therefore safe
|
94 |
|
|
to free up the handler's data as soon as synchronize_sched() returns.
|
95 |
|
|
|
96 |
|
|
|
97 |
|
|
Answer to Quick Quiz
|
98 |
|
|
|
99 |
|
|
Why might the rcu_dereference() be necessary on Alpha, given
|
100 |
|
|
that the code referenced by the pointer is read-only?
|
101 |
|
|
|
102 |
|
|
Answer: The caller to set_nmi_callback() might well have
|
103 |
|
|
initialized some data that is to be used by the
|
104 |
|
|
new NMI handler. In this case, the rcu_dereference()
|
105 |
|
|
would be needed, because otherwise a CPU that received
|
106 |
|
|
an NMI just after the new handler was set might see
|
107 |
|
|
the pointer to the new NMI handler, but the old
|
108 |
|
|
pre-initialized version of the handler's data.
|
109 |
|
|
|
110 |
|
|
More important, the rcu_dereference() makes it clear
|
111 |
|
|
to someone reading the code that the pointer is being
|
112 |
|
|
protected by RCU.
|