linux/virt/lib/irqbypass.c
<<
>>
Prefs
   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
  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 producer
  79 * @producer: pointer to producer structure
  80 *
  81 * Add the provided IRQ producer to the list of producers and connect
  82 * 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
  89        if (!producer->token)
  90                return -EINVAL;
  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}
 125EXPORT_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 */
 134void irq_bypass_unregister_producer(struct irq_bypass_producer *producer)
 135{
 136        struct irq_bypass_producer *tmp;
 137        struct irq_bypass_consumer *consumer;
 138
 139        if (!producer->token)
 140                return;
 141
 142        might_sleep();
 143
 144        if (!try_module_get(THIS_MODULE))
 145                return; /* nothing in the list anyway */
 146
 147        mutex_lock(&lock);
 148
 149        list_for_each_entry(tmp, &producers, node) {
 150                if (tmp->token != producer->token)
 151                        continue;
 152
 153                list_for_each_entry(consumer, &consumers, node) {
 154                        if (consumer->token == producer->token) {
 155                                __disconnect(producer, consumer);
 156                                break;
 157                        }
 158                }
 159
 160                list_del(&producer->node);
 161                module_put(THIS_MODULE);
 162                break;
 163        }
 164
 165        mutex_unlock(&lock);
 166
 167        module_put(THIS_MODULE);
 168}
 169EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer);
 170
 171/**
 172 * irq_bypass_register_consumer - register IRQ bypass consumer
 173 * @consumer: pointer to consumer structure
 174 *
 175 * Add the provided IRQ consumer to the list of consumers and connect
 176 * with any matching token found on the IRQ producer list.
 177 */
 178int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer)
 179{
 180        struct irq_bypass_consumer *tmp;
 181        struct irq_bypass_producer *producer;
 182
 183        if (!consumer->token ||
 184            !consumer->add_producer || !consumer->del_producer)
 185                return -EINVAL;
 186
 187        might_sleep();
 188
 189        if (!try_module_get(THIS_MODULE))
 190                return -ENODEV;
 191
 192        mutex_lock(&lock);
 193
 194        list_for_each_entry(tmp, &consumers, node) {
 195                if (tmp->token == consumer->token || tmp == consumer) {
 196                        mutex_unlock(&lock);
 197                        module_put(THIS_MODULE);
 198                        return -EBUSY;
 199                }
 200        }
 201
 202        list_for_each_entry(producer, &producers, node) {
 203                if (producer->token == consumer->token) {
 204                        int ret = __connect(producer, consumer);
 205                        if (ret) {
 206                                mutex_unlock(&lock);
 207                                module_put(THIS_MODULE);
 208                                return ret;
 209                        }
 210                        break;
 211                }
 212        }
 213
 214        list_add(&consumer->node, &consumers);
 215
 216        mutex_unlock(&lock);
 217
 218        return 0;
 219}
 220EXPORT_SYMBOL_GPL(irq_bypass_register_consumer);
 221
 222/**
 223 * irq_bypass_unregister_consumer - unregister IRQ bypass consumer
 224 * @consumer: pointer to consumer structure
 225 *
 226 * Remove a previously registered IRQ consumer from the list of consumers
 227 * and disconnect it from any connected IRQ producer.
 228 */
 229void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer)
 230{
 231        struct irq_bypass_consumer *tmp;
 232        struct irq_bypass_producer *producer;
 233
 234        if (!consumer->token)
 235                return;
 236
 237        might_sleep();
 238
 239        if (!try_module_get(THIS_MODULE))
 240                return; /* nothing in the list anyway */
 241
 242        mutex_lock(&lock);
 243
 244        list_for_each_entry(tmp, &consumers, node) {
 245                if (tmp != consumer)
 246                        continue;
 247
 248                list_for_each_entry(producer, &producers, node) {
 249                        if (producer->token == consumer->token) {
 250                                __disconnect(producer, consumer);
 251                                break;
 252                        }
 253                }
 254
 255                list_del(&consumer->node);
 256                module_put(THIS_MODULE);
 257                break;
 258        }
 259
 260        mutex_unlock(&lock);
 261
 262        module_put(THIS_MODULE);
 263}
 264EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);
 265