Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 7332
b: refs/heads/master
c: 1930605
h: refs/heads/master
v: v3
  • Loading branch information
Paul E. McKenney authored and Linus Torvalds committed Sep 7, 2005
1 parent fafc8cb commit 32dbdf2
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 5 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: fe21773d655c2c64641ec2cef499289ea175c817
refs/heads/master: 19306059cd7fedaf96b4b0260a9a8a45e513c857
112 changes: 112 additions & 0 deletions trunk/Documentation/RCU/NMI-RCU.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
Using RCU to Protect Dynamic NMI Handlers


Although RCU is usually used to protect read-mostly data structures,
it is possible to use RCU to provide dynamic non-maskable interrupt
handlers, as well as dynamic irq handlers. This document describes
how to do this, drawing loosely from Zwane Mwaikambo's NMI-timer
work in "arch/i386/oprofile/nmi_timer_int.c" and in
"arch/i386/kernel/traps.c".

The relevant pieces of code are listed below, each followed by a
brief explanation.

static int dummy_nmi_callback(struct pt_regs *regs, int cpu)
{
return 0;
}

The dummy_nmi_callback() function is a "dummy" NMI handler that does
nothing, but returns zero, thus saying that it did nothing, allowing
the NMI handler to take the default machine-specific action.

static nmi_callback_t nmi_callback = dummy_nmi_callback;

This nmi_callback variable is a global function pointer to the current
NMI handler.

fastcall void do_nmi(struct pt_regs * regs, long error_code)
{
int cpu;

nmi_enter();

cpu = smp_processor_id();
++nmi_count(cpu);

if (!rcu_dereference(nmi_callback)(regs, cpu))
default_do_nmi(regs);

nmi_exit();
}

The do_nmi() function processes each NMI. It first disables preemption
in the same way that a hardware irq would, then increments the per-CPU
count of NMIs. It then invokes the NMI handler stored in the nmi_callback
function pointer. If this handler returns zero, do_nmi() invokes the
default_do_nmi() function to handle a machine-specific NMI. Finally,
preemption is restored.

Strictly speaking, rcu_dereference() is not needed, since this code runs
only on i386, which does not need rcu_dereference() anyway. However,
it is a good documentation aid, particularly for anyone attempting to
do something similar on Alpha.

Quick Quiz: Why might the rcu_dereference() be necessary on Alpha,
given that the code referenced by the pointer is read-only?


Back to the discussion of NMI and RCU...

void set_nmi_callback(nmi_callback_t callback)
{
rcu_assign_pointer(nmi_callback, callback);
}

The set_nmi_callback() function registers an NMI handler. Note that any
data that is to be used by the callback must be initialized up -before-
the call to set_nmi_callback(). On architectures that do not order
writes, the rcu_assign_pointer() ensures that the NMI handler sees the
initialized values.

void unset_nmi_callback(void)
{
rcu_assign_pointer(nmi_callback, dummy_nmi_callback);
}

This function unregisters an NMI handler, restoring the original
dummy_nmi_handler(). However, there may well be an NMI handler
currently executing on some other CPU. We therefore cannot free
up any data structures used by the old NMI handler until execution
of it completes on all other CPUs.

One way to accomplish this is via synchronize_sched(), perhaps as
follows:

unset_nmi_callback();
synchronize_sched();
kfree(my_nmi_data);

This works because synchronize_sched() blocks until all CPUs complete
any preemption-disabled segments of code that they were executing.
Since NMI handlers disable preemption, synchronize_sched() is guaranteed
not to return until all ongoing NMI handlers exit. It is therefore safe
to free up the handler's data as soon as synchronize_sched() returns.


Answer to Quick Quiz

Why might the rcu_dereference() be necessary on Alpha, given
that the code referenced by the pointer is read-only?

Answer: The caller to set_nmi_callback() might well have
initialized some data that is to be used by the
new NMI handler. In this case, the rcu_dereference()
would be needed, because otherwise a CPU that received
an NMI just after the new handler was set might see
the pointer to the new NMI handler, but the old
pre-initialized version of the handler's data.

More important, the rcu_dereference() makes it clear
to someone reading the code that the pointer is being
protected by RCU.
4 changes: 2 additions & 2 deletions trunk/arch/i386/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -657,15 +657,15 @@ fastcall void do_nmi(struct pt_regs * regs, long error_code)

++nmi_count(cpu);

if (!nmi_callback(regs, cpu))
if (!rcu_dereference(nmi_callback)(regs, cpu))
default_do_nmi(regs);

nmi_exit();
}

void set_nmi_callback(nmi_callback_t callback)
{
nmi_callback = callback;
rcu_assign_pointer(nmi_callback, callback);
}
EXPORT_SYMBOL_GPL(set_nmi_callback);

Expand Down
4 changes: 2 additions & 2 deletions trunk/arch/x86_64/kernel/nmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,14 @@ asmlinkage void do_nmi(struct pt_regs * regs, long error_code)

nmi_enter();
add_pda(__nmi_count,1);
if (!nmi_callback(regs, cpu))
if (!rcu_dereference(nmi_callback)(regs, cpu))
default_do_nmi(regs);
nmi_exit();
}

void set_nmi_callback(nmi_callback_t callback)
{
nmi_callback = callback;
rcu_assign_pointer(nmi_callback, callback);
}

void unset_nmi_callback(void)
Expand Down

0 comments on commit 32dbdf2

Please sign in to comment.