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                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);
  54err_add_producer:
  55        if (prod->del_consumer)
  56                prod->del_consumer(prod, cons);
  57err_add_consumer:
  58        return ret;
  59}
  60
  61/* @lock must be held when calling disconnect */
  62static 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 */
  88int 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;
 125out_err:
 126        mutex_unlock(&lock);
 127        module_put(THIS_MODULE);
 128        return ret;
 129}
 130EXPORT_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 */
 139void 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}
 174EXPORT_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 */
 183int 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;
 221out_err:
 222        mutex_unlock(&lock);
 223        module_put(THIS_MODULE);
 224        return ret;
 225}
 226EXPORT_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 */
 235void 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}
 270EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);
 271