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