267 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * IRQ offload/bypass manager4 *5 * Copyright (C) 2015 Red Hat, Inc.6 * Copyright (c) 2015 Linaro Ltd.7 *8 * Various virtualization hardware acceleration techniques allow bypassing or9 * offloading interrupts received from devices around the host kernel. Posted10 * Interrupts on Intel VT-d systems can allow interrupts to be received11 * directly by a virtual machine. ARM IRQ Forwarding allows forwarded physical12 * interrupts to be directly deactivated by the guest. This manager allows13 * interrupt producers and consumers to find each other to enable this sort of14 * bypass.15 */16 17#include <linux/irqbypass.h>18#include <linux/list.h>19#include <linux/module.h>20#include <linux/mutex.h>21 22MODULE_LICENSE("GPL v2");23MODULE_DESCRIPTION("IRQ bypass manager utility module");24 25static LIST_HEAD(producers);26static LIST_HEAD(consumers);27static DEFINE_MUTEX(lock);28 29/* @lock must be held when calling connect */30static 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 return ret;55}56 57/* @lock must be held when calling disconnect */58static void __disconnect(struct irq_bypass_producer *prod,59 struct irq_bypass_consumer *cons)60{61 if (prod->stop)62 prod->stop(prod);63 if (cons->stop)64 cons->stop(cons);65 66 cons->del_producer(cons, prod);67 68 if (prod->del_consumer)69 prod->del_consumer(prod, cons);70 71 if (cons->start)72 cons->start(cons);73 if (prod->start)74 prod->start(prod);75}76 77/**78 * irq_bypass_register_producer - register IRQ bypass producer79 * @producer: pointer to producer structure80 *81 * Add the provided IRQ producer to the list of producers and connect82 * with any matching token found on the IRQ consumers list.83 */84int irq_bypass_register_producer(struct irq_bypass_producer *producer)85{86 struct irq_bypass_producer *tmp;87 struct irq_bypass_consumer *consumer;88 int ret;89 90 if (!producer->token)91 return -EINVAL;92 93 might_sleep();94 95 if (!try_module_get(THIS_MODULE))96 return -ENODEV;97 98 mutex_lock(&lock);99 100 list_for_each_entry(tmp, &producers, node) {101 if (tmp->token == producer->token) {102 ret = -EBUSY;103 goto out_err;104 }105 }106 107 list_for_each_entry(consumer, &consumers, node) {108 if (consumer->token == producer->token) {109 ret = __connect(producer, consumer);110 if (ret)111 goto out_err;112 break;113 }114 }115 116 list_add(&producer->node, &producers);117 118 mutex_unlock(&lock);119 120 return 0;121out_err:122 mutex_unlock(&lock);123 module_put(THIS_MODULE);124 return ret;125}126EXPORT_SYMBOL_GPL(irq_bypass_register_producer);127 128/**129 * irq_bypass_unregister_producer - unregister IRQ bypass producer130 * @producer: pointer to producer structure131 *132 * Remove a previously registered IRQ producer from the list of producers133 * and disconnect it from any connected IRQ consumer.134 */135void irq_bypass_unregister_producer(struct irq_bypass_producer *producer)136{137 struct irq_bypass_producer *tmp;138 struct irq_bypass_consumer *consumer;139 140 if (!producer->token)141 return;142 143 might_sleep();144 145 if (!try_module_get(THIS_MODULE))146 return; /* nothing in the list anyway */147 148 mutex_lock(&lock);149 150 list_for_each_entry(tmp, &producers, node) {151 if (tmp->token != producer->token)152 continue;153 154 list_for_each_entry(consumer, &consumers, node) {155 if (consumer->token == producer->token) {156 __disconnect(producer, consumer);157 break;158 }159 }160 161 list_del(&producer->node);162 module_put(THIS_MODULE);163 break;164 }165 166 mutex_unlock(&lock);167 168 module_put(THIS_MODULE);169}170EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer);171 172/**173 * irq_bypass_register_consumer - register IRQ bypass consumer174 * @consumer: pointer to consumer structure175 *176 * Add the provided IRQ consumer to the list of consumers and connect177 * with any matching token found on the IRQ producer list.178 */179int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer)180{181 struct irq_bypass_consumer *tmp;182 struct irq_bypass_producer *producer;183 int ret;184 185 if (!consumer->token ||186 !consumer->add_producer || !consumer->del_producer)187 return -EINVAL;188 189 might_sleep();190 191 if (!try_module_get(THIS_MODULE))192 return -ENODEV;193 194 mutex_lock(&lock);195 196 list_for_each_entry(tmp, &consumers, node) {197 if (tmp->token == consumer->token || tmp == consumer) {198 ret = -EBUSY;199 goto out_err;200 }201 }202 203 list_for_each_entry(producer, &producers, node) {204 if (producer->token == consumer->token) {205 ret = __connect(producer, consumer);206 if (ret)207 goto out_err;208 break;209 }210 }211 212 list_add(&consumer->node, &consumers);213 214 mutex_unlock(&lock);215 216 return 0;217out_err:218 mutex_unlock(&lock);219 module_put(THIS_MODULE);220 return ret;221}222EXPORT_SYMBOL_GPL(irq_bypass_register_consumer);223 224/**225 * irq_bypass_unregister_consumer - unregister IRQ bypass consumer226 * @consumer: pointer to consumer structure227 *228 * Remove a previously registered IRQ consumer from the list of consumers229 * and disconnect it from any connected IRQ producer.230 */231void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer)232{233 struct irq_bypass_consumer *tmp;234 struct irq_bypass_producer *producer;235 236 if (!consumer->token)237 return;238 239 might_sleep();240 241 if (!try_module_get(THIS_MODULE))242 return; /* nothing in the list anyway */243 244 mutex_lock(&lock);245 246 list_for_each_entry(tmp, &consumers, node) {247 if (tmp != consumer)248 continue;249 250 list_for_each_entry(producer, &producers, node) {251 if (producer->token == consumer->token) {252 __disconnect(producer, consumer);253 break;254 }255 }256 257 list_del(&consumer->node);258 module_put(THIS_MODULE);259 break;260 }261 262 mutex_unlock(&lock);263 264 module_put(THIS_MODULE);265}266EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);267