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