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