Message ID | 1420725159-20720-1-git-send-email-boris.brezillon@free-electrons.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Thu, 8 Jan 2015, Boris Brezillon wrote: > 1) Is it close to what you had in mind ? Yes. > 2) I'm not sure which part of the code should go where, so don't hesitate > to point out misplaced sections. Looks sane. Nits below. > 3) Do I need to disable the source irq (the one feeding the irqchip) when > entering suspend (and enable it on resume) ? That probably needs to be part of the dumb mask/unmask functions., i.e. if no demux interrupt is enabled, the source irq should be masked. > 4) I'm not sure of what flags should be set/cleared when mapping an > interrupt. Should I let the caller decide of this config (something > similar to what is done in generic-chip) ? I don't think you need to set/clear anything. Lets look at that dumb demux chip as a real electronic circuit |----------------| | | | --|Unmasked|--|---- Demux0 | | | SRC irq -----|-- -|Unmasked|--|---- Demux1 | | | | --|Unmasked|--|---- Demux2 | | |----------------| Whether a demultiplexed interrupt is mapped or not is not really important. The only relevant information is whether its masked or not. So the default state is masked until a demultiplexed interrupt gets requested. > +/** > + * enum irq_dumb_demux_flags - Initialization flags for generic irq chips > + * @IRQ_DD_INIT_NESTED_LOCK: Set the lock class of the irqs to nested for > + * irq chips which need to call irq_set_wake() on > + * the parent irq. Usually GPIO implementations > + */ > +enum irq_dumb_demux_flags { > + IRQ_DD_INIT_NESTED_LOCK = 1 << 0, > +}; > + > +/** > + * struct irq_chip_dumb_demux - Dumb demultiplexer irq chip data structure > + * @domain: irq domain pointer > + * @max_irq: Last valid irq > + * @available: Bitfield of valid irqs > + * @unmasked: Bitfield containing irqs status > + * @flags: irq_dumb_demux_flags flags > + * > + * 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_dumb_demux { > + struct irq_domain *domain; > + int max_irq; > + unsigned long *available; > + unsigned long *unmasked; Why pointers? A single ulong is certainly enough. > +/** > + * handle_dumb_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 interrupt handler > + * should be called. > + * > + * Note: The caller is expected to handle the ack, clear, mask and > + * unmask issues if necessary. > + */ > +irqreturn_t > +handle_dumb_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_dumb_demux_irq); This should go into the new file as well, so it gets compiled out when not enabled. > +static void irq_dumb_demux_mask(struct irq_data *d) > +{ > + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); > + > + clear_bit(d->hwirq, demux->unmasked); > +} > + > +static void irq_dumb_demux_unmask(struct irq_data *d) > +{ > + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); > + > + set_bit(d->hwirq, demux->unmasked); > +} So this needs the handling of enabling/disabling the source irq. > +static struct irq_chip irq_dumb_demux_chip = { > + .name = "dumb-demux-irq", > + .irq_mask = irq_dumb_demux_mask, > + .irq_unmask = irq_dumb_demux_unmask, + .name = "dumb-demux-irq", + .irq_mask = irq_dumb_demux_mask, + .irq_unmask = irq_dumb_demux_unmask, Makes it simpler to read. > +struct irq_domain_ops irq_dumb_demux_domain_ops = { > + .map = irq_map_dumb_demux_chip, > + .xlate = irq_domain_xlate_onecell, > +}; > +EXPORT_SYMBOL(irq_dumb_demux_domain_ops); SYMBOL_GPL please Rest looks good. Thanks, tglx
Hi Thomas, On Tue, 13 Jan 2015 11:38:14 +0100 (CET) Thomas Gleixner <tglx@linutronix.de> wrote: > On Thu, 8 Jan 2015, Boris Brezillon wrote: > > 1) Is it close to what you had in mind ? > > Yes. > > > 2) I'm not sure which part of the code should go where, so don't hesitate > > to point out misplaced sections. > > Looks sane. Nits below. > > > 3) Do I need to disable the source irq (the one feeding the irqchip) when > > entering suspend (and enable it on resume) ? > > That probably needs to be part of the dumb mask/unmask functions., > i.e. if no demux interrupt is enabled, the source irq should be > masked. Ok, I'll add that part. > > > 4) I'm not sure of what flags should be set/cleared when mapping an > > interrupt. Should I let the caller decide of this config (something > > similar to what is done in generic-chip) ? > > I don't think you need to set/clear anything. Lets look at that dumb > demux chip as a real electronic circuit > > |----------------| > | | > | --|Unmasked|--|---- Demux0 > | | | > SRC irq -----|-- -|Unmasked|--|---- Demux1 > | | | > | --|Unmasked|--|---- Demux2 > | | > |----------------| > > Whether a demultiplexed interrupt is mapped or not is not really > important. The only relevant information is whether its masked or > not. So the default state is masked until a demultiplexed interrupt > gets requested. Hmm, my question was not really clear: I was talking about irq flags [1] (those that are set with irq_modify_status in the generic irq chip [2]). > > > +/** > > + * enum irq_dumb_demux_flags - Initialization flags for generic irq chips > > + * @IRQ_DD_INIT_NESTED_LOCK: Set the lock class of the irqs to nested for > > + * irq chips which need to call irq_set_wake() on > > + * the parent irq. Usually GPIO implementations > > + */ > > +enum irq_dumb_demux_flags { > > + IRQ_DD_INIT_NESTED_LOCK = 1 << 0, > > +}; > > + > > +/** > > + * struct irq_chip_dumb_demux - Dumb demultiplexer irq chip data structure > > + * @domain: irq domain pointer > > + * @max_irq: Last valid irq > > + * @available: Bitfield of valid irqs > > + * @unmasked: Bitfield containing irqs status > > + * @flags: irq_dumb_demux_flags flags > > + * > > + * 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_dumb_demux { > > + struct irq_domain *domain; > > + int max_irq; > > + unsigned long *available; > > + unsigned long *unmasked; > > Why pointers? A single ulong is certainly enough. Okay, just thought one might need a dumb demuxer with more than 32 (or 64) outputs, but I guess we can limit it to an unsigned long for now. > > > +/** > > + * handle_dumb_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 interrupt handler > > + * should be called. > > + * > > + * Note: The caller is expected to handle the ack, clear, mask and > > + * unmask issues if necessary. > > + */ > > +irqreturn_t > > +handle_dumb_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_dumb_demux_irq); > > This should go into the new file as well, so it gets compiled out when > not enabled. Sure. > > > +static void irq_dumb_demux_mask(struct irq_data *d) > > +{ > > + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); > > + > > + clear_bit(d->hwirq, demux->unmasked); > > +} > > + > > +static void irq_dumb_demux_unmask(struct irq_data *d) > > +{ > > + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); > > + > > + set_bit(d->hwirq, demux->unmasked); > > +} > > So this needs the handling of enabling/disabling the source irq. Yep. > > > +static struct irq_chip irq_dumb_demux_chip = { > > + .name = "dumb-demux-irq", > > + .irq_mask = irq_dumb_demux_mask, > > + .irq_unmask = irq_dumb_demux_unmask, > > + .name = "dumb-demux-irq", > + .irq_mask = irq_dumb_demux_mask, > + .irq_unmask = irq_dumb_demux_unmask, > > Makes it simpler to read. I'll fix that > > > +struct irq_domain_ops irq_dumb_demux_domain_ops = { > > + .map = irq_map_dumb_demux_chip, > > + .xlate = irq_domain_xlate_onecell, > > +}; > > +EXPORT_SYMBOL(irq_dumb_demux_domain_ops); > > SYMBOL_GPL please and that too. Thanks, Boris [1]http://lxr.free-electrons.com/source/kernel/irq/settings.h#L21 [2]http://lxr.free-electrons.com/source/kernel/irq/generic-chip.c#L394
On Tue, 13 Jan 2015, Boris Brezillon wrote: > Hmm, my question was not really clear: I was talking about irq flags [1] > (those that are set with irq_modify_status in the generic irq chip [2]). Ah, ok. Taking the same approach should be ok. Thanks, tglx
On Tue, 13 Jan 2015 11:38:14 +0100 (CET) Thomas Gleixner <tglx@linutronix.de> wrote: > > > +/** > > + * handle_dumb_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 interrupt handler > > + * should be called. > > + * > > + * Note: The caller is expected to handle the ack, clear, mask and > > + * unmask issues if necessary. > > + */ > > +irqreturn_t > > +handle_dumb_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_dumb_demux_irq); > > This should go into the new file as well, so it gets compiled out when > not enabled. Actually, I can't move that function out of kernel/irq/chip.c because it calls irq_may_run which is statically defined in this file. Should I export this function or just enclose it in a #ifdef DUMB_IRQ_DEMUX section ?
On Tue, 13 Jan 2015, Boris Brezillon wrote: > On Tue, 13 Jan 2015 11:38:14 +0100 (CET) > Thomas Gleixner <tglx@linutronix.de> wrote: > > > > > +/** > > > + * handle_dumb_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 interrupt handler > > > + * should be called. > > > + * > > > + * Note: The caller is expected to handle the ack, clear, mask and > > > + * unmask issues if necessary. > > > + */ > > > +irqreturn_t > > > +handle_dumb_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_dumb_demux_irq); > > > > This should go into the new file as well, so it gets compiled out when > > not enabled. > > Actually, I can't move that function out of kernel/irq/chip.c because > it calls irq_may_run which is statically defined in this file. > Should I export this function or just enclose it in a > #ifdef DUMB_IRQ_DEMUX section ? Missed that. Make it conditional then.
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index cc79d2a..e315c63 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 DUMB_IRQ_DEMUX + bool + select IRQ_DOMAIN + config DW_APB_ICTL bool select GENERIC_IRQ_CHIP diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 9516a32..6ad7441 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_DUMB_IRQ_DEMUX) += irq-dumb-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-dumb-demux.c b/drivers/irqchip/irq-dumb-demux.c new file mode 100644 index 0000000..b941b8b --- /dev/null +++ b/drivers/irqchip/irq-dumb-demux.c @@ -0,0 +1,71 @@ +#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 dumb_irq_demux_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_dumb_demux *demux; + struct property *prop; + const __be32 *propiter; + unsigned int irq; + int max_irq = -1; + u32 tmpval; + int ret; + + irq = irq_of_parse_and_map(node, 0); + if (!irq) { + pr_err("Failed to retrieve dumb irq demuxer source\n"); + return -EINVAL; + } + + of_property_for_each_u32(node, "irqs", prop, propiter, tmpval) { + if (max_irq < (int)tmpval) + max_irq = tmpval; + } + + if (max_irq < 0) { + pr_err("Missing 'irqs' property\n"); + return -EINVAL; + } + + demux = irq_alloc_dumb_demux_chip(0, max_irq); + if (!demux) { + pr_err("Failed to allocate dumb irq demuxer struct\n"); + return -ENOMEM; + } + + of_property_for_each_u32(node, "irqs", prop, propiter, tmpval) + set_bit(tmpval, demux->available); + + demux->domain = irq_domain_add_linear(node, max_irq + 1, + &irq_dumb_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(irq, irq_dumb_demux_handler); + + return 0; + +err_free_domain: + irq_domain_remove(demux->domain); + +err_free_demux: + kfree(demux); + + return ret; +} +IRQCHIP_DECLARE(dumb_irq_demux, "irqchip-dumb-demux", dumb_irq_demux_of_init); diff --git a/include/linux/irq.h b/include/linux/irq.h index d09ec7a..6217284 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -445,6 +445,8 @@ 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); +extern irqreturn_t handle_dumb_demux_irq(unsigned int irq, + struct irq_desc *desc); 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); @@ -862,4 +864,42 @@ static inline u32 irq_reg_readl(struct irq_chip_generic *gc, return readl(gc->reg_base + reg_offset); } +/** + * enum irq_dumb_demux_flags - Initialization flags for generic irq chips + * @IRQ_DD_INIT_NESTED_LOCK: Set the lock class of the irqs to nested for + * irq chips which need to call irq_set_wake() on + * the parent irq. Usually GPIO implementations + */ +enum irq_dumb_demux_flags { + IRQ_DD_INIT_NESTED_LOCK = 1 << 0, +}; + +/** + * struct irq_chip_dumb_demux - Dumb demultiplexer irq chip data structure + * @domain: irq domain pointer + * @max_irq: Last valid irq + * @available: Bitfield of valid irqs + * @unmasked: Bitfield containing irqs status + * @flags: irq_dumb_demux_flags flags + * + * 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_dumb_demux { + struct irq_domain *domain; + int max_irq; + unsigned long *available; + unsigned long *unmasked; + unsigned int flags; +}; + +void irq_dumb_demux_handler(unsigned int irq, struct irq_desc *desc); + +struct irq_chip_dumb_demux * +irq_alloc_dumb_demux_chip(unsigned int dd_flags, + unsigned int max_out_irq); + #endif /* _LINUX_IRQ_H */ diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 676d730..1de3808 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_dumb_demux_domain_ops; struct irq_domain_chip_generic; diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index d121235..5aad99d 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_DUMB_IRQ_DEMUX) += dumb-demux-chip.o diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 6f1c7a5..3318972 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -405,6 +405,45 @@ out_unlock: } EXPORT_SYMBOL_GPL(handle_simple_irq); +/** + * handle_dumb_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 interrupt handler + * should be called. + * + * Note: The caller is expected to handle the ack, clear, mask and + * unmask issues if necessary. + */ +irqreturn_t +handle_dumb_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_dumb_demux_irq); + /* * Called unconditionally from handle_level_irq() and only for oneshot * interrupts from handle_fasteoi_irq() diff --git a/kernel/irq/dumb-demux-chip.c b/kernel/irq/dumb-demux-chip.c new file mode 100644 index 0000000..049e537 --- /dev/null +++ b/kernel/irq/dumb-demux-chip.c @@ -0,0 +1,128 @@ +/* + * Library implementing common dumb irq demux chip functions + * + * Copyright (C) 2015, Boris Brezillon + */ +#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_dumb_demux_mask(struct irq_data *d) +{ + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); + + clear_bit(d->hwirq, demux->unmasked); +} + +static void irq_dumb_demux_unmask(struct irq_data *d) +{ + struct irq_chip_dumb_demux *demux = irq_data_get_irq_chip_data(d); + + set_bit(d->hwirq, demux->unmasked); +} + +static struct irq_chip irq_dumb_demux_chip = { + .name = "dumb-demux-irq", + .irq_mask = irq_dumb_demux_mask, + .irq_unmask = irq_dumb_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_dumb_demux_chip - Map a dumb demux chip for an irq domain + */ +static int irq_map_dumb_demux_chip(struct irq_domain *d, + unsigned int virq, + irq_hw_number_t hw_irq) +{ + struct irq_chip_dumb_demux *demux = d->host_data; + + if (hw_irq > demux->max_irq || + !test_bit(hw_irq, demux->available)) + return -EINVAL; + + if (demux->flags & IRQ_DD_INIT_NESTED_LOCK) + irq_set_lockdep_class(virq, &irq_nested_lock_class); + + irq_set_chip(virq, &irq_dumb_demux_chip); + set_irq_flags(virq, IRQF_VALID); + irq_set_chip_data(virq, demux); + + return 0; +} + +struct irq_domain_ops irq_dumb_demux_domain_ops = { + .map = irq_map_dumb_demux_chip, + .xlate = irq_domain_xlate_onecell, +}; +EXPORT_SYMBOL(irq_dumb_demux_domain_ops); + +/** + * irq_dumb_demux_handler - Dumb demux flow handler + * @irq: Virtual irq number + * @irq_desc: irq descriptor + */ +void irq_dumb_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct irq_chip_dumb_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, demux->max_irq) { + int demuxed_irq = irq_find_mapping(demux->domain, i); + struct irq_desc *desc = irq_to_desc(demuxed_irq); + + ret |= handle_dumb_demux_irq(demuxed_irq, desc); + } + chained_irq_exit(chip, desc); + + if (!noirqdebug) + note_interrupt(irq, desc, ret); +} +EXPORT_SYMBOL(irq_dumb_demux_handler); + +/** + * irq_alloc_dumb_demux_chip - Allocate a dumb demux chip + * @dd_flags: irq_dumb_demux_flags flags + * @max_out_irq: Last valid irq number + */ +struct irq_chip_dumb_demux * +irq_alloc_dumb_demux_chip(unsigned int dd_flags, + unsigned int max_out_irq) +{ + struct irq_chip_dumb_demux *demux; + int max_irq = -1; + + demux = kzalloc(sizeof(*demux) + + (DIV_ROUND_UP(max_out_irq + 1, BITS_PER_LONG) * 2), + GFP_KERNEL); + if (!demux) { + pr_err("Failed to allocate dumb irq demuxer struct\n"); + return ERR_PTR(-ENOMEM); + } + + demux->available = (void *)demux + sizeof(*demux); + demux->unmasked = demux->available + + DIV_ROUND_UP(max_irq + 1, BITS_PER_LONG); + demux->max_irq = max_out_irq; + + return demux; +} +EXPORT_SYMBOL(irq_alloc_dumb_demux_chip); 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);
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 forward interrupts to all peripherals (exactly what's happening with IRQ_SHARED) but keeps 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@free-electrons.com> --- Hi Thomas, Here is a proof of concept of the dumb demux irqchip implementation (tested on an at91 board). This is definitely not the final implementation, but before going further, and sending a proper patch series, I'd like to have your opinion on several aspects. 1) Is it close to what you had in mind ? 2) I'm not sure which part of the code should go where, so don't hesitate to point out misplaced sections. 3) Do I need to disable the source irq (the one feeding the irqchip) when entering suspend (and enable it on resume) ? 4) I'm not sure of what flags should be set/cleared when mapping an interrupt. Should I let the caller decide of this config (something similar to what is done in generic-chip) ? Best Regards, Boris drivers/irqchip/Kconfig | 4 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-dumb-demux.c | 71 ++++++++++++++++++++++ include/linux/irq.h | 40 ++++++++++++ include/linux/irqdomain.h | 1 + kernel/irq/Makefile | 1 + kernel/irq/chip.c | 39 ++++++++++++ kernel/irq/dumb-demux-chip.c | 128 +++++++++++++++++++++++++++++++++++++++ kernel/irq/handle.c | 31 +++++++++- kernel/irq/internals.h | 3 + 10 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 drivers/irqchip/irq-dumb-demux.c create mode 100644 kernel/irq/dumb-demux-chip.c