162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IRQ offload/bypass manager
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015 Red Hat, Inc.
662306a36Sopenharmony_ci * Copyright (c) 2015 Linaro Ltd.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Various virtualization hardware acceleration techniques allow bypassing or
962306a36Sopenharmony_ci * offloading interrupts received from devices around the host kernel.  Posted
1062306a36Sopenharmony_ci * Interrupts on Intel VT-d systems can allow interrupts to be received
1162306a36Sopenharmony_ci * directly by a virtual machine.  ARM IRQ Forwarding allows forwarded physical
1262306a36Sopenharmony_ci * interrupts to be directly deactivated by the guest.  This manager allows
1362306a36Sopenharmony_ci * interrupt producers and consumers to find each other to enable this sort of
1462306a36Sopenharmony_ci * bypass.
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/irqbypass.h>
1862306a36Sopenharmony_ci#include <linux/list.h>
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/mutex.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
2362306a36Sopenharmony_ciMODULE_DESCRIPTION("IRQ bypass manager utility module");
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic LIST_HEAD(producers);
2662306a36Sopenharmony_cistatic LIST_HEAD(consumers);
2762306a36Sopenharmony_cistatic DEFINE_MUTEX(lock);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* @lock must be held when calling connect */
3062306a36Sopenharmony_cistatic int __connect(struct irq_bypass_producer *prod,
3162306a36Sopenharmony_ci		     struct irq_bypass_consumer *cons)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	int ret = 0;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	if (prod->stop)
3662306a36Sopenharmony_ci		prod->stop(prod);
3762306a36Sopenharmony_ci	if (cons->stop)
3862306a36Sopenharmony_ci		cons->stop(cons);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (prod->add_consumer)
4162306a36Sopenharmony_ci		ret = prod->add_consumer(prod, cons);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (!ret) {
4462306a36Sopenharmony_ci		ret = cons->add_producer(cons, prod);
4562306a36Sopenharmony_ci		if (ret && prod->del_consumer)
4662306a36Sopenharmony_ci			prod->del_consumer(prod, cons);
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (cons->start)
5062306a36Sopenharmony_ci		cons->start(cons);
5162306a36Sopenharmony_ci	if (prod->start)
5262306a36Sopenharmony_ci		prod->start(prod);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return ret;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/* @lock must be held when calling disconnect */
5862306a36Sopenharmony_cistatic void __disconnect(struct irq_bypass_producer *prod,
5962306a36Sopenharmony_ci			 struct irq_bypass_consumer *cons)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	if (prod->stop)
6262306a36Sopenharmony_ci		prod->stop(prod);
6362306a36Sopenharmony_ci	if (cons->stop)
6462306a36Sopenharmony_ci		cons->stop(cons);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	cons->del_producer(cons, prod);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (prod->del_consumer)
6962306a36Sopenharmony_ci		prod->del_consumer(prod, cons);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (cons->start)
7262306a36Sopenharmony_ci		cons->start(cons);
7362306a36Sopenharmony_ci	if (prod->start)
7462306a36Sopenharmony_ci		prod->start(prod);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/**
7862306a36Sopenharmony_ci * irq_bypass_register_producer - register IRQ bypass producer
7962306a36Sopenharmony_ci * @producer: pointer to producer structure
8062306a36Sopenharmony_ci *
8162306a36Sopenharmony_ci * Add the provided IRQ producer to the list of producers and connect
8262306a36Sopenharmony_ci * with any matching token found on the IRQ consumers list.
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_ciint irq_bypass_register_producer(struct irq_bypass_producer *producer)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct irq_bypass_producer *tmp;
8762306a36Sopenharmony_ci	struct irq_bypass_consumer *consumer;
8862306a36Sopenharmony_ci	int ret;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (!producer->token)
9162306a36Sopenharmony_ci		return -EINVAL;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	might_sleep();
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!try_module_get(THIS_MODULE))
9662306a36Sopenharmony_ci		return -ENODEV;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	mutex_lock(&lock);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	list_for_each_entry(tmp, &producers, node) {
10162306a36Sopenharmony_ci		if (tmp->token == producer->token) {
10262306a36Sopenharmony_ci			ret = -EBUSY;
10362306a36Sopenharmony_ci			goto out_err;
10462306a36Sopenharmony_ci		}
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	list_for_each_entry(consumer, &consumers, node) {
10862306a36Sopenharmony_ci		if (consumer->token == producer->token) {
10962306a36Sopenharmony_ci			ret = __connect(producer, consumer);
11062306a36Sopenharmony_ci			if (ret)
11162306a36Sopenharmony_ci				goto out_err;
11262306a36Sopenharmony_ci			break;
11362306a36Sopenharmony_ci		}
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	list_add(&producer->node, &producers);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	mutex_unlock(&lock);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return 0;
12162306a36Sopenharmony_ciout_err:
12262306a36Sopenharmony_ci	mutex_unlock(&lock);
12362306a36Sopenharmony_ci	module_put(THIS_MODULE);
12462306a36Sopenharmony_ci	return ret;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(irq_bypass_register_producer);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/**
12962306a36Sopenharmony_ci * irq_bypass_unregister_producer - unregister IRQ bypass producer
13062306a36Sopenharmony_ci * @producer: pointer to producer structure
13162306a36Sopenharmony_ci *
13262306a36Sopenharmony_ci * Remove a previously registered IRQ producer from the list of producers
13362306a36Sopenharmony_ci * and disconnect it from any connected IRQ consumer.
13462306a36Sopenharmony_ci */
13562306a36Sopenharmony_civoid irq_bypass_unregister_producer(struct irq_bypass_producer *producer)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	struct irq_bypass_producer *tmp;
13862306a36Sopenharmony_ci	struct irq_bypass_consumer *consumer;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!producer->token)
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	might_sleep();
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (!try_module_get(THIS_MODULE))
14662306a36Sopenharmony_ci		return; /* nothing in the list anyway */
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	mutex_lock(&lock);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	list_for_each_entry(tmp, &producers, node) {
15162306a36Sopenharmony_ci		if (tmp->token != producer->token)
15262306a36Sopenharmony_ci			continue;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci		list_for_each_entry(consumer, &consumers, node) {
15562306a36Sopenharmony_ci			if (consumer->token == producer->token) {
15662306a36Sopenharmony_ci				__disconnect(producer, consumer);
15762306a36Sopenharmony_ci				break;
15862306a36Sopenharmony_ci			}
15962306a36Sopenharmony_ci		}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		list_del(&producer->node);
16262306a36Sopenharmony_ci		module_put(THIS_MODULE);
16362306a36Sopenharmony_ci		break;
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	mutex_unlock(&lock);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	module_put(THIS_MODULE);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(irq_bypass_unregister_producer);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci/**
17362306a36Sopenharmony_ci * irq_bypass_register_consumer - register IRQ bypass consumer
17462306a36Sopenharmony_ci * @consumer: pointer to consumer structure
17562306a36Sopenharmony_ci *
17662306a36Sopenharmony_ci * Add the provided IRQ consumer to the list of consumers and connect
17762306a36Sopenharmony_ci * with any matching token found on the IRQ producer list.
17862306a36Sopenharmony_ci */
17962306a36Sopenharmony_ciint irq_bypass_register_consumer(struct irq_bypass_consumer *consumer)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct irq_bypass_consumer *tmp;
18262306a36Sopenharmony_ci	struct irq_bypass_producer *producer;
18362306a36Sopenharmony_ci	int ret;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (!consumer->token ||
18662306a36Sopenharmony_ci	    !consumer->add_producer || !consumer->del_producer)
18762306a36Sopenharmony_ci		return -EINVAL;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	might_sleep();
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (!try_module_get(THIS_MODULE))
19262306a36Sopenharmony_ci		return -ENODEV;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	mutex_lock(&lock);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	list_for_each_entry(tmp, &consumers, node) {
19762306a36Sopenharmony_ci		if (tmp->token == consumer->token || tmp == consumer) {
19862306a36Sopenharmony_ci			ret = -EBUSY;
19962306a36Sopenharmony_ci			goto out_err;
20062306a36Sopenharmony_ci		}
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	list_for_each_entry(producer, &producers, node) {
20462306a36Sopenharmony_ci		if (producer->token == consumer->token) {
20562306a36Sopenharmony_ci			ret = __connect(producer, consumer);
20662306a36Sopenharmony_ci			if (ret)
20762306a36Sopenharmony_ci				goto out_err;
20862306a36Sopenharmony_ci			break;
20962306a36Sopenharmony_ci		}
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	list_add(&consumer->node, &consumers);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	mutex_unlock(&lock);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ciout_err:
21862306a36Sopenharmony_ci	mutex_unlock(&lock);
21962306a36Sopenharmony_ci	module_put(THIS_MODULE);
22062306a36Sopenharmony_ci	return ret;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(irq_bypass_register_consumer);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci/**
22562306a36Sopenharmony_ci * irq_bypass_unregister_consumer - unregister IRQ bypass consumer
22662306a36Sopenharmony_ci * @consumer: pointer to consumer structure
22762306a36Sopenharmony_ci *
22862306a36Sopenharmony_ci * Remove a previously registered IRQ consumer from the list of consumers
22962306a36Sopenharmony_ci * and disconnect it from any connected IRQ producer.
23062306a36Sopenharmony_ci */
23162306a36Sopenharmony_civoid irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	struct irq_bypass_consumer *tmp;
23462306a36Sopenharmony_ci	struct irq_bypass_producer *producer;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (!consumer->token)
23762306a36Sopenharmony_ci		return;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	might_sleep();
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (!try_module_get(THIS_MODULE))
24262306a36Sopenharmony_ci		return; /* nothing in the list anyway */
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	mutex_lock(&lock);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	list_for_each_entry(tmp, &consumers, node) {
24762306a36Sopenharmony_ci		if (tmp != consumer)
24862306a36Sopenharmony_ci			continue;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci		list_for_each_entry(producer, &producers, node) {
25162306a36Sopenharmony_ci			if (producer->token == consumer->token) {
25262306a36Sopenharmony_ci				__disconnect(producer, consumer);
25362306a36Sopenharmony_ci				break;
25462306a36Sopenharmony_ci			}
25562306a36Sopenharmony_ci		}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci		list_del(&consumer->node);
25862306a36Sopenharmony_ci		module_put(THIS_MODULE);
25962306a36Sopenharmony_ci		break;
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	mutex_unlock(&lock);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	module_put(THIS_MODULE);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);
267