diff options
Diffstat (limited to 'kernel/irq')
-rw-r--r-- | kernel/irq/Kconfig | 3 | ||||
-rw-r--r-- | kernel/irq/Makefile | 1 | ||||
-rw-r--r-- | kernel/irq/handle.c | 2 | ||||
-rw-r--r-- | kernel/irq/internals.h | 62 | ||||
-rw-r--r-- | kernel/irq/irqdesc.c | 6 | ||||
-rw-r--r-- | kernel/irq/manage.c | 3 | ||||
-rw-r--r-- | kernel/irq/timings.c | 75 |
7 files changed, 152 insertions, 0 deletions
diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 3bbfd6a9c475..38e551dc8bfb 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -81,6 +81,9 @@ config GENERIC_MSI_IRQ_DOMAIN config HANDLE_DOMAIN_IRQ bool +config IRQ_TIMINGS + bool + config IRQ_DOMAIN_DEBUG bool "Expose hardware/virtual IRQ mapping via debugfs" depends on IRQ_DOMAIN && DEBUG_FS diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index 2ee42e95a3ce..e1debaa941ef 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_GENERIC_IRQ_MIGRATION) += cpuhotplug.o obj-$(CONFIG_PM_SLEEP) += pm.o obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o obj-$(CONFIG_GENERIC_IRQ_IPI) += ipi.o +obj-$(CONFIG_IRQ_TIMINGS) += timings.o diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c index a15b5485b446..335847eb6714 100644 --- a/kernel/irq/handle.c +++ b/kernel/irq/handle.c @@ -138,6 +138,8 @@ irqreturn_t handle_irq_event_percpu(struct irq_desc *desc) unsigned int flags = 0, irq = desc->irq_data.irq; struct irqaction *action; + record_irq_time(desc); + for_each_action_of_desc(desc, action) { irqreturn_t res; diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index d5edcdc9382a..734fe38ad618 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -57,6 +57,7 @@ enum { IRQS_WAITING = 0x00000080, IRQS_PENDING = 0x00000200, IRQS_SUSPENDED = 0x00000800, + IRQS_TIMINGS = 0x00001000, }; #include "debug.h" @@ -223,3 +224,64 @@ irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) { } static inline void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) { } #endif + +#ifdef CONFIG_IRQ_TIMINGS +static inline int alloc_timings(struct irq_desc *desc) +{ + desc->timings = alloc_percpu(struct irq_timings); + if (!desc->timings) + return -ENOMEM; + + return 0; +} + +static inline void free_timings(struct irq_desc *desc) +{ + free_percpu(desc->timings); +} + +static inline void remove_timings(struct irq_desc *desc) +{ + desc->istate &= ~IRQS_TIMINGS; +} + +static inline void setup_timings(struct irq_desc *desc, struct irqaction *act) +{ + /* + * We don't need the measurement because the idle code already + * knows the next expiry event. + */ + if (act->flags & __IRQF_TIMER) + return; + + desc->istate |= IRQS_TIMINGS; +} + +extern struct static_key_false irq_timing_enabled; + +/* + * The function record_irq_time is only called in one place in the + * interrupts handler. We want this function always inline so the code + * inside is embedded in the function and the static key branching + * code can act at the higher level. Without the explicit + * __always_inline we can end up with a function call and a small + * overhead in the hotpath for nothing. + */ +static __always_inline void record_irq_time(struct irq_desc *desc) +{ + if (static_key_enabled(&irq_timing_enabled)) { + if (desc->istate & IRQS_TIMINGS) { + struct irq_timings *timings = this_cpu_ptr(desc->timings); + timings->w_index = (timings->w_index + 1) & IRQ_TIMINGS_MASK; + timings->values[timings->w_index] = local_clock(); + } + } +} +#else +static inline int alloc_timings(struct irq_desc *desc) { return 0; } +static inline void free_timings(struct irq_desc *desc) {} +static inline void remove_timings(struct irq_desc *desc) {} +static inline void setup_timings(struct irq_desc *desc, + struct irqaction *act) {}; +static inline void record_irq_time(struct irq_desc *desc) {} +#endif diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 8731e1c5d1e7..2c3ce741f251 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -174,6 +174,9 @@ static struct irq_desc *alloc_desc(int irq, int node, struct module *owner) if (alloc_masks(desc, gfp, node)) goto err_kstat; + if (alloc_timings(desc)) + goto err_mask; + raw_spin_lock_init(&desc->lock); lockdep_set_class(&desc->lock, &irq_desc_lock_class); init_rcu_head(&desc->rcu); @@ -182,6 +185,8 @@ static struct irq_desc *alloc_desc(int irq, int node, struct module *owner) return desc; +err_mask: + free_masks(desc); err_kstat: free_percpu(desc->kstat_irqs); err_desc: @@ -193,6 +198,7 @@ static void delayed_free_desc(struct rcu_head *rhp) { struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu); + free_timings(desc); free_masks(desc); free_percpu(desc->kstat_irqs); kfree(desc); diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 00cfc852cca8..7a9f460000d5 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -1350,6 +1350,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) __enable_irq(desc); } + setup_timings(desc, new); + raw_spin_unlock_irqrestore(&desc->lock, flags); /* @@ -1480,6 +1482,7 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id) irq_settings_clr_disable_unlazy(desc); irq_shutdown(desc); irq_release_resources(desc); + remove_timings(desc); } #ifdef CONFIG_SMP diff --git a/kernel/irq/timings.c b/kernel/irq/timings.c new file mode 100644 index 000000000000..e6f1d61925f0 --- /dev/null +++ b/kernel/irq/timings.c @@ -0,0 +1,75 @@ +/* + * linux/kernel/irq/timings.c + * + * Copyright (C) 2016, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdesc.h> +#include <linux/percpu.h> +#include <linux/static_key.h> + +#include "internals.h" + +DEFINE_STATIC_KEY_FALSE(irq_timing_enabled); + +void irq_timings_enable(void) +{ + static_branch_inc(&irq_timing_enabled); +} + +void irq_timings_disable(void) +{ + static_branch_dec(&irq_timing_enabled); +} + +/** + * irqtiming_get_next - return the next irq timing + * + * @irq: a pointer to an integer representing the interrupt number + * + * Must be called under rcu_read_lock(). + * + * This function allows to browse safely the interrupt descriptors in order + * to retrieve the interrupts timings. The parameter gives the interrupt + * number to begin with and will return the interrupt timings for the next + * allocated irq. This approach gives us the possibility to go through the + * different interrupts without having to handle the sparse irq. + * + * The function changes @irq to the next allocated irq + 1, it should be + * passed back again and again until NULL is returned. Usually this function + * is called the first time with @irq = 0. + * + * Returns a struct irq_timings, NULL if we reach the end of the interrupts + * list. + */ +struct irq_timings *irq_timings_get_next(int *irq) +{ + struct irq_desc *desc; + int next; + +again: + /* Do a racy lookup of the next allocated irq */ + next = irq_get_next_irq(*irq); + if (next >= nr_irqs) + return NULL; + + *irq = next + 1; + + /* + * Now lookup the descriptor. It's RCU protected. This + * descriptor might belong to an uninteresting interrupt or + * one that is not measured. Look for the next interrupt in + * that case. + */ + desc = irq_to_desc(next); + if (!desc || !(desc->istate & IRQS_TIMINGS)) + goto again; + + return this_cpu_ptr(desc->timings); +} |