diff options
Diffstat (limited to 'kernel/irq_work.c')
-rw-r--r-- | kernel/irq_work.c | 110 |
1 files changed, 45 insertions, 65 deletions
diff --git a/kernel/irq_work.c b/kernel/irq_work.c index a82170e2fa78..e6bcbe756663 100644 --- a/kernel/irq_work.c +++ b/kernel/irq_work.c | |||
@@ -16,11 +16,12 @@ | |||
16 | #include <linux/tick.h> | 16 | #include <linux/tick.h> |
17 | #include <linux/cpu.h> | 17 | #include <linux/cpu.h> |
18 | #include <linux/notifier.h> | 18 | #include <linux/notifier.h> |
19 | #include <linux/smp.h> | ||
19 | #include <asm/processor.h> | 20 | #include <asm/processor.h> |
20 | 21 | ||
21 | 22 | ||
22 | static DEFINE_PER_CPU(struct llist_head, irq_work_list); | 23 | static DEFINE_PER_CPU(struct llist_head, raised_list); |
23 | static DEFINE_PER_CPU(int, irq_work_raised); | 24 | static DEFINE_PER_CPU(struct llist_head, lazy_list); |
24 | 25 | ||
25 | /* | 26 | /* |
26 | * Claim the entry so that no one else will poke at it. | 27 | * Claim the entry so that no one else will poke at it. |
@@ -55,12 +56,34 @@ void __weak arch_irq_work_raise(void) | |||
55 | */ | 56 | */ |
56 | } | 57 | } |
57 | 58 | ||
59 | #ifdef CONFIG_SMP | ||
58 | /* | 60 | /* |
59 | * Enqueue the irq_work @entry unless it's already pending | 61 | * Enqueue the irq_work @work on @cpu unless it's already pending |
60 | * somewhere. | 62 | * somewhere. |
61 | * | 63 | * |
62 | * Can be re-enqueued while the callback is still in progress. | 64 | * Can be re-enqueued while the callback is still in progress. |
63 | */ | 65 | */ |
66 | bool irq_work_queue_on(struct irq_work *work, int cpu) | ||
67 | { | ||
68 | /* All work should have been flushed before going offline */ | ||
69 | WARN_ON_ONCE(cpu_is_offline(cpu)); | ||
70 | |||
71 | /* Arch remote IPI send/receive backend aren't NMI safe */ | ||
72 | WARN_ON_ONCE(in_nmi()); | ||
73 | |||
74 | /* Only queue if not already pending */ | ||
75 | if (!irq_work_claim(work)) | ||
76 | return false; | ||
77 | |||
78 | if (llist_add(&work->llnode, &per_cpu(raised_list, cpu))) | ||
79 | arch_send_call_function_single_ipi(cpu); | ||
80 | |||
81 | return true; | ||
82 | } | ||
83 | EXPORT_SYMBOL_GPL(irq_work_queue_on); | ||
84 | #endif | ||
85 | |||
86 | /* Enqueue the irq work @work on the current CPU */ | ||
64 | bool irq_work_queue(struct irq_work *work) | 87 | bool irq_work_queue(struct irq_work *work) |
65 | { | 88 | { |
66 | /* Only queue if not already pending */ | 89 | /* Only queue if not already pending */ |
@@ -70,15 +93,13 @@ bool irq_work_queue(struct irq_work *work) | |||
70 | /* Queue the entry and raise the IPI if needed. */ | 93 | /* Queue the entry and raise the IPI if needed. */ |
71 | preempt_disable(); | 94 | preempt_disable(); |
72 | 95 | ||
73 | llist_add(&work->llnode, &__get_cpu_var(irq_work_list)); | 96 | /* If the work is "lazy", handle it from next tick if any */ |
74 | 97 | if (work->flags & IRQ_WORK_LAZY) { | |
75 | /* | 98 | if (llist_add(&work->llnode, &__get_cpu_var(lazy_list)) && |
76 | * If the work is not "lazy" or the tick is stopped, raise the irq | 99 | tick_nohz_tick_stopped()) |
77 | * work interrupt (if supported by the arch), otherwise, just wait | 100 | arch_irq_work_raise(); |
78 | * for the next tick. | 101 | } else { |
79 | */ | 102 | if (llist_add(&work->llnode, &__get_cpu_var(raised_list))) |
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(); | 103 | arch_irq_work_raise(); |
83 | } | 104 | } |
84 | 105 | ||
@@ -90,10 +111,11 @@ EXPORT_SYMBOL_GPL(irq_work_queue); | |||
90 | 111 | ||
91 | bool irq_work_needs_cpu(void) | 112 | bool irq_work_needs_cpu(void) |
92 | { | 113 | { |
93 | struct llist_head *this_list; | 114 | struct llist_head *raised, *lazy; |
94 | 115 | ||
95 | this_list = &__get_cpu_var(irq_work_list); | 116 | raised = &__get_cpu_var(raised_list); |
96 | if (llist_empty(this_list)) | 117 | lazy = &__get_cpu_var(lazy_list); |
118 | if (llist_empty(raised) && llist_empty(lazy)) | ||
97 | return false; | 119 | return false; |
98 | 120 | ||
99 | /* All work should have been flushed before going offline */ | 121 | /* All work should have been flushed before going offline */ |
@@ -102,28 +124,18 @@ bool irq_work_needs_cpu(void) | |||
102 | return true; | 124 | return true; |
103 | } | 125 | } |
104 | 126 | ||
105 | static void __irq_work_run(void) | 127 | static void irq_work_run_list(struct llist_head *list) |
106 | { | 128 | { |
107 | unsigned long flags; | 129 | unsigned long flags; |
108 | struct irq_work *work; | 130 | struct irq_work *work; |
109 | struct llist_head *this_list; | ||
110 | struct llist_node *llnode; | 131 | struct llist_node *llnode; |
111 | 132 | ||
133 | BUG_ON(!irqs_disabled()); | ||
112 | 134 | ||
113 | /* | 135 | if (llist_empty(list)) |
114 | * Reset the "raised" state right before we check the list because | ||
115 | * an NMI may enqueue after we find the list empty from the runner. | ||
116 | */ | ||
117 | __this_cpu_write(irq_work_raised, 0); | ||
118 | barrier(); | ||
119 | |||
120 | this_list = &__get_cpu_var(irq_work_list); | ||
121 | if (llist_empty(this_list)) | ||
122 | return; | 136 | return; |
123 | 137 | ||
124 | BUG_ON(!irqs_disabled()); | 138 | llnode = llist_del_all(list); |
125 | |||
126 | llnode = llist_del_all(this_list); | ||
127 | while (llnode != NULL) { | 139 | while (llnode != NULL) { |
128 | work = llist_entry(llnode, struct irq_work, llnode); | 140 | work = llist_entry(llnode, struct irq_work, llnode); |
129 | 141 | ||
@@ -149,13 +161,13 @@ static void __irq_work_run(void) | |||
149 | } | 161 | } |
150 | 162 | ||
151 | /* | 163 | /* |
152 | * Run the irq_work entries on this cpu. Requires to be ran from hardirq | 164 | * hotplug calls this through: |
153 | * context with local IRQs disabled. | 165 | * hotplug_cfd() -> flush_smp_call_function_queue() |
154 | */ | 166 | */ |
155 | void irq_work_run(void) | 167 | void irq_work_run(void) |
156 | { | 168 | { |
157 | BUG_ON(!in_irq()); | 169 | irq_work_run_list(&__get_cpu_var(raised_list)); |
158 | __irq_work_run(); | 170 | irq_work_run_list(&__get_cpu_var(lazy_list)); |
159 | } | 171 | } |
160 | EXPORT_SYMBOL_GPL(irq_work_run); | 172 | EXPORT_SYMBOL_GPL(irq_work_run); |
161 | 173 | ||
@@ -171,35 +183,3 @@ void irq_work_sync(struct irq_work *work) | |||
171 | cpu_relax(); | 183 | cpu_relax(); |
172 | } | 184 | } |
173 | EXPORT_SYMBOL_GPL(irq_work_sync); | 185 | EXPORT_SYMBOL_GPL(irq_work_sync); |
174 | |||
175 | #ifdef CONFIG_HOTPLUG_CPU | ||
176 | static int irq_work_cpu_notify(struct notifier_block *self, | ||
177 | unsigned long action, void *hcpu) | ||
178 | { | ||
179 | long cpu = (long)hcpu; | ||
180 | |||
181 | switch (action) { | ||
182 | case CPU_DYING: | ||
183 | /* Called from stop_machine */ | ||
184 | if (WARN_ON_ONCE(cpu != smp_processor_id())) | ||
185 | break; | ||
186 | __irq_work_run(); | ||
187 | break; | ||
188 | default: | ||
189 | break; | ||
190 | } | ||
191 | return NOTIFY_OK; | ||
192 | } | ||
193 | |||
194 | static struct notifier_block cpu_notify; | ||
195 | |||
196 | static __init int irq_work_init_cpu_notifier(void) | ||
197 | { | ||
198 | cpu_notify.notifier_call = irq_work_cpu_notify; | ||
199 | cpu_notify.priority = 0; | ||
200 | register_cpu_notifier(&cpu_notify); | ||
201 | return 0; | ||
202 | } | ||
203 | device_initcall(irq_work_init_cpu_notifier); | ||
204 | |||
205 | #endif /* CONFIG_HOTPLUG_CPU */ | ||