1 .. _NMI_rcu_doc: 1 .. _NMI_rcu_doc: 2 2 3 Using RCU to Protect Dynamic NMI Handlers 3 Using RCU to Protect Dynamic NMI Handlers 4 ========================================= 4 ========================================= 5 5 6 6 7 Although RCU is usually used to protect read-m 7 Although RCU is usually used to protect read-mostly data structures, 8 it is possible to use RCU to provide dynamic n 8 it is possible to use RCU to provide dynamic non-maskable interrupt 9 handlers, as well as dynamic irq handlers. Th 9 handlers, as well as dynamic irq handlers. This document describes 10 how to do this, drawing loosely from Zwane Mwa 10 how to do this, drawing loosely from Zwane Mwaikambo's NMI-timer 11 work in an old version of "arch/x86/kernel/tra 11 work in an old version of "arch/x86/kernel/traps.c". 12 12 13 The relevant pieces of code are listed below, 13 The relevant pieces of code are listed below, each followed by a 14 brief explanation:: 14 brief explanation:: 15 15 16 static int dummy_nmi_callback(struct p 16 static int dummy_nmi_callback(struct pt_regs *regs, int cpu) 17 { 17 { 18 return 0; 18 return 0; 19 } 19 } 20 20 21 The dummy_nmi_callback() function is a "dummy" 21 The dummy_nmi_callback() function is a "dummy" NMI handler that does 22 nothing, but returns zero, thus saying that it 22 nothing, but returns zero, thus saying that it did nothing, allowing 23 the NMI handler to take the default machine-sp 23 the NMI handler to take the default machine-specific action:: 24 24 25 static nmi_callback_t nmi_callback = d 25 static nmi_callback_t nmi_callback = dummy_nmi_callback; 26 26 27 This nmi_callback variable is a global functio 27 This nmi_callback variable is a global function pointer to the current 28 NMI handler:: 28 NMI handler:: 29 29 30 void do_nmi(struct pt_regs * regs, lon 30 void do_nmi(struct pt_regs * regs, long error_code) 31 { 31 { 32 int cpu; 32 int cpu; 33 33 34 nmi_enter(); 34 nmi_enter(); 35 35 36 cpu = smp_processor_id(); 36 cpu = smp_processor_id(); 37 ++nmi_count(cpu); 37 ++nmi_count(cpu); 38 38 39 if (!rcu_dereference_sched(nmi 39 if (!rcu_dereference_sched(nmi_callback)(regs, cpu)) 40 default_do_nmi(regs); 40 default_do_nmi(regs); 41 41 42 nmi_exit(); 42 nmi_exit(); 43 } 43 } 44 44 45 The do_nmi() function processes each NMI. It 45 The do_nmi() function processes each NMI. It first disables preemption 46 in the same way that a hardware irq would, the 46 in the same way that a hardware irq would, then increments the per-CPU 47 count of NMIs. It then invokes the NMI handle 47 count of NMIs. It then invokes the NMI handler stored in the nmi_callback 48 function pointer. If this handler returns zer 48 function pointer. If this handler returns zero, do_nmi() invokes the 49 default_do_nmi() function to handle a machine- 49 default_do_nmi() function to handle a machine-specific NMI. Finally, 50 preemption is restored. 50 preemption is restored. 51 51 52 In theory, rcu_dereference_sched() is not need 52 In theory, rcu_dereference_sched() is not needed, since this code runs 53 only on i386, which in theory does not need rc 53 only on i386, which in theory does not need rcu_dereference_sched() 54 anyway. However, in practice it is a good doc 54 anyway. However, in practice it is a good documentation aid, particularly 55 for anyone attempting to do something similar 55 for anyone attempting to do something similar on Alpha or on systems 56 with aggressive optimizing compilers. 56 with aggressive optimizing compilers. 57 57 58 Quick Quiz: 58 Quick Quiz: 59 Why might the rcu_dereference_ 59 Why might the rcu_dereference_sched() be necessary on Alpha, given that the code referenced by the pointer is read-only? 60 60 61 :ref:`Answer to Quick Quiz <answer_quick_quiz_ 61 :ref:`Answer to Quick Quiz <answer_quick_quiz_NMI>` 62 62 63 Back to the discussion of NMI and RCU:: 63 Back to the discussion of NMI and RCU:: 64 64 65 void set_nmi_callback(nmi_callback_t c 65 void set_nmi_callback(nmi_callback_t callback) 66 { 66 { 67 rcu_assign_pointer(nmi_callbac 67 rcu_assign_pointer(nmi_callback, callback); 68 } 68 } 69 69 70 The set_nmi_callback() function registers an N 70 The set_nmi_callback() function registers an NMI handler. Note that any 71 data that is to be used by the callback must b 71 data that is to be used by the callback must be initialized up -before- 72 the call to set_nmi_callback(). On architectu 72 the call to set_nmi_callback(). On architectures that do not order 73 writes, the rcu_assign_pointer() ensures that 73 writes, the rcu_assign_pointer() ensures that the NMI handler sees the 74 initialized values:: 74 initialized values:: 75 75 76 void unset_nmi_callback(void) 76 void unset_nmi_callback(void) 77 { 77 { 78 rcu_assign_pointer(nmi_callbac 78 rcu_assign_pointer(nmi_callback, dummy_nmi_callback); 79 } 79 } 80 80 81 This function unregisters an NMI handler, rest 81 This function unregisters an NMI handler, restoring the original 82 dummy_nmi_handler(). However, there may well 82 dummy_nmi_handler(). However, there may well be an NMI handler 83 currently executing on some other CPU. We the 83 currently executing on some other CPU. We therefore cannot free 84 up any data structures used by the old NMI han 84 up any data structures used by the old NMI handler until execution 85 of it completes on all other CPUs. 85 of it completes on all other CPUs. 86 86 87 One way to accomplish this is via synchronize_ 87 One way to accomplish this is via synchronize_rcu(), perhaps as 88 follows:: 88 follows:: 89 89 90 unset_nmi_callback(); 90 unset_nmi_callback(); 91 synchronize_rcu(); 91 synchronize_rcu(); 92 kfree(my_nmi_data); 92 kfree(my_nmi_data); 93 93 94 This works because (as of v4.20) synchronize_r 94 This works because (as of v4.20) synchronize_rcu() blocks until all 95 CPUs complete any preemption-disabled segments 95 CPUs complete any preemption-disabled segments of code that they were 96 executing. 96 executing. 97 Since NMI handlers disable preemption, synchro 97 Since NMI handlers disable preemption, synchronize_rcu() is guaranteed 98 not to return until all ongoing NMI handlers e 98 not to return until all ongoing NMI handlers exit. It is therefore safe 99 to free up the handler's data as soon as synch 99 to free up the handler's data as soon as synchronize_rcu() returns. 100 100 101 Important note: for this to work, the architec 101 Important note: for this to work, the architecture in question must 102 invoke nmi_enter() and nmi_exit() on NMI entry 102 invoke nmi_enter() and nmi_exit() on NMI entry and exit, respectively. 103 103 104 .. _answer_quick_quiz_NMI: 104 .. _answer_quick_quiz_NMI: 105 105 106 Answer to Quick Quiz: 106 Answer to Quick Quiz: 107 Why might the rcu_dereference_sched() 107 Why might the rcu_dereference_sched() be necessary on Alpha, given that the code referenced by the pointer is read-only? 108 108 109 The caller to set_nmi_callback() might 109 The caller to set_nmi_callback() might well have 110 initialized some data that is to be us 110 initialized some data that is to be used by the new NMI 111 handler. In this case, the rcu_derefe 111 handler. In this case, the rcu_dereference_sched() would 112 be needed, because otherwise a CPU tha 112 be needed, because otherwise a CPU that received an NMI 113 just after the new handler was set mig 113 just after the new handler was set might see the pointer 114 to the new NMI handler, but the old pr 114 to the new NMI handler, but the old pre-initialized 115 version of the handler's data. 115 version of the handler's data. 116 116 117 This same sad story can happen on othe 117 This same sad story can happen on other CPUs when using 118 a compiler with aggressive pointer-val 118 a compiler with aggressive pointer-value speculation 119 optimizations. (But please don't!) 119 optimizations. (But please don't!) 120 120 121 More important, the rcu_dereference_sc 121 More important, the rcu_dereference_sched() makes it 122 clear to someone reading the code that 122 clear to someone reading the code that the pointer is 123 being protected by RCU-sched. 123 being protected by RCU-sched.
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.