Some interrupt controllers are multiplexing several peripheral IRQs on a single interrupt line. While this is not a problem for most IRQs (as long as all peripherals request the interrupt with IRQF_SHARED flag set), multiplexing timers and other type of peripherals will generate a WARNING (mixing IRQF_NO_SUSPEND and !IRQF_NO_SUSPEND is prohibited). Create a dumb irq demultiplexer which simply forwards interrupts to all peripherals (exactly what's happening with IRQ_SHARED) but keep a unique irq number for each peripheral, thus preventing the IRQF_NO_SUSPEND and !IRQF_NO_SUSPEND mix on a given interrupt. Signed-off-by: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxxxxxxx> Acked-by: Nicolas Ferre <nicolas.ferre@xxxxxxxxx> --- drivers/irqchip/Kconfig | 4 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-virt-demux.c | 72 ++++++++++++++++++++ include/linux/irq.h | 38 +++++++++++ include/linux/irqdomain.h | 1 + kernel/irq/Kconfig | 5 ++ kernel/irq/Makefile | 1 + kernel/irq/chip.c | 41 ++++++++++++ kernel/irq/handle.c | 31 ++++++++- kernel/irq/internals.h | 3 + kernel/irq/virt-demux-chip.c | 140 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 drivers/irqchip/irq-virt-demux.c create mode 100644 kernel/irq/virt-demux-chip.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index cc79d2a..268f01f 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -70,6 +70,10 @@ config BRCMSTB_L2_IRQ select GENERIC_IRQ_CHIP select IRQ_DOMAIN +config VIRT_DEMUX_IRQ + bool + select VIRT_IRQ_DEMUX_CHIP + config DW_APB_ICTL bool select GENERIC_IRQ_CHIP diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 9516a32..ee81262 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARCH_MVEBU) += irq-armada-370-xp.o obj-$(CONFIG_ARCH_MXS) += irq-mxs.o obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o obj-$(CONFIG_DW_APB_ICTL) += irq-dw-apb-ictl.o +obj-$(CONFIG_VIRT_DEMUX_IRQ) += irq-virt-demux.o obj-$(CONFIG_METAG) += irq-metag-ext.o obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o obj-$(CONFIG_ARCH_MOXART) += irq-moxart.o diff --git a/drivers/irqchip/irq-virt-demux.c b/drivers/irqchip/irq-virt-demux.c new file mode 100644 index 0000000..501978e --- /dev/null +++ b/drivers/irqchip/irq-virt-demux.c @@ -0,0 +1,72 @@ +/* + * Virtual irq demux chip driver + * + * Copyright (C) 2015 Atmel + * + * Author: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxxxxxxx> + * + * This file is licensed under GPLv2. + */ +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of_irq.h> +#include <linux/slab.h> + +#include "irqchip.h" + +static int __init virt_irq_demux_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_virt_demux *demux; + unsigned int irq; + u32 valid_irqs; + int ret; + + irq = irq_of_parse_and_map(node, 0); + if (!irq) { + pr_err("Failed to retrieve virt irq demuxer source\n"); + return -EINVAL; + } + + ret = of_property_read_u32(node, "irqs", &valid_irqs); + if (ret) { + pr_err("Invalid of missing 'irqs' property\n"); + return ret; + } + + demux = irq_alloc_virt_demux_chip(irq, valid_irqs, + IRQ_NOREQUEST | IRQ_NOPROBE | + IRQ_NOAUTOEN, 0); + if (!demux) { + pr_err("Failed to allocate virt irq demuxer struct\n"); + return -ENOMEM; + } + + demux->domain = irq_domain_add_linear(node, BITS_PER_LONG, + &irq_virt_demux_domain_ops, + demux); + if (!demux->domain) { + ret = -ENOMEM; + goto err_free_demux; + } + + ret = irq_set_handler_data(irq, demux); + if (ret) { + pr_err("Failed to assign handler data\n"); + goto err_free_domain; + } + + irq_set_chained_handler_nostartup(irq, irq_virt_demux_handler); + + return 0; + +err_free_domain: + irq_domain_remove(demux->domain); + +err_free_demux: + kfree(demux); + + return ret; +} +IRQCHIP_DECLARE(virt_irq_demux, "virtual,irq-demux", virt_irq_demux_of_init); diff --git a/include/linux/irq.h b/include/linux/irq.h index 247b2d1..b703093 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -445,6 +445,10 @@ extern void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc); extern void handle_edge_irq(unsigned int irq, struct irq_desc *desc); extern void handle_edge_eoi_irq(unsigned int irq, struct irq_desc *desc); extern void handle_simple_irq(unsigned int irq, struct irq_desc *desc); +#ifdef CONFIG_VIRT_IRQ_DEMUX_CHIP +extern irqreturn_t handle_virt_demux_irq(unsigned int irq, + struct irq_desc *desc); +#endif extern void handle_percpu_irq(unsigned int irq, struct irq_desc *desc); extern void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc); extern void handle_bad_irq(unsigned int irq, struct irq_desc *desc); @@ -880,4 +884,38 @@ static inline u32 irq_reg_readl(struct irq_chip_generic *gc, return readl(gc->reg_base + reg_offset); } +#ifdef CONFIG_VIRT_IRQ_DEMUX_CHIP +/** + * struct irq_chip_virt_demux - Dumb demultiplexer irq chip data structure + * @domain: irq domain pointer + * @available: Bitfield of valid irqs + * @unmasked: Bitfield containing irqs status + * @flags: irq_virt_demux_flags flags + * @src_irq: irq feeding the virt demux chip + * + * Note, that irq_chip_generic can have multiple irq_chip_type + * implementations which can be associated to a particular irq line of + * an irq_chip_generic instance. That allows to share and protect + * state in an irq_chip_generic instance when we need to implement + * different flow mechanisms (level/edge) for it. + */ +struct irq_chip_virt_demux { + struct irq_domain *domain; + unsigned long available; + unsigned long unmasked; + unsigned int flags; + unsigned int src_irq; + unsigned int irq_flags_to_clear; + unsigned int irq_flags_to_set; +}; + +void irq_virt_demux_handler(unsigned int irq, struct irq_desc *desc); + +struct irq_chip_virt_demux * +irq_alloc_virt_demux_chip(unsigned int src_irq, + unsigned long valid_irqs, + unsigned int clr_flags, + unsigned int set_flags); +#endif /* CONFIG_VIRT_IRQ_DEMUX_CHIP */ + #endif /* _LINUX_IRQ_H */ diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 676d730..93d62bd 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -80,6 +80,7 @@ struct irq_domain_ops { }; extern struct irq_domain_ops irq_generic_chip_ops; +extern struct irq_domain_ops irq_virt_demux_domain_ops; struct irq_domain_chip_generic; diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 9a76e3b..506644f 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -51,6 +51,11 @@ config GENERIC_IRQ_CHIP bool select IRQ_DOMAIN +# Dumb interrupt demuxer chip implementation +config VIRT_IRQ_DEMUX_CHIP + bool + select IRQ_DOMAIN + # Generic irq_domain hw <--> linux irq number translation config IRQ_DOMAIN bool diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index d121235..8c46994 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o obj-$(CONFIG_PM_SLEEP) += pm.o obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o +obj-$(CONFIG_VIRT_IRQ_DEMUX_CHIP) += virt-demux-chip.o diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 5de82dc0..38abf8d 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -405,6 +405,47 @@ out_unlock: } EXPORT_SYMBOL_GPL(handle_simple_irq); +#ifdef CONFIG_VIRT_IRQ_DEMUX_CHIP +/** + * handle_virt_demux_irq - Dumb demuxer irq handle function. + * @irq: the interrupt number + * @desc: the interrupt description structure for this irq + * + * Dumb demux interrupts are sent from a demultiplexing interrupt handler + * which is not able to decide which child interrupt handler should be + * called. + * + * Note: The caller is expected to handle the ack, clear, mask and + * unmask issues if necessary. + */ +irqreturn_t +handle_virt_demux_irq(unsigned int irq, struct irq_desc *desc) +{ + irqreturn_t retval = IRQ_NONE; + + raw_spin_lock(&desc->lock); + + if (!irq_may_run(desc)) + goto out_unlock; + + desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); + kstat_incr_irqs_this_cpu(irq, desc); + + if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { + desc->istate |= IRQS_PENDING; + goto out_unlock; + } + + retval = handle_irq_event_no_spurious_check(desc); + +out_unlock: + raw_spin_unlock(&desc->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(handle_virt_demux_irq); +#endif /* CONFIG_VIRT_IRQ_DEMUX_CHIP */ + /* * Called unconditionally from handle_level_irq() and only for oneshot * interrupts from handle_fasteoi_irq() diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c index 6354802..f786850 100644 --- a/kernel/irq/handle.c +++ b/kernel/irq/handle.c @@ -131,7 +131,8 @@ void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action) } irqreturn_t -handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action) +handle_irq_event_percpu_no_spurious_check(struct irq_desc *desc, + struct irqaction *action) { irqreturn_t retval = IRQ_NONE; unsigned int flags = 0, irq = desc->irq_data.irq; @@ -175,8 +176,18 @@ handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action) add_interrupt_randomness(irq, flags); + return retval; +} + +irqreturn_t +handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action) +{ + irqreturn_t retval; + + retval = handle_irq_event_percpu_no_spurious_check(desc, action); + if (!noirqdebug) - note_interrupt(irq, desc, retval); + note_interrupt(desc->irq_data.irq, desc, retval); return retval; } @@ -195,3 +206,19 @@ irqreturn_t handle_irq_event(struct irq_desc *desc) irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); return ret; } + +irqreturn_t handle_irq_event_no_spurious_check(struct irq_desc *desc) +{ + struct irqaction *action = desc->action; + irqreturn_t ret; + + desc->istate &= ~IRQS_PENDING; + irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); + raw_spin_unlock(&desc->lock); + + ret = handle_irq_event_percpu_no_spurious_check(desc, action); + + raw_spin_lock(&desc->lock); + irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); + return ret; +} diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index df553b0..fe056fb 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -90,6 +90,9 @@ extern void init_kstat_irqs(struct irq_desc *desc, int node, int nr); irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action); irqreturn_t handle_irq_event(struct irq_desc *desc); +irqreturn_t handle_irq_event_percpu_no_spurious_check(struct irq_desc *desc, + struct irqaction *action); +irqreturn_t handle_irq_event_no_spurious_check(struct irq_desc *desc); /* Resending of interrupts :*/ void check_irq_resend(struct irq_desc *desc, unsigned int irq); diff --git a/kernel/irq/virt-demux-chip.c b/kernel/irq/virt-demux-chip.c new file mode 100644 index 0000000..31f35f0 --- /dev/null +++ b/kernel/irq/virt-demux-chip.c @@ -0,0 +1,140 @@ +/* + * Library implementing common virtual irq demux chip functions + * + * Copyright (C) 2015 Atmel + * + * Author: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxxxxxxx> + * + * This file is licensed under GPLv2. + */ +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/syscore_ops.h> + +#include "internals.h" + +static void irq_virt_demux_mask(struct irq_data *d) +{ + struct irq_chip_virt_demux *demux = irq_data_get_irq_chip_data(d); + + clear_bit(d->hwirq, &demux->unmasked); + + if (!demux->unmasked) + disable_irq_nosync(demux->src_irq); +} + +static void irq_virt_demux_unmask(struct irq_data *d) +{ + struct irq_chip_virt_demux *demux = irq_data_get_irq_chip_data(d); + bool enable_src_irq = !demux->unmasked; + + set_bit(d->hwirq, &demux->unmasked); + + if (enable_src_irq) + enable_irq(demux->src_irq); +} + +static struct irq_chip irq_virt_demux_chip = { + .name = "virt-demux-irq", + .irq_mask = irq_virt_demux_mask, + .irq_unmask = irq_virt_demux_unmask, +}; + +/* + * Separate lockdep class for interrupt chip which can nest irq_desc + * lock. + */ +static struct lock_class_key irq_nested_lock_class; + +/* + * irq_map_virt_demux_chip - Map a virt demux chip for an irq domain + */ +static int irq_map_virt_demux_chip(struct irq_domain *d, + unsigned int virq, + irq_hw_number_t hw_irq) +{ + struct irq_chip_virt_demux *demux = d->host_data; + + if (!test_bit(hw_irq, &demux->available)) + return -EINVAL; + + irq_set_lockdep_class(virq, &irq_nested_lock_class); + + irq_set_chip(virq, &irq_virt_demux_chip); + irq_set_chip_data(virq, demux); + irq_modify_status(virq, demux->irq_flags_to_clear, + demux->irq_flags_to_set); + + return 0; +} + +struct irq_domain_ops irq_virt_demux_domain_ops = { + .map = irq_map_virt_demux_chip, + .xlate = irq_domain_xlate_onecell, +}; +EXPORT_SYMBOL_GPL(irq_virt_demux_domain_ops); + +/** + * irq_virt_demux_handler - Dumb demux flow handler + * @irq: Virtual irq number + * @irq_desc: irq descriptor + */ +void irq_virt_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct irq_chip_virt_demux *demux = irq_get_handler_data(irq); + struct irq_chip *chip = irq_desc_get_chip(desc); + irqreturn_t ret = IRQ_NONE; + int i; + + chained_irq_enter(chip, desc); + for_each_set_bit(i, &demux->unmasked, BITS_PER_LONG) { + int demuxed_irq = irq_find_mapping(demux->domain, i); + struct irq_desc *desc = irq_to_desc(demuxed_irq); + + ret |= handle_virt_demux_irq(demuxed_irq, desc); + } + chained_irq_exit(chip, desc); + + if (!noirqdebug) + note_interrupt(irq, desc, ret); +} +EXPORT_SYMBOL_GPL(irq_virt_demux_handler); + +/** + * irq_alloc_virt_demux_chip - Allocate a virt demux chip + * @src_irq: irq feeding the virt demux chip + * @valid_irqs: Bitmask representing valid irqs + * @clr_flags: irq_flags to clear when mapping an interrupt + * @set_flags: irq_flags to set when mapping an interrupt + */ +struct irq_chip_virt_demux * +irq_alloc_virt_demux_chip(unsigned int src_irq, + unsigned long valid_irqs, + unsigned int clr_flags, + unsigned int set_flags) +{ + struct irq_chip_virt_demux *demux; + + if (!src_irq) + return ERR_PTR(-EINVAL); + + demux = kzalloc(sizeof(*demux), GFP_KERNEL); + if (!demux) + return ERR_PTR(-ENOMEM); + + demux->available = valid_irqs; + demux->src_irq = src_irq; + demux->irq_flags_to_clear = clr_flags; + demux->irq_flags_to_set = set_flags; + + return demux; +} +EXPORT_SYMBOL_GPL(irq_alloc_virt_demux_chip); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html