Skip to content

aarch64: the IRQ-exit ksoftirqd wakeup takes the global SCHEDULER spin lock #780

Description

@ryanbreen

The aarch64 IRQ-exit path takes the global SCHEDULER spin lock when
do_softirq overflows its iteration limit and wakes the local ksoftirqd.

The path, with the lines it is made of

4 of the 4 lines are on main today, and 0 of the 4 come from an open
branch (git blame against origin/main, commits below):

file:line on main text introduced
kernel/src/arch_impl/aarch64/exception.rs:2212 crate::task::softirqd::do_softirq(); 0668208d7, 2026-02-06
kernel/src/task/softirqd.rs:220 wakeup_ksoftirqd(); (the restart_count >= MAX_SOFTIRQ_RESTART arm) edf9186fe, 2026-01-21
kernel/src/task/kthread.rs:227 `scheduler::with_scheduler( sched
kernel/src/task/scheduler.rs:4609 let mut scheduler_lock = lock_scheduler(); inside with_scheduler af73c0d45, 2026-08-06

exception.rs:2212 runs after irq_exit() on the aarch64 IRQ path -- 1 of
the 1 post-irq_exit do_softirq call site compiled on that arch. When the
dispatch loop hits MAX_SOFTIRQ_RESTART with work still pending it calls
wakeup_ksoftirqd(), which calls kthread_unpark, which calls
with_scheduler, which blocks in lock_scheduler() on the one global
SCHEDULER mutex. So an interrupt handler can spin on a lock a thread on
another CPU is holding.

Reproduce the shape by reading the four spans:

nl -ba kernel/src/arch_impl/aarch64/exception.rs | sed -n '2205,2216p'
nl -ba kernel/src/task/softirqd.rs               | sed -n '214,224p'
nl -ba kernel/src/task/kthread.rs                | sed -n '223,234p'
nl -ba kernel/src/task/scheduler.rs              | sed -n '4603,4614p'

Linux, for contrast

Linux takes no global runqueue lock here. wakeup_softirqd() is called from
__do_softirq's overflow arm and from irq_exit(), and it wakes the current
CPU's ksoftirqd through wake_up_process, which takes that task's own
runqueue lock
with raw_spin_lock_irqsave (rq_lock_irqsave /
task_rq_lock). The lock is per-runqueue, not global, and it is
interrupt-safe by construction: an IRQ that arrives while a thread on the same
CPU holds it cannot deadlock, and a peer CPU's runqueue is a different lock.
claim-lint:ok: the contrast is a source reading of Linux's
kernel/softirq.c::wakeup_softirqd and kernel/sched/core.c::try_to_wake_up,
not a measurement of this tree.

claim-lint:ok: the count is a source count -- static SCHEDULER has 1
declaration in kernel/src/task/scheduler.rs.
This kernel has one SCHEDULER mutex shared by every CPU, and it is a bare
spin::Mutex with no irqsave discipline of its own, so an IRQ-exit acquisition
contends with every thread-context acquisition on every CPU.

Scope

An IRQ-exit wakeup in this kernel shares this property whichever thread it
wakes
, not just the ksoftirqd one: a wake reached from exception.rs's
post-irq_exit work ends in Scheduler::unblock, which is a method on the
locked scheduler: it takes &mut Scheduler, and that reference exists only
inside the lock_scheduler() critical section.
claim-lint:ok: source counts -- 1 fn unblock definition and 34 .unblock(
call sites in kernel/src, 34 of 34 typed on &mut Scheduler.

No deadlock has been observed from it. It is filed because it is a real
structural property that the aarch64 testing-profile work (#562/#761) kept
running into while removing the other two locks on that path -- the softirq
handler's identity read and the KSOFTIRQD handle mutex, both of which are
gone. This one is left deliberately: the fix is per-CPU (or irqsave-typed)
runqueue locking, which is a scheduler change, not a testing-profile change.

What would close it

A wake that reaches only the woken thread's own CPU-local scheduling state,
under an interrupt-safe lock -- the #609 irqsave-typed lock work is the
nearest existing precedent in this tree.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions