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 goto err_add_consumer; 45 46 ret = cons->add_producer(cons, prod); 47 if (ret) 48 goto err_add_producer; 49 50 if (cons->start) 51 cons->start(cons); 52 if (prod->start) 53 prod->start(prod); 54 err_add_producer: 55 if (prod->del_consumer) 56 prod->del_consumer(prod, cons); 57 err_add_consumer: 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 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 */ 88 int irq_bypass_register_producer(struct irq_bypass_producer *producer) 89 { 90 struct irq_bypass_producer *tmp; 91 struct irq_bypass_consumer *consumer; 92 int ret; 93 94 if (!producer->token) 95 return -EINVAL; 96 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) { 106 ret = -EBUSY; 107 goto out_err; 108 } 109 } 110 111 list_for_each_entry(consumer, &consumers, node) { 112 if (consumer->token == producer->token) { 113 ret = __connect(producer, consumer); 114 if (ret) 115 goto out_err; 116 break; 117 } 118 } 119 120 list_add(&producer->node, &producers); 121 122 mutex_unlock(&lock); 123 124 return 0; 125 out_err: 126 mutex_unlock(&lock); 127 module_put(THIS_MODULE); 128 return ret; 129 } 130 EXPORT_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 */ 139 void irq_bypass_unregister_producer(struct irq_bypass_producer *producer) 140 { 141 struct irq_bypass_producer *tmp; 142 struct irq_bypass_consumer *consumer; 143 144 if (!producer->token) 145 return; 146 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 } 174 EXPORT_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 */ 183 int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer) 184 { 185 struct irq_bypass_consumer *tmp; 186 struct irq_bypass_producer *producer; 187 int ret; 188 189 if (!consumer->token || 190 !consumer->add_producer || !consumer->del_producer) 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) { 201 if (tmp->token == consumer->token || tmp == consumer) { 202 ret = -EBUSY; 203 goto out_err; 204 } 205 } 206 207 list_for_each_entry(producer, &producers, node) { 208 if (producer->token == consumer->token) { 209 ret = __connect(producer, consumer); 210 if (ret) 211 goto out_err; 212 break; 213 } 214 } 215 216 list_add(&consumer->node, &consumers); 217 218 mutex_unlock(&lock); 219 220 return 0; 221 out_err: 222 mutex_unlock(&lock); 223 module_put(THIS_MODULE); 224 return ret; 225 } 226 EXPORT_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 */ 235 void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer) 236 { 237 struct irq_bypass_consumer *tmp; 238 struct irq_bypass_producer *producer; 239 240 if (!consumer->token) 241 return; 242 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) { 251 if (tmp != consumer) 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 } 270 EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer); 271