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        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 producer
 130 * @producer: pointer to producer structure
 131 *
 132 * Remove a previously registered IRQ producer from the list of producers
 133 * 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 consumer
 174 * @consumer: pointer to consumer structure
 175 *
 176 * Add the provided IRQ consumer to the list of consumers and connect
 177 * 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 consumer
 226 * @consumer: pointer to consumer structure
 227 *
 228 * Remove a previously registered IRQ consumer from the list of consumers
 229 * 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