Merge tag 'for-5.10-rc5-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave...
[linux-2.6-block.git] / virt / lib / irqbypass.c
CommitLineData
d2912cb1 1// SPDX-License-Identifier: GPL-2.0-only
f73f8173
AW
2/*
3 * IRQ offload/bypass manager
4 *
5 * Copyright (C) 2015 Red Hat, Inc.
6 * Copyright (c) 2015 Linaro Ltd.
7 *
f73f8173
AW
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
22MODULE_LICENSE("GPL v2");
23MODULE_DESCRIPTION("IRQ bypass manager utility module");
24
25static LIST_HEAD(producers);
26static LIST_HEAD(consumers);
27static DEFINE_MUTEX(lock);
28
29/* @lock must be held when calling connect */
30static 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
a979a6aa
ZL
43 if (ret)
44 goto err_add_consumer;
45
46 ret = cons->add_producer(cons, prod);
47 if (ret)
48 goto err_add_producer;
f73f8173
AW
49
50 if (cons->start)
51 cons->start(cons);
52 if (prod->start)
53 prod->start(prod);
a979a6aa
ZL
54err_add_producer:
55 if (prod->del_consumer)
56 prod->del_consumer(prod, cons);
57err_add_consumer:
f73f8173
AW
58 return ret;
59}
60
61/* @lock must be held when calling disconnect */
62static void __disconnect(struct irq_bypass_producer *prod,
63 struct irq_bypass_consumer *cons)
64{
65 if (prod->stop)
66 prod->stop(prod);
67 if (cons->stop)
68 cons->stop(cons);
69
70 cons->del_producer(cons, prod);
71
72 if (prod->del_consumer)
73 prod->del_consumer(prod, cons);
74
75 if (cons->start)
76 cons->start(cons);
77 if (prod->start)
78 prod->start(prod);
79}
80
81/**
82 * irq_bypass_register_producer - register IRQ bypass producer
83 * @producer: pointer to producer structure
84 *
85 * Add the provided IRQ producer to the list of producers and connect
86 * with any matching token found on the IRQ consumers list.
87 */
88int irq_bypass_register_producer(struct irq_bypass_producer *producer)
89{
90 struct irq_bypass_producer *tmp;
91 struct irq_bypass_consumer *consumer;
bbfdafa8 92 int ret;
f73f8173 93
b52f3ed0
AW
94 if (!producer->token)
95 return -EINVAL;
96
f73f8173
AW
97 might_sleep();
98
99 if (!try_module_get(THIS_MODULE))
100 return -ENODEV;
101
102 mutex_lock(&lock);
103
104 list_for_each_entry(tmp, &producers, node) {
105 if (tmp->token == producer->token) {
bbfdafa8
ML
106 ret = -EBUSY;
107 goto out_err;
f73f8173
AW
108 }
109 }
110
111 list_for_each_entry(consumer, &consumers, node) {
112 if (consumer->token == producer->token) {
bbfdafa8
ML
113 ret = __connect(producer, consumer);
114 if (ret)
115 goto out_err;
f73f8173
AW
116 break;
117 }
118 }
119
120 list_add(&producer->node, &producers);
121
122 mutex_unlock(&lock);
123
124 return 0;
bbfdafa8
ML
125out_err:
126 mutex_unlock(&lock);
127 module_put(THIS_MODULE);
128 return ret;
f73f8173
AW
129}
130EXPORT_SYMBOL_GPL(irq_bypass_register_producer);
131
132/**
133 * irq_bypass_unregister_producer - unregister IRQ bypass producer
134 * @producer: pointer to producer structure
135 *
136 * Remove a previously registered IRQ producer from the list of producers
137 * and disconnect it from any connected IRQ consumer.
138 */
139void irq_bypass_unregister_producer(struct irq_bypass_producer *producer)
140{
141 struct irq_bypass_producer *tmp;
142 struct irq_bypass_consumer *consumer;
143
b52f3ed0
AW
144 if (!producer->token)
145 return;
146
f73f8173
AW
147 might_sleep();
148
149 if (!try_module_get(THIS_MODULE))
150 return; /* nothing in the list anyway */
151
152 mutex_lock(&lock);
153
154 list_for_each_entry(tmp, &producers, node) {
155 if (tmp->token != producer->token)
156 continue;
157
158 list_for_each_entry(consumer, &consumers, node) {
159 if (consumer->token == producer->token) {
160 __disconnect(producer, consumer);
161 break;
162 }
163 }
164
165 list_del(&producer->node);
166 module_put(THIS_MODULE);
167 break;
168 }
169
170 mutex_unlock(&lock);
171
172 module_put(THIS_MODULE);
173}
174EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer);
175
176/**
177 * irq_bypass_register_consumer - register IRQ bypass consumer
178 * @consumer: pointer to consumer structure
179 *
180 * Add the provided IRQ consumer to the list of consumers and connect
181 * with any matching token found on the IRQ producer list.
182 */
183int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer)
184{
185 struct irq_bypass_consumer *tmp;
186 struct irq_bypass_producer *producer;
8262fe85 187 int ret;
f73f8173 188
b52f3ed0
AW
189 if (!consumer->token ||
190 !consumer->add_producer || !consumer->del_producer)
f73f8173
AW
191 return -EINVAL;
192
193 might_sleep();
194
195 if (!try_module_get(THIS_MODULE))
196 return -ENODEV;
197
198 mutex_lock(&lock);
199
200 list_for_each_entry(tmp, &consumers, node) {
4f3dbdf4 201 if (tmp->token == consumer->token || tmp == consumer) {
8262fe85
ML
202 ret = -EBUSY;
203 goto out_err;
f73f8173
AW
204 }
205 }
206
207 list_for_each_entry(producer, &producers, node) {
208 if (producer->token == consumer->token) {
8262fe85
ML
209 ret = __connect(producer, consumer);
210 if (ret)
211 goto out_err;
f73f8173
AW
212 break;
213 }
214 }
215
216 list_add(&consumer->node, &consumers);
217
218 mutex_unlock(&lock);
219
220 return 0;
8262fe85
ML
221out_err:
222 mutex_unlock(&lock);
223 module_put(THIS_MODULE);
224 return ret;
f73f8173
AW
225}
226EXPORT_SYMBOL_GPL(irq_bypass_register_consumer);
227
228/**
229 * irq_bypass_unregister_consumer - unregister IRQ bypass consumer
230 * @consumer: pointer to consumer structure
231 *
232 * Remove a previously registered IRQ consumer from the list of consumers
233 * and disconnect it from any connected IRQ producer.
234 */
235void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer)
236{
237 struct irq_bypass_consumer *tmp;
238 struct irq_bypass_producer *producer;
239
b52f3ed0
AW
240 if (!consumer->token)
241 return;
242
f73f8173
AW
243 might_sleep();
244
245 if (!try_module_get(THIS_MODULE))
246 return; /* nothing in the list anyway */
247
248 mutex_lock(&lock);
249
250 list_for_each_entry(tmp, &consumers, node) {
4f3dbdf4 251 if (tmp != consumer)
f73f8173
AW
252 continue;
253
254 list_for_each_entry(producer, &producers, node) {
255 if (producer->token == consumer->token) {
256 __disconnect(producer, consumer);
257 break;
258 }
259 }
260
261 list_del(&consumer->node);
262 module_put(THIS_MODULE);
263 break;
264 }
265
266 mutex_unlock(&lock);
267
268 module_put(THIS_MODULE);
269}
270EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);