summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/irq/internals.h2
-rw-r--r--kernel/irq/irqdesc.c10
-rw-r--r--kernel/irq/manage.c31
-rw-r--r--kernel/irq/msi.c4
-rw-r--r--kernel/irq/proc.c24
-rw-r--r--kernel/softirq.c64
6 files changed, 101 insertions, 34 deletions
diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h
index 0ce21dd45404..2c30e7537987 100644
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -36,6 +36,7 @@ extern struct irqaction chained_action;
* IRQTF_AFFINITY - irq thread is requested to adjust affinity
* IRQTF_FORCED_THREAD - irq action is force threaded
* IRQTF_READY - signals that irq thread is ready
+ * IRQTF_EXIT_WORK - irq thread exit task_work is pending
*/
enum {
IRQTF_RUNTHREAD,
@@ -43,6 +44,7 @@ enum {
IRQTF_AFFINITY,
IRQTF_FORCED_THREAD,
IRQTF_READY,
+ IRQTF_EXIT_WORK,
};
/*
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index 3a818f07a101..7cbcc3fb9e6d 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -28,7 +28,15 @@ static struct lock_class_key irq_desc_lock_class;
static int __init irq_affinity_setup(char *str)
{
alloc_bootmem_cpumask_var(&irq_default_affinity);
- cpulist_parse(str, irq_default_affinity);
+ if (cpulist_parse(str, irq_default_affinity) < 0) {
+ /*
+ * Clear the mask so that init_irq_default_affinity()
+ * defaults it later.
+ */
+ cpumask_clear(irq_default_affinity);
+ pr_warn("irqaffinity: incorrect CPU range, using default\n");
+ return 1;
+ }
/*
* Set at least the boot cpu. We don't want to end up with
* bugreports caused by random commandline masks
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 57eff26fa646..3b647d9b458a 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -21,6 +21,7 @@
#include <linux/sched/isolation.h>
#include <uapi/linux/sched/types.h>
#include <linux/task_work.h>
+#include <linux/wait_bit.h>
#include "internals.h"
@@ -1176,17 +1177,14 @@ void wake_threads_waitq(struct irq_desc *desc)
wake_up(&desc->wait_for_threads);
}
-static void irq_thread_dtor(struct callback_head *unused)
+static void __irq_thread_dtor(struct irqaction *action)
{
struct task_struct *tsk = current;
struct irq_desc *desc;
- struct irqaction *action;
if (WARN_ON_ONCE(!(current->flags & PF_EXITING)))
return;
- action = kthread_data(tsk);
-
pr_err("exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n",
tsk->comm, tsk->pid, action->irq);
@@ -1203,6 +1201,14 @@ static void irq_thread_dtor(struct callback_head *unused)
irq_finalize_oneshot(desc, action);
}
+static void irq_thread_dtor(struct callback_head *work)
+{
+ struct irqaction *action = container_of(work, struct irqaction, thread_dtor);
+
+ __irq_thread_dtor(action);
+ clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
+}
+
static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action)
{
struct irqaction *secondary = action->secondary;
@@ -1244,7 +1250,6 @@ static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
*/
static int irq_thread(void *data)
{
- struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
@@ -1263,8 +1268,9 @@ static int irq_thread(void *data)
else
handler_fn = irq_thread_fn;
- init_task_work(&on_exit_work, irq_thread_dtor);
- task_work_add(current, &on_exit_work, TWA_NONE);
+ set_bit(IRQTF_EXIT_WORK, &action->thread_flags);
+ init_task_work(&action->thread_dtor, irq_thread_dtor);
+ task_work_add(current, &action->thread_dtor, TWA_NONE);
while (!irq_wait_for_interrupt(desc, action)) {
irqreturn_t action_ret;
@@ -1283,6 +1289,7 @@ static int irq_thread(void *data)
* oneshot mask bit can be set.
*/
task_work_cancel_func(current, irq_thread_dtor);
+ clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
return 0;
}
@@ -1853,6 +1860,12 @@ out_mput:
return ret;
}
+static void irq_stop_thread(struct irqaction *action)
+{
+ kthread_stop_put(action->thread);
+ wait_on_bit(&action->thread_flags, IRQTF_EXIT_WORK, TASK_UNINTERRUPTIBLE);
+}
+
/*
* Internal function to unregister an irqaction - used to free
* regular and special interrupts that are part of the architecture.
@@ -1958,9 +1971,9 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
* the same bit to a newly requested action.
*/
if (action->thread) {
- kthread_stop_put(action->thread);
+ irq_stop_thread(action);
if (action->secondary && action->secondary->thread)
- kthread_stop_put(action->secondary->thread);
+ irq_stop_thread(action->secondary);
}
/* Last action releases resources */
diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c
index fb5f372215bf..b22d74496af1 100644
--- a/kernel/irq/msi.c
+++ b/kernel/irq/msi.c
@@ -142,7 +142,7 @@ fail:
* insert it at @init_desc->msi_index
*
* @dev: Pointer to the device for which the descriptor is allocated
- * @domid: The id of the interrupt domain to which the desriptor is added
+ * @domid: The id of the interrupt domain to which the descriptor is added
* @init_desc: Pointer to an MSI descriptor to initialize the new descriptor
*
* Return: 0 on success or an appropriate failure code.
@@ -416,7 +416,7 @@ EXPORT_SYMBOL_GPL(msi_domain_first_desc);
* @domid: The id of the interrupt domain which should be walked.
* @filter: Descriptor state filter
*
- * The first invocation of msi_next_desc() has to be preceeded by a
+ * The first invocation of msi_next_desc() has to be preceded by a
* successful invocation of __msi_first_desc(). Consecutive invocations are
* only valid if the previous one was successful. All these operations have
* to be done within the same MSI mutex held region.
diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 1b835725f7b1..0c0df16de581 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
static struct irq_proc_constraints {
- bool print_header;
+ loff_t first_pos;
unsigned int num_prec;
unsigned int chip_width;
} irq_proc_constraints __read_mostly = {
@@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
struct irqaction *action;
/* Print header for the first interrupt? */
- if (constr->print_header) {
+ if (p->index == constr->first_pos) {
unsigned int cpu;
seq_printf(p, "%*s", constr->num_prec + 8, "");
for_each_online_cpu(cpu)
seq_printf(p, "CPU%-8d", cpu);
seq_putc(p, '\n');
- constr->print_header = false;
}
if (desc == ARCH_PROC_IRQDESC)
@@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
static void *irq_seq_start(struct seq_file *f, loff_t *pos)
{
- if (!*pos) {
- struct irq_proc_constraints *constr = f->private;
+ struct irq_proc_constraints *constr = f->private;
+ bool first = !*pos;
+ void *ret;
+ if (first) {
constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
- constr->print_header = true;
}
- return irq_seq_next_desc(pos);
+
+ ret = irq_seq_next_desc(pos);
+
+ /*
+ * Record position of the first output record so that irq_seq_show()
+ * knows where to write the header.
+ */
+ if (first)
+ constr->first_pos = *pos;
+
+ return ret;
}
static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 5d02c36c40e3..7fc2200392cd 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -350,8 +350,8 @@ static inline void ksoftirqd_run_end(void)
local_irq_enable();
}
-static inline void softirq_handle_begin(void) { }
-static inline void softirq_handle_end(void) { }
+static inline bool softirq_handle_begin(void) { return false; }
+static inline void softirq_handle_end(bool from_irq_exit) { }
static inline bool should_wake_ksoftirqd(void)
{
@@ -481,15 +481,38 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
}
EXPORT_SYMBOL(__local_bh_enable_ip);
-static inline void softirq_handle_begin(void)
+static inline bool softirq_handle_begin(void)
{
- __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ if (!in_hardirq()) {
+ __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ return false;
+ }
+
+ /*
+ * Only reached from irq_exit(), with HARDIRQ_OFFSET still set.
+ * Replace it with SOFTIRQ_OFFSET before handle_softirqs() enables
+ * interrupts. Use the raw operation to preserve the preemption
+ * disable location recorded by irq_enter_rcu(), and update lockdep
+ * directly.
+ */
+ __preempt_count_sub(HARDIRQ_OFFSET - SOFTIRQ_OFFSET);
+ lockdep_softirqs_off(_RET_IP_);
+ WARN_ON_ONCE(irq_count() != SOFTIRQ_OFFSET);
+
+ return true;
}
-static inline void softirq_handle_end(void)
+static inline void softirq_handle_end(bool from_irq_exit)
{
- __local_bh_enable(SOFTIRQ_OFFSET);
- WARN_ON_ONCE(in_interrupt());
+ if (!from_irq_exit) {
+ __local_bh_enable(SOFTIRQ_OFFSET);
+ WARN_ON_ONCE(in_interrupt());
+ return;
+ }
+
+ lockdep_softirqs_on(_RET_IP_);
+ __preempt_count_add(HARDIRQ_OFFSET - SOFTIRQ_OFFSET);
+ WARN_ON_ONCE(irq_count() != HARDIRQ_OFFSET);
}
static inline void ksoftirqd_run_begin(void)
@@ -605,6 +628,7 @@ static void handle_softirqs(bool ksirqd)
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
+ bool from_irq_exit;
bool in_hardirq;
__u32 pending;
int softirq_bit;
@@ -618,7 +642,7 @@ static void handle_softirqs(bool ksirqd)
pending = local_softirq_pending();
- softirq_handle_begin();
+ from_irq_exit = softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);
@@ -645,8 +669,8 @@ restart:
h->action();
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
- pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
- vec_nr, softirq_to_name[vec_nr], h->action,
+ pr_err("huh, entered softirq %u %s with preempt_count %08x, exited with %08x?\n",
+ vec_nr, softirq_to_name[vec_nr],
prev_count, preempt_count());
preempt_count_set(prev_count);
}
@@ -670,7 +694,7 @@ restart:
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
- softirq_handle_end();
+ softirq_handle_end(from_irq_exit);
current_restore_flags(old_flags, PF_MEMALLOC);
}
@@ -716,7 +740,7 @@ static inline void tick_irq_exit(void)
/* Make sure that timer wheel updates are propagated */
if ((sched_core_idle_cpu(cpu) && !need_resched()) || tick_nohz_full_cpu(cpu)) {
- if (!in_hardirq())
+ if (hardirq_count() == HARDIRQ_OFFSET)
tick_nohz_irq_exit();
}
#endif
@@ -748,8 +772,12 @@ static inline void __irq_exit_rcu(void)
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
- preempt_count_sub(HARDIRQ_OFFSET);
- if (!in_interrupt() && local_softirq_pending()) {
+
+ /*
+ * HARDIRQ_OFFSET is still set. Only the outermost interrupt handles
+ * softirqs, and only if it did not hit a softirq or BH disabled section.
+ */
+ if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
* before enabling interrupts to run softirq.
@@ -758,11 +786,17 @@ static inline void __irq_exit_rcu(void)
invoke_softirq();
}
+ /*
+ * Wake the timer thread even if the interrupt hit a softirq or a
+ * section with BHs disabled. Only the outermost interrupt can wake it.
+ */
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
- local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
+ local_timers_pending_force_th() &&
+ hardirq_count() == HARDIRQ_OFFSET)
wake_timersd();
tick_irq_exit();
+ preempt_count_sub(HARDIRQ_OFFSET);
}
/**