diff options
Diffstat (limited to 'kernel/irq_work.c')
-rw-r--r-- | kernel/irq_work.c | 150 |
1 files changed, 104 insertions, 46 deletions
diff --git a/kernel/irq_work.c b/kernel/irq_work.c index 1588e3b2871b..55fcce6065cf 100644 --- a/kernel/irq_work.c +++ b/kernel/irq_work.c | |||
@@ -12,37 +12,36 @@ | |||
12 | #include <linux/percpu.h> | 12 | #include <linux/percpu.h> |
13 | #include <linux/hardirq.h> | 13 | #include <linux/hardirq.h> |
14 | #include <linux/irqflags.h> | 14 | #include <linux/irqflags.h> |
15 | #include <linux/sched.h> | ||
16 | #include <linux/tick.h> | ||
17 | #include <linux/cpu.h> | ||
18 | #include <linux/notifier.h> | ||
15 | #include <asm/processor.h> | 19 | #include <asm/processor.h> |
16 | 20 | ||
17 | /* | ||
18 | * An entry can be in one of four states: | ||
19 | * | ||
20 | * free NULL, 0 -> {claimed} : free to be used | ||
21 | * claimed NULL, 3 -> {pending} : claimed to be enqueued | ||
22 | * pending next, 3 -> {busy} : queued, pending callback | ||
23 | * busy NULL, 2 -> {free, claimed} : callback in progress, can be claimed | ||
24 | */ | ||
25 | |||
26 | #define IRQ_WORK_PENDING 1UL | ||
27 | #define IRQ_WORK_BUSY 2UL | ||
28 | #define IRQ_WORK_FLAGS 3UL | ||
29 | 21 | ||
30 | static DEFINE_PER_CPU(struct llist_head, irq_work_list); | 22 | static DEFINE_PER_CPU(struct llist_head, irq_work_list); |
23 | static DEFINE_PER_CPU(int, irq_work_raised); | ||
31 | 24 | ||
32 | /* | 25 | /* |
33 | * Claim the entry so that no one else will poke at it. | 26 | * Claim the entry so that no one else will poke at it. |
34 | */ | 27 | */ |
35 | static bool irq_work_claim(struct irq_work *work) | 28 | static bool irq_work_claim(struct irq_work *work) |
36 | { | 29 | { |
37 | unsigned long flags, nflags; | 30 | unsigned long flags, oflags, nflags; |
38 | 31 | ||
32 | /* | ||
33 | * Start with our best wish as a premise but only trust any | ||
34 | * flag value after cmpxchg() result. | ||
35 | */ | ||
36 | flags = work->flags & ~IRQ_WORK_PENDING; | ||
39 | for (;;) { | 37 | for (;;) { |
40 | flags = work->flags; | ||
41 | if (flags & IRQ_WORK_PENDING) | ||
42 | return false; | ||
43 | nflags = flags | IRQ_WORK_FLAGS; | 38 | nflags = flags | IRQ_WORK_FLAGS; |
44 | if (cmpxchg(&work->flags, flags, nflags) == flags) | 39 | oflags = cmpxchg(&work->flags, flags, nflags); |
40 | if (oflags == flags) | ||
45 | break; | 41 | break; |
42 | if (oflags & IRQ_WORK_PENDING) | ||
43 | return false; | ||
44 | flags = oflags; | ||
46 | cpu_relax(); | 45 | cpu_relax(); |
47 | } | 46 | } |
48 | 47 | ||
@@ -57,57 +56,69 @@ void __weak arch_irq_work_raise(void) | |||
57 | } | 56 | } |
58 | 57 | ||
59 | /* | 58 | /* |
60 | * Queue the entry and raise the IPI if needed. | 59 | * Enqueue the irq_work @entry unless it's already pending |
60 | * somewhere. | ||
61 | * | ||
62 | * Can be re-enqueued while the callback is still in progress. | ||
61 | */ | 63 | */ |
62 | static void __irq_work_queue(struct irq_work *work) | 64 | void irq_work_queue(struct irq_work *work) |
63 | { | 65 | { |
64 | bool empty; | 66 | /* Only queue if not already pending */ |
67 | if (!irq_work_claim(work)) | ||
68 | return; | ||
65 | 69 | ||
70 | /* Queue the entry and raise the IPI if needed. */ | ||
66 | preempt_disable(); | 71 | preempt_disable(); |
67 | 72 | ||
68 | empty = llist_add(&work->llnode, &__get_cpu_var(irq_work_list)); | 73 | llist_add(&work->llnode, &__get_cpu_var(irq_work_list)); |
69 | /* The list was empty, raise self-interrupt to start processing. */ | 74 | |
70 | if (empty) | 75 | /* |
71 | arch_irq_work_raise(); | 76 | * If the work is not "lazy" or the tick is stopped, raise the irq |
77 | * work interrupt (if supported by the arch), otherwise, just wait | ||
78 | * for the next tick. | ||
79 | */ | ||
80 | if (!(work->flags & IRQ_WORK_LAZY) || tick_nohz_tick_stopped()) { | ||
81 | if (!this_cpu_cmpxchg(irq_work_raised, 0, 1)) | ||
82 | arch_irq_work_raise(); | ||
83 | } | ||
72 | 84 | ||
73 | preempt_enable(); | 85 | preempt_enable(); |
74 | } | 86 | } |
87 | EXPORT_SYMBOL_GPL(irq_work_queue); | ||
75 | 88 | ||
76 | /* | 89 | bool irq_work_needs_cpu(void) |
77 | * Enqueue the irq_work @entry, returns true on success, failure when the | ||
78 | * @entry was already enqueued by someone else. | ||
79 | * | ||
80 | * Can be re-enqueued while the callback is still in progress. | ||
81 | */ | ||
82 | bool irq_work_queue(struct irq_work *work) | ||
83 | { | 90 | { |
84 | if (!irq_work_claim(work)) { | 91 | struct llist_head *this_list; |
85 | /* | 92 | |
86 | * Already enqueued, can't do! | 93 | this_list = &__get_cpu_var(irq_work_list); |
87 | */ | 94 | if (llist_empty(this_list)) |
88 | return false; | 95 | return false; |
89 | } | ||
90 | 96 | ||
91 | __irq_work_queue(work); | 97 | /* All work should have been flushed before going offline */ |
98 | WARN_ON_ONCE(cpu_is_offline(smp_processor_id())); | ||
99 | |||
92 | return true; | 100 | return true; |
93 | } | 101 | } |
94 | EXPORT_SYMBOL_GPL(irq_work_queue); | ||
95 | 102 | ||
96 | /* | 103 | static void __irq_work_run(void) |
97 | * Run the irq_work entries on this cpu. Requires to be ran from hardirq | ||
98 | * context with local IRQs disabled. | ||
99 | */ | ||
100 | void irq_work_run(void) | ||
101 | { | 104 | { |
105 | unsigned long flags; | ||
102 | struct irq_work *work; | 106 | struct irq_work *work; |
103 | struct llist_head *this_list; | 107 | struct llist_head *this_list; |
104 | struct llist_node *llnode; | 108 | struct llist_node *llnode; |
105 | 109 | ||
110 | |||
111 | /* | ||
112 | * Reset the "raised" state right before we check the list because | ||
113 | * an NMI may enqueue after we find the list empty from the runner. | ||
114 | */ | ||
115 | __this_cpu_write(irq_work_raised, 0); | ||
116 | barrier(); | ||
117 | |||
106 | this_list = &__get_cpu_var(irq_work_list); | 118 | this_list = &__get_cpu_var(irq_work_list); |
107 | if (llist_empty(this_list)) | 119 | if (llist_empty(this_list)) |
108 | return; | 120 | return; |
109 | 121 | ||
110 | BUG_ON(!in_irq()); | ||
111 | BUG_ON(!irqs_disabled()); | 122 | BUG_ON(!irqs_disabled()); |
112 | 123 | ||
113 | llnode = llist_del_all(this_list); | 124 | llnode = llist_del_all(this_list); |
@@ -119,16 +130,31 @@ void irq_work_run(void) | |||
119 | /* | 130 | /* |
120 | * Clear the PENDING bit, after this point the @work | 131 | * Clear the PENDING bit, after this point the @work |
121 | * can be re-used. | 132 | * can be re-used. |
133 | * Make it immediately visible so that other CPUs trying | ||
134 | * to claim that work don't rely on us to handle their data | ||
135 | * while we are in the middle of the func. | ||
122 | */ | 136 | */ |
123 | work->flags = IRQ_WORK_BUSY; | 137 | flags = work->flags & ~IRQ_WORK_PENDING; |
138 | xchg(&work->flags, flags); | ||
139 | |||
124 | work->func(work); | 140 | work->func(work); |
125 | /* | 141 | /* |
126 | * Clear the BUSY bit and return to the free state if | 142 | * Clear the BUSY bit and return to the free state if |
127 | * no-one else claimed it meanwhile. | 143 | * no-one else claimed it meanwhile. |
128 | */ | 144 | */ |
129 | (void)cmpxchg(&work->flags, IRQ_WORK_BUSY, 0); | 145 | (void)cmpxchg(&work->flags, flags, flags & ~IRQ_WORK_BUSY); |
130 | } | 146 | } |
131 | } | 147 | } |
148 | |||
149 | /* | ||
150 | * Run the irq_work entries on this cpu. Requires to be ran from hardirq | ||
151 | * context with local IRQs disabled. | ||
152 | */ | ||
153 | void irq_work_run(void) | ||
154 | { | ||
155 | BUG_ON(!in_irq()); | ||
156 | __irq_work_run(); | ||
157 | } | ||
132 | EXPORT_SYMBOL_GPL(irq_work_run); | 158 | EXPORT_SYMBOL_GPL(irq_work_run); |
133 | 159 | ||
134 | /* | 160 | /* |
@@ -143,3 +169,35 @@ void irq_work_sync(struct irq_work *work) | |||
143 | cpu_relax(); | 169 | cpu_relax(); |
144 | } | 170 | } |
145 | EXPORT_SYMBOL_GPL(irq_work_sync); | 171 | EXPORT_SYMBOL_GPL(irq_work_sync); |
172 | |||
173 | #ifdef CONFIG_HOTPLUG_CPU | ||
174 | static int irq_work_cpu_notify(struct notifier_block *self, | ||
175 | unsigned long action, void *hcpu) | ||
176 | { | ||
177 | long cpu = (long)hcpu; | ||
178 | |||
179 | switch (action) { | ||
180 | case CPU_DYING: | ||
181 | /* Called from stop_machine */ | ||
182 | if (WARN_ON_ONCE(cpu != smp_processor_id())) | ||
183 | break; | ||
184 | __irq_work_run(); | ||
185 | break; | ||
186 | default: | ||
187 | break; | ||
188 | } | ||
189 | return NOTIFY_OK; | ||
190 | } | ||
191 | |||
192 | static struct notifier_block cpu_notify; | ||
193 | |||
194 | static __init int irq_work_init_cpu_notifier(void) | ||
195 | { | ||
196 | cpu_notify.notifier_call = irq_work_cpu_notify; | ||
197 | cpu_notify.priority = 0; | ||
198 | register_cpu_notifier(&cpu_notify); | ||
199 | return 0; | ||
200 | } | ||
201 | device_initcall(irq_work_init_cpu_notifier); | ||
202 | |||
203 | #endif /* CONFIG_HOTPLUG_CPU */ | ||