xref: /linux/virt/lib/irqbypass.c (revision 46a4bfd0ae480cabbacc56fe0d8f91cbe229c7ce)
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 	if (!ret) {
55 		prod->consumer = cons;
56 		cons->producer = prod;
57 	}
58 	return ret;
59 }
60 
61 /* @lock must be held when calling disconnect */
62 static 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 	prod->consumer = NULL;
81 	cons->producer = NULL;
82 }
83 
84 /**
85  * irq_bypass_register_producer - register IRQ bypass producer
86  * @producer: pointer to producer structure
87  * @eventfd: pointer to the eventfd context associated with the producer
88  *
89  * Add the provided IRQ producer to the list of producers and connect
90  * with any matching eventfd found on the IRQ consumers list.
91  */
92 int irq_bypass_register_producer(struct irq_bypass_producer *producer,
93 				 struct eventfd_ctx *eventfd)
94 {
95 	struct irq_bypass_producer *tmp;
96 	struct irq_bypass_consumer *consumer;
97 	int ret;
98 
99 	if (WARN_ON_ONCE(producer->eventfd))
100 		return -EINVAL;
101 
102 	guard(mutex)(&lock);
103 
104 	list_for_each_entry(tmp, &producers, node) {
105 		if (tmp->eventfd == eventfd)
106 			return -EBUSY;
107 	}
108 
109 	list_for_each_entry(consumer, &consumers, node) {
110 		if (consumer->eventfd == eventfd) {
111 			ret = __connect(producer, consumer);
112 			if (ret)
113 				return ret;
114 			break;
115 		}
116 	}
117 
118 	producer->eventfd = eventfd;
119 	list_add(&producer->node, &producers);
120 	return 0;
121 }
122 EXPORT_SYMBOL_GPL(irq_bypass_register_producer);
123 
124 /**
125  * irq_bypass_unregister_producer - unregister IRQ bypass producer
126  * @producer: pointer to producer structure
127  *
128  * Remove a previously registered IRQ producer from the list of producers
129  * and disconnect it from any connected IRQ consumer.
130  */
131 void irq_bypass_unregister_producer(struct irq_bypass_producer *producer)
132 {
133 	if (!producer->eventfd)
134 		return;
135 
136 	guard(mutex)(&lock);
137 
138 	if (producer->consumer)
139 		__disconnect(producer, producer->consumer);
140 
141 	producer->eventfd = NULL;
142 	list_del(&producer->node);
143 }
144 EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer);
145 
146 /**
147  * irq_bypass_register_consumer - register IRQ bypass consumer
148  * @consumer: pointer to consumer structure
149  * @eventfd: pointer to the eventfd context associated with the consumer
150  *
151  * Add the provided IRQ consumer to the list of consumers and connect
152  * with any matching eventfd found on the IRQ producer list.
153  */
154 int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer,
155 				 struct eventfd_ctx *eventfd)
156 {
157 	struct irq_bypass_consumer *tmp;
158 	struct irq_bypass_producer *producer;
159 	int ret;
160 
161 	if (WARN_ON_ONCE(consumer->eventfd))
162 		return -EINVAL;
163 
164 	if (!consumer->add_producer || !consumer->del_producer)
165 		return -EINVAL;
166 
167 	guard(mutex)(&lock);
168 
169 	list_for_each_entry(tmp, &consumers, node) {
170 		if (tmp->eventfd == eventfd)
171 			return -EBUSY;
172 	}
173 
174 	list_for_each_entry(producer, &producers, node) {
175 		if (producer->eventfd == eventfd) {
176 			ret = __connect(producer, consumer);
177 			if (ret)
178 				return ret;
179 			break;
180 		}
181 	}
182 
183 	consumer->eventfd = eventfd;
184 	list_add(&consumer->node, &consumers);
185 	return 0;
186 }
187 EXPORT_SYMBOL_GPL(irq_bypass_register_consumer);
188 
189 /**
190  * irq_bypass_unregister_consumer - unregister IRQ bypass consumer
191  * @consumer: pointer to consumer structure
192  *
193  * Remove a previously registered IRQ consumer from the list of consumers
194  * and disconnect it from any connected IRQ producer.
195  */
196 void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer)
197 {
198 	if (!consumer->eventfd)
199 		return;
200 
201 	guard(mutex)(&lock);
202 
203 	if (consumer->producer)
204 		__disconnect(consumer->producer, consumer);
205 
206 	consumer->eventfd = NULL;
207 	list_del(&consumer->node);
208 }
209 EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);
210