1
2
3
4
5
6
7
8#include <linux/kernel.h>
9#include <linux/module.h>
10#include <linux/irq_work.h>
11#include <linux/hardirq.h>
12
13
14
15
16
17
18
19
20
21
22
23
24
25#define IRQ_WORK_PENDING 1UL
26#define IRQ_WORK_BUSY 2UL
27#define IRQ_WORK_FLAGS 3UL
28
29static inline bool irq_work_is_set(struct irq_work *entry, int flags)
30{
31 return (unsigned long)entry->next & flags;
32}
33
34static inline struct irq_work *irq_work_next(struct irq_work *entry)
35{
36 unsigned long next = (unsigned long)entry->next;
37 next &= ~IRQ_WORK_FLAGS;
38 return (struct irq_work *)next;
39}
40
41static inline struct irq_work *next_flags(struct irq_work *entry, int flags)
42{
43 unsigned long next = (unsigned long)entry;
44 next |= flags;
45 return (struct irq_work *)next;
46}
47
48static DEFINE_PER_CPU(struct irq_work *, irq_work_list);
49
50
51
52
53static bool irq_work_claim(struct irq_work *entry)
54{
55 struct irq_work *next, *nflags;
56
57 do {
58 next = entry->next;
59 if ((unsigned long)next & IRQ_WORK_PENDING)
60 return false;
61 nflags = next_flags(next, IRQ_WORK_FLAGS);
62 } while (cmpxchg(&entry->next, next, nflags) != next);
63
64 return true;
65}
66
67
68void __weak arch_irq_work_raise(void)
69{
70
71
72
73}
74
75
76
77
78static void __irq_work_queue(struct irq_work *entry)
79{
80 struct irq_work *next;
81
82 preempt_disable();
83
84 do {
85 next = __this_cpu_read(irq_work_list);
86
87 entry->next = next_flags(next, IRQ_WORK_FLAGS);
88 } while (this_cpu_cmpxchg(irq_work_list, next, entry) != next);
89
90
91 if (!irq_work_next(entry))
92 arch_irq_work_raise();
93
94 preempt_enable();
95}
96
97
98
99
100
101
102
103bool irq_work_queue(struct irq_work *entry)
104{
105 if (!irq_work_claim(entry)) {
106
107
108
109 return false;
110 }
111
112 __irq_work_queue(entry);
113 return true;
114}
115EXPORT_SYMBOL_GPL(irq_work_queue);
116
117
118
119
120
121void irq_work_run(void)
122{
123 struct irq_work *list;
124
125 if (this_cpu_read(irq_work_list) == NULL)
126 return;
127
128 BUG_ON(!in_irq());
129 BUG_ON(!irqs_disabled());
130
131 list = this_cpu_xchg(irq_work_list, NULL);
132
133 while (list != NULL) {
134 struct irq_work *entry = list;
135
136 list = irq_work_next(list);
137
138
139
140
141
142 entry->next = next_flags(NULL, IRQ_WORK_BUSY);
143 entry->func(entry);
144
145
146
147
148 (void)cmpxchg(&entry->next,
149 next_flags(NULL, IRQ_WORK_BUSY),
150 NULL);
151 }
152}
153EXPORT_SYMBOL_GPL(irq_work_run);
154
155
156
157
158
159void irq_work_sync(struct irq_work *entry)
160{
161 WARN_ON_ONCE(irqs_disabled());
162
163 while (irq_work_is_set(entry, IRQ_WORK_BUSY))
164 cpu_relax();
165}
166EXPORT_SYMBOL_GPL(irq_work_sync);
167