summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Pitre <nicolas.pitre@linaro.org>2015-01-12 16:17:47 -0500
committerDaniel Lezcano <daniel.lezcano@linaro.org>2015-01-15 13:38:03 +0100
commit57a61a9ba3e991e2cf9a769114eb264cf47d7c47 (patch)
treeb288f939c055517c331ca4e73a30e2b611d752ba
parentafd42120fa9fb92b3e0dec81bfd4721cb859573e (diff)
irq_timings: add per-CPU prediction queueing
Once a good IRQ prediction is made, we need to enqueue it for later consumption. While at it we discard any predictions whose time stamp is in the past. There shouldn't be that many expected IRQs at any given time. A sorted list is most likely going to be good enough. And, by definition, the most frequent IRQs will end up near the beginning of the list anyway. Tthere is no generic way to determine what the IRQ controller is going to do if the IRQ affinity mask contains multiple CPUs. It is therefore assumed that the next occurrence of an IRQ is most likely to happen on the same CPU as the last one. It appears to be the case overall from observations on X86 despite active migration controlled from user space. On ARM it is the first CPU in the affinity mask that is selected by the GIC driver so this assumption is quite right in that case. If migration frequency becomes significant compared to IRQ occurrences then we could consider registering an affinity notifier. Signed-off-by: Nicolas Pitre <nico@linaro.org> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--kernel/irq/internals.h6
-rw-r--r--kernel/irq/irqdesc.c2
-rw-r--r--kernel/irq/timings.c80
3 files changed, 86 insertions, 2 deletions
diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h
index 4332d766619d..dcfdf76718e6 100644
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -106,6 +106,12 @@ static inline void unregister_handler_proc(unsigned int irq,
struct irqaction *action) { }
#endif
+#ifdef CONFIG_IRQ_TIMINGS
+extern void __init irqt_init(void);
+#else
+static inline void irqt_init(void) { }
+#endif
+
extern int irq_select_affinity_usr(unsigned int irq, struct cpumask *mask);
extern void irq_set_thread_affinity(struct irq_desc *desc);
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index a1782f88f0af..75a289b0f589 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -216,6 +216,7 @@ int __init early_irq_init(void)
int i, initcnt, node = first_online_node;
struct irq_desc *desc;
+ irqt_init();
init_irq_default_affinity();
/* Let arch update nr_irqs and return the nr of preallocated irqs */
@@ -254,6 +255,7 @@ int __init early_irq_init(void)
int count, i, node = first_online_node;
struct irq_desc *desc;
+ irqt_init();
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
diff --git a/kernel/irq/timings.c b/kernel/irq/timings.c
index ae8dbce37528..b9498f306d61 100644
--- a/kernel/irq/timings.c
+++ b/kernel/irq/timings.c
@@ -11,6 +11,8 @@
#include <linux/irq.h>
#include <linux/ktime.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
#include "internals.h"
@@ -23,6 +25,13 @@
*/
#define IRQT_INTERVAL_WINDOW 3
+
+struct irqt_prediction {
+ struct list_head node;
+ ktime_t time; /* expected occurrence time */
+ int cpu; /* CPU for which this was queued for */
+};
+
struct irqt_stat {
ktime_t last_time; /* previous IRQ occurrence */
u64 n_M2; /* IRQ interval variance (n scaled) */
@@ -32,8 +41,71 @@ struct irqt_stat {
unsigned int w_ptr; /* current window pointer */
u32 predictable; /* # of IRQs that were predictable */
u32 unpredictable; /* # of IRQs that were not */
+ struct irqt_prediction prediction;
};
+static DEFINE_PER_CPU(struct list_head, irqt_predictions);
+static DEFINE_PER_CPU(raw_spinlock_t, irqt_predictions_lock);
+
+void __init irqt_init(void)
+{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ INIT_LIST_HEAD(&per_cpu(irqt_predictions, cpu));
+ raw_spin_lock_init(&per_cpu(irqt_predictions_lock, cpu));
+ }
+}
+
+/*
+ * Purge past events.
+ * Caller must take care of locking.
+ */
+static void irqt_purge(ktime_t now, struct list_head *head)
+{
+ struct irqt_prediction *entry, *n;
+
+ list_for_each_entry_safe(entry, n, head, node) {
+ if (ktime_after(entry->time, now))
+ break;
+ list_del_init(&entry->node);
+ }
+}
+
+/*
+ * Enqueue the next predicted event for this IRQ on this CPU.
+ * We are in interrupt context with IRQs disabled.
+ */
+static void irqt_enqueue_prediction(ktime_t now, struct irqt_stat *s)
+{
+ int this_cpu = raw_smp_processor_id();
+ int prev_cpu = s->prediction.cpu;
+ struct list_head *head = &per_cpu(irqt_predictions, this_cpu);
+ u32 predicted_interval = s->n_mean / IRQT_INTERVAL_WINDOW;
+ struct irqt_prediction *list_entry, *new_entry;
+ raw_spinlock_t *lock;
+
+ if (unlikely(prev_cpu != this_cpu && prev_cpu != -1)) {
+ lock = &per_cpu(irqt_predictions_lock, prev_cpu);
+ raw_spin_lock(lock);
+ list_del_init(&s->prediction.node);
+ raw_spin_unlock(lock);
+ }
+
+ lock = &per_cpu(irqt_predictions_lock, this_cpu);
+ raw_spin_lock(lock);
+ irqt_purge(now, head);
+ __list_del_entry(&s->prediction.node);
+ new_entry = &s->prediction;
+ new_entry->time = ktime_add_us(now, predicted_interval);
+ new_entry->cpu = this_cpu;
+ list_for_each_entry(list_entry, head, node)
+ if (ktime_after(new_entry->time, list_entry->time))
+ break;
+ list_add_tail(&new_entry->node, &list_entry->node);
+ raw_spin_unlock(lock);
+}
+
/*
* irqt_process - update timing interval statistics for the given IRQ
*
@@ -152,8 +224,12 @@ void irqt_process(unsigned int irq, struct irqt_stat *s)
* n_mean/n * n_mean/n < n_M2/n / (n - 1) -->
* n_mean * n_mean * (n - 1) < n_M2 * n
*/
- if ((u64)s->n_mean * s->n_mean * (n - 1) > s->n_M2 * n)
+ if ((u64)s->n_mean * s->n_mean * (n - 1) > s->n_M2 * n) {
s->predictable++;
- else
+ if (s->predictable >= IRQT_INTERVAL_WINDOW)
+ irqt_enqueue_prediction(now, s);
+ } else {
+ s->predictable = 0;
s->unpredictable++;
+ }
}