1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IRQ offload/bypass manager |
4 | * |
5 | * Copyright (C) 2015 Red Hat, Inc. |
6 | * Copyright (c) 2015 Linaro Ltd. |
7 | * |
8 | * Various virtualization hardware acceleration techniques allow bypassing or |
9 | * offloading interrupts received from devices around the host kernel. Posted |
10 | * Interrupts on Intel VT-d systems can allow interrupts to be received |
11 | * directly by a virtual machine. ARM IRQ Forwarding allows forwarded physical |
12 | * interrupts to be directly deactivated by the guest. This manager allows |
13 | * interrupt producers and consumers to find each other to enable this sort of |
14 | * bypass. |
15 | */ |
16 | |
17 | #include <linux/irqbypass.h> |
18 | #include <linux/list.h> |
19 | #include <linux/module.h> |
20 | #include <linux/mutex.h> |
21 | |
22 | MODULE_LICENSE("GPL v2" ); |
23 | MODULE_DESCRIPTION("IRQ bypass manager utility module" ); |
24 | |
25 | static LIST_HEAD(producers); |
26 | static LIST_HEAD(consumers); |
27 | static DEFINE_MUTEX(lock); |
28 | |
29 | /* @lock must be held when calling connect */ |
30 | static int __connect(struct irq_bypass_producer *prod, |
31 | struct irq_bypass_consumer *cons) |
32 | { |
33 | int ret = 0; |
34 | |
35 | if (prod->stop) |
36 | prod->stop(prod); |
37 | if (cons->stop) |
38 | cons->stop(cons); |
39 | |
40 | if (prod->add_consumer) |
41 | ret = prod->add_consumer(prod, cons); |
42 | |
43 | if (!ret) { |
44 | ret = cons->add_producer(cons, prod); |
45 | if (ret && prod->del_consumer) |
46 | prod->del_consumer(prod, cons); |
47 | } |
48 | |
49 | if (cons->start) |
50 | cons->start(cons); |
51 | if (prod->start) |
52 | prod->start(prod); |
53 | |
54 | return ret; |
55 | } |
56 | |
57 | /* @lock must be held when calling disconnect */ |
58 | static void __disconnect(struct irq_bypass_producer *prod, |
59 | struct irq_bypass_consumer *cons) |
60 | { |
61 | if (prod->stop) |
62 | prod->stop(prod); |
63 | if (cons->stop) |
64 | cons->stop(cons); |
65 | |
66 | cons->del_producer(cons, prod); |
67 | |
68 | if (prod->del_consumer) |
69 | prod->del_consumer(prod, cons); |
70 | |
71 | if (cons->start) |
72 | cons->start(cons); |
73 | if (prod->start) |
74 | prod->start(prod); |
75 | } |
76 | |
77 | /** |
78 | * irq_bypass_register_producer - register IRQ bypass producer |
79 | * @producer: pointer to producer structure |
80 | * |
81 | * Add the provided IRQ producer to the list of producers and connect |
82 | * with any matching token found on the IRQ consumers list. |
83 | */ |
84 | int irq_bypass_register_producer(struct irq_bypass_producer *producer) |
85 | { |
86 | struct irq_bypass_producer *tmp; |
87 | struct irq_bypass_consumer *consumer; |
88 | int ret; |
89 | |
90 | if (!producer->token) |
91 | return -EINVAL; |
92 | |
93 | might_sleep(); |
94 | |
95 | if (!try_module_get(THIS_MODULE)) |
96 | return -ENODEV; |
97 | |
98 | mutex_lock(&lock); |
99 | |
100 | list_for_each_entry(tmp, &producers, node) { |
101 | if (tmp->token == producer->token) { |
102 | ret = -EBUSY; |
103 | goto out_err; |
104 | } |
105 | } |
106 | |
107 | list_for_each_entry(consumer, &consumers, node) { |
108 | if (consumer->token == producer->token) { |
109 | ret = __connect(prod: producer, cons: consumer); |
110 | if (ret) |
111 | goto out_err; |
112 | break; |
113 | } |
114 | } |
115 | |
116 | list_add(new: &producer->node, head: &producers); |
117 | |
118 | mutex_unlock(lock: &lock); |
119 | |
120 | return 0; |
121 | out_err: |
122 | mutex_unlock(lock: &lock); |
123 | module_put(THIS_MODULE); |
124 | return ret; |
125 | } |
126 | EXPORT_SYMBOL_GPL(irq_bypass_register_producer); |
127 | |
128 | /** |
129 | * irq_bypass_unregister_producer - unregister IRQ bypass producer |
130 | * @producer: pointer to producer structure |
131 | * |
132 | * Remove a previously registered IRQ producer from the list of producers |
133 | * and disconnect it from any connected IRQ consumer. |
134 | */ |
135 | void irq_bypass_unregister_producer(struct irq_bypass_producer *producer) |
136 | { |
137 | struct irq_bypass_producer *tmp; |
138 | struct irq_bypass_consumer *consumer; |
139 | |
140 | if (!producer->token) |
141 | return; |
142 | |
143 | might_sleep(); |
144 | |
145 | if (!try_module_get(THIS_MODULE)) |
146 | return; /* nothing in the list anyway */ |
147 | |
148 | mutex_lock(&lock); |
149 | |
150 | list_for_each_entry(tmp, &producers, node) { |
151 | if (tmp->token != producer->token) |
152 | continue; |
153 | |
154 | list_for_each_entry(consumer, &consumers, node) { |
155 | if (consumer->token == producer->token) { |
156 | __disconnect(prod: producer, cons: consumer); |
157 | break; |
158 | } |
159 | } |
160 | |
161 | list_del(entry: &producer->node); |
162 | module_put(THIS_MODULE); |
163 | break; |
164 | } |
165 | |
166 | mutex_unlock(lock: &lock); |
167 | |
168 | module_put(THIS_MODULE); |
169 | } |
170 | EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer); |
171 | |
172 | /** |
173 | * irq_bypass_register_consumer - register IRQ bypass consumer |
174 | * @consumer: pointer to consumer structure |
175 | * |
176 | * Add the provided IRQ consumer to the list of consumers and connect |
177 | * with any matching token found on the IRQ producer list. |
178 | */ |
179 | int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer) |
180 | { |
181 | struct irq_bypass_consumer *tmp; |
182 | struct irq_bypass_producer *producer; |
183 | int ret; |
184 | |
185 | if (!consumer->token || |
186 | !consumer->add_producer || !consumer->del_producer) |
187 | return -EINVAL; |
188 | |
189 | might_sleep(); |
190 | |
191 | if (!try_module_get(THIS_MODULE)) |
192 | return -ENODEV; |
193 | |
194 | mutex_lock(&lock); |
195 | |
196 | list_for_each_entry(tmp, &consumers, node) { |
197 | if (tmp->token == consumer->token || tmp == consumer) { |
198 | ret = -EBUSY; |
199 | goto out_err; |
200 | } |
201 | } |
202 | |
203 | list_for_each_entry(producer, &producers, node) { |
204 | if (producer->token == consumer->token) { |
205 | ret = __connect(prod: producer, cons: consumer); |
206 | if (ret) |
207 | goto out_err; |
208 | break; |
209 | } |
210 | } |
211 | |
212 | list_add(new: &consumer->node, head: &consumers); |
213 | |
214 | mutex_unlock(lock: &lock); |
215 | |
216 | return 0; |
217 | out_err: |
218 | mutex_unlock(lock: &lock); |
219 | module_put(THIS_MODULE); |
220 | return ret; |
221 | } |
222 | EXPORT_SYMBOL_GPL(irq_bypass_register_consumer); |
223 | |
224 | /** |
225 | * irq_bypass_unregister_consumer - unregister IRQ bypass consumer |
226 | * @consumer: pointer to consumer structure |
227 | * |
228 | * Remove a previously registered IRQ consumer from the list of consumers |
229 | * and disconnect it from any connected IRQ producer. |
230 | */ |
231 | void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer) |
232 | { |
233 | struct irq_bypass_consumer *tmp; |
234 | struct irq_bypass_producer *producer; |
235 | |
236 | if (!consumer->token) |
237 | return; |
238 | |
239 | might_sleep(); |
240 | |
241 | if (!try_module_get(THIS_MODULE)) |
242 | return; /* nothing in the list anyway */ |
243 | |
244 | mutex_lock(&lock); |
245 | |
246 | list_for_each_entry(tmp, &consumers, node) { |
247 | if (tmp != consumer) |
248 | continue; |
249 | |
250 | list_for_each_entry(producer, &producers, node) { |
251 | if (producer->token == consumer->token) { |
252 | __disconnect(prod: producer, cons: consumer); |
253 | break; |
254 | } |
255 | } |
256 | |
257 | list_del(entry: &consumer->node); |
258 | module_put(THIS_MODULE); |
259 | break; |
260 | } |
261 | |
262 | mutex_unlock(lock: &lock); |
263 | |
264 | module_put(THIS_MODULE); |
265 | } |
266 | EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer); |
267 | |