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