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 might_sleep(); 93 94 if (!try_module_get(THIS_MODULE)) 95 return -ENODEV; 96 97 mutex_lock(&lock); 98 99 list_for_each_entry(tmp, &producers, node) { 100 if (tmp->token == producer->token) { 101 mutex_unlock(&lock); 102 module_put(THIS_MODULE); 103 return -EBUSY; 104 } 105 } 106 107 list_for_each_entry(consumer, &consumers, node) { 108 if (consumer->token == producer->token) { 109 int ret = __connect(producer, consumer); 110 if (ret) { 111 mutex_unlock(&lock); 112 module_put(THIS_MODULE); 113 return ret; 114 } 115 break; 116 } 117 } 118 119 list_add(&producer->node, &producers); 120 121 mutex_unlock(&lock); 122 123 return 0; 124 } 125 EXPORT_SYMBOL_GPL(irq_bypass_register_producer); 126 127 /** 128 * irq_bypass_unregister_producer - unregister IRQ bypass producer 129 * @producer: pointer to producer structure 130 * 131 * Remove a previously registered IRQ producer from the list of producers 132 * and disconnect it from any connected IRQ consumer. 133 */ 134 void irq_bypass_unregister_producer(struct irq_bypass_producer *producer) 135 { 136 struct irq_bypass_producer *tmp; 137 struct irq_bypass_consumer *consumer; 138 139 might_sleep(); 140 141 if (!try_module_get(THIS_MODULE)) 142 return; /* nothing in the list anyway */ 143 144 mutex_lock(&lock); 145 146 list_for_each_entry(tmp, &producers, node) { 147 if (tmp->token != producer->token) 148 continue; 149 150 list_for_each_entry(consumer, &consumers, node) { 151 if (consumer->token == producer->token) { 152 __disconnect(producer, consumer); 153 break; 154 } 155 } 156 157 list_del(&producer->node); 158 module_put(THIS_MODULE); 159 break; 160 } 161 162 mutex_unlock(&lock); 163 164 module_put(THIS_MODULE); 165 } 166 EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer); 167 168 /** 169 * irq_bypass_register_consumer - register IRQ bypass consumer 170 * @consumer: pointer to consumer structure 171 * 172 * Add the provided IRQ consumer to the list of consumers and connect 173 * with any matching token found on the IRQ producer list. 174 */ 175 int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer) 176 { 177 struct irq_bypass_consumer *tmp; 178 struct irq_bypass_producer *producer; 179 180 if (!consumer->add_producer || !consumer->del_producer) 181 return -EINVAL; 182 183 might_sleep(); 184 185 if (!try_module_get(THIS_MODULE)) 186 return -ENODEV; 187 188 mutex_lock(&lock); 189 190 list_for_each_entry(tmp, &consumers, node) { 191 if (tmp->token == consumer->token) { 192 mutex_unlock(&lock); 193 module_put(THIS_MODULE); 194 return -EBUSY; 195 } 196 } 197 198 list_for_each_entry(producer, &producers, node) { 199 if (producer->token == consumer->token) { 200 int ret = __connect(producer, consumer); 201 if (ret) { 202 mutex_unlock(&lock); 203 module_put(THIS_MODULE); 204 return ret; 205 } 206 break; 207 } 208 } 209 210 list_add(&consumer->node, &consumers); 211 212 mutex_unlock(&lock); 213 214 return 0; 215 } 216 EXPORT_SYMBOL_GPL(irq_bypass_register_consumer); 217 218 /** 219 * irq_bypass_unregister_consumer - unregister IRQ bypass consumer 220 * @consumer: pointer to consumer structure 221 * 222 * Remove a previously registered IRQ consumer from the list of consumers 223 * and disconnect it from any connected IRQ producer. 224 */ 225 void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer) 226 { 227 struct irq_bypass_consumer *tmp; 228 struct irq_bypass_producer *producer; 229 230 might_sleep(); 231 232 if (!try_module_get(THIS_MODULE)) 233 return; /* nothing in the list anyway */ 234 235 mutex_lock(&lock); 236 237 list_for_each_entry(tmp, &consumers, node) { 238 if (tmp->token != consumer->token) 239 continue; 240 241 list_for_each_entry(producer, &producers, node) { 242 if (producer->token == consumer->token) { 243 __disconnect(producer, consumer); 244 break; 245 } 246 } 247 248 list_del(&consumer->node); 249 module_put(THIS_MODULE); 250 break; 251 } 252 253 mutex_unlock(&lock); 254 255 module_put(THIS_MODULE); 256 } 257 EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer); 258