* interrupt handler after suspending interrupts. For system
* wakeup devices users need to implement wakeup detection in
* their interrupt handlers.
+ * IRQF_PERF_CRITICAL - Interrupt is critical to the overall performance of the
+ * system and should be processed on a fast CPU.
*/
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
+#define IRQF_PERF_CRITICAL 0x00080000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
extern void enable_irq(unsigned int irq);
extern void enable_percpu_irq(unsigned int irq, unsigned int type);
extern void irq_wake_thread(unsigned int irq, void *dev_id);
+extern void irq_set_perf_affinity(unsigned int irq);
/* The following three functions are for the core kernel use only. */
extern void suspend_device_irqs(void);
extern void resume_device_irqs(void);
+extern void unaffine_perf_irqs(void);
+extern void reaffine_perf_irqs(void);
/**
* struct irq_affinity_notify - context for notification of IRQ affinity changes
#include <linux/sched.h>
#include <linux/sched/rt.h>
#include <linux/task_work.h>
+#include <linux/cpu.h>
#include "internals.h"
+struct irq_desc_list {
+ struct list_head list;
+ struct irq_desc *desc;
+} perf_crit_irqs = {
+ .list = LIST_HEAD_INIT(perf_crit_irqs.list)
+};
+
+static DEFINE_RAW_SPINLOCK(perf_irqs_lock);
+static int perf_cpu_index = -1;
+
#ifdef CONFIG_IRQ_FORCED_THREADING
__read_mostly bool force_irqthreads;
return 0;
}
+static void add_desc_to_perf_list(struct irq_desc *desc)
+{
+ struct irq_desc_list *item;
+
+ item = kmalloc(sizeof(*item), GFP_ATOMIC | __GFP_NOFAIL);
+ item->desc = desc;
+
+ raw_spin_lock(&perf_irqs_lock);
+ list_add(&item->list, &perf_crit_irqs.list);
+ raw_spin_unlock(&perf_irqs_lock);
+}
+
+static void affine_one_perf_thread(struct task_struct *t)
+{
+ t->flags |= PF_PERF_CRITICAL;
+ set_cpus_allowed_ptr(t, cpu_perf_mask);
+}
+
+static void unaffine_one_perf_thread(struct task_struct *t)
+{
+ t->flags &= ~PF_PERF_CRITICAL;
+ set_cpus_allowed_ptr(t, cpu_all_mask);
+}
+
+static void affine_one_perf_irq(struct irq_desc *desc)
+{
+ int cpu;
+
+ /* Balance the performance-critical IRQs across all perf CPUs */
+ while (1) {
+ cpu = cpumask_next_and(perf_cpu_index, cpu_perf_mask,
+ cpu_online_mask);
+ if (cpu < nr_cpu_ids)
+ break;
+ perf_cpu_index = -1;
+ }
+ irq_set_affinity_locked(&desc->irq_data, cpumask_of(cpu), true);
+
+ perf_cpu_index = cpu;
+}
+
+static void setup_perf_irq_locked(struct irq_desc *desc)
+{
+ add_desc_to_perf_list(desc);
+ irqd_set(&desc->irq_data, IRQD_AFFINITY_MANAGED);
+ raw_spin_lock(&perf_irqs_lock);
+ affine_one_perf_irq(desc);
+ raw_spin_unlock(&perf_irqs_lock);
+}
+
+void irq_set_perf_affinity(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+ struct irqaction *action;
+ unsigned long flags;
+
+ if (!desc)
+ return;
+
+ raw_spin_lock_irqsave(&desc->lock, flags);
+ action = desc->action;
+ while (action) {
+ action->flags |= IRQF_PERF_CRITICAL;
+ action = action->next;
+ }
+ setup_perf_irq_locked(desc);
+ raw_spin_unlock_irqrestore(&desc->lock, flags);
+}
+
+void unaffine_perf_irqs(void)
+{
+ struct irq_desc_list *data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&perf_irqs_lock, flags);
+ list_for_each_entry(data, &perf_crit_irqs.list, list) {
+ struct irq_desc *desc = data->desc;
+
+ raw_spin_lock(&desc->lock);
+ irq_set_affinity_locked(&desc->irq_data, cpu_all_mask, true);
+ if (desc->action->thread)
+ unaffine_one_perf_thread(desc->action->thread);
+ raw_spin_unlock(&desc->lock);
+ }
+ perf_cpu_index = -1;
+ raw_spin_unlock_irqrestore(&perf_irqs_lock, flags);
+}
+
+void reaffine_perf_irqs(void)
+{
+ struct irq_desc_list *data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&perf_irqs_lock, flags);
+ list_for_each_entry(data, &perf_crit_irqs.list, list) {
+ struct irq_desc *desc = data->desc;
+
+ raw_spin_lock(&desc->lock);
+ affine_one_perf_irq(desc);
+ if (desc->action->thread)
+ affine_one_perf_thread(desc->action->thread);
+ raw_spin_unlock(&desc->lock);
+ }
+ raw_spin_unlock_irqrestore(&perf_irqs_lock, flags);
+}
+
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
if (ret)
goto out_thread;
}
+
+ if (new->flags & IRQF_PERF_CRITICAL)
+ affine_one_perf_thread(new->thread);
}
if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
}
/* Set default affinity mask once everything is setup */
- setup_affinity(desc, mask);
+ if (new->flags & IRQF_PERF_CRITICAL)
+ setup_perf_irq_locked(desc);
+ else
+ setup_affinity(desc, mask);
} else if (new->flags & IRQF_TRIGGER_MASK) {
unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
action_ptr = &action->next;
}
+ if (action->flags & IRQF_PERF_CRITICAL) {
+ struct irq_desc_list *data;
+
+ raw_spin_lock(&perf_irqs_lock);
+ list_for_each_entry(data, &perf_crit_irqs.list, list) {
+ if (data->desc == desc) {
+ list_del(&data->list);
+ kfree(data);
+ break;
+ }
+ }
+ raw_spin_unlock(&perf_irqs_lock);
+ }
+
/* Found it - now remove it from the list of entries: */
*action_ptr = action->next;