// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2010 Red Hat, Inc., Peter Zijlstra * * Provides a framework for enqueueing and running callbacks from hardirq * context. The enqueueing is NMI-safe.
*/
/* * Claim the entry so that no one else will poke at it.
*/ staticbool irq_work_claim(struct irq_work *work)
{ int oflags;
oflags = atomic_fetch_or(IRQ_WORK_CLAIMED | CSD_TYPE_IRQ_WORK, &work->node.a_flags); /* * If the work is already pending, no need to raise the IPI. * The pairing smp_mb() in irq_work_single() makes sure * everything we did before is visible.
*/ if (oflags & IRQ_WORK_PENDING) returnfalse; returntrue;
}
void __weak arch_irq_work_raise(void)
{ /* * Lame architectures will get the timer tick callback
*/
}
/* Enqueue on current CPU, work must already be claimed and preempt disabled */ staticvoid __irq_work_queue_local(struct irq_work *work)
{ struct llist_head *list; bool rt_lazy_work = false; bool lazy_work = false; int work_flags;
if (lazy_work || rt_lazy_work)
list = this_cpu_ptr(&lazy_list); else
list = this_cpu_ptr(&raised_list);
if (!llist_add(&work->node.llist, list)) return;
/* If the work is "lazy", handle it from next tick if any */ if (!lazy_work || tick_nohz_tick_stopped())
irq_work_raise(work);
}
/* Enqueue the irq work @work on the current CPU */ bool irq_work_queue(struct irq_work *work)
{ /* Only queue if not already pending */ if (!irq_work_claim(work)) returnfalse;
/* Queue the entry and raise the IPI if needed. */
preempt_disable();
__irq_work_queue_local(work);
preempt_enable();
returntrue;
}
EXPORT_SYMBOL_GPL(irq_work_queue);
/* * Enqueue the irq_work @work on @cpu unless it's already pending * somewhere. * * Can be re-enqueued while the callback is still in progress.
*/ bool irq_work_queue_on(struct irq_work *work, int cpu)
{ #ifndef CONFIG_SMP return irq_work_queue(work);
#else/* CONFIG_SMP: */ /* All work should have been flushed before going offline */
WARN_ON_ONCE(cpu_is_offline(cpu));
/* Only queue if not already pending */ if (!irq_work_claim(work)) returnfalse;
/* * On PREEMPT_RT the items which are not marked as * IRQ_WORK_HARD_IRQ are added to the lazy list and a HARD work * item is used on the remote CPU to wake the thread.
*/ if (IS_ENABLED(CONFIG_PREEMPT_RT) &&
!(atomic_read(&work->node.a_flags) & IRQ_WORK_HARD_IRQ)) {
if (!llist_add(&work->node.llist, &per_cpu(lazy_list, cpu))) goto out;
work = &per_cpu(irq_work_wakeup, cpu); if (!irq_work_claim(work)) goto out;
}
/* * Clear the PENDING bit, after this point the @work can be re-used. * The PENDING bit acts as a lock, and we own it, so we can clear it * without atomic ops.
*/
flags = atomic_read(&work->node.a_flags);
flags &= ~IRQ_WORK_PENDING;
atomic_set(&work->node.a_flags, flags);
/* * Clear the BUSY bit, if set, and return to the free state if no-one * else claimed it meanwhile.
*/
(void)atomic_cmpxchg(&work->node.a_flags, flags, flags & ~IRQ_WORK_BUSY);
if ((IS_ENABLED(CONFIG_PREEMPT_RT) && !irq_work_is_hard(work)) ||
!arch_irq_work_has_interrupt())
rcuwait_wake_up(&work->irqwait);
}
/* * On PREEMPT_RT IRQ-work which is not marked as HARD will be processed * in a per-CPU thread in preemptible context. Only the items which are * marked as IRQ_WORK_HARD_IRQ will be processed in hardirq context.
*/
BUG_ON(!irqs_disabled() && !IS_ENABLED(CONFIG_PREEMPT_RT));
if (!llist_empty(raised) && !arch_irq_work_has_interrupt())
irq_work_run_list(raised);
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
irq_work_run_list(this_cpu_ptr(&lazy_list)); else
wake_irq_workd();
}
/* * Synchronize against the irq_work @entry, ensures the entry is not * currently in use.
*/ void irq_work_sync(struct irq_work *work)
{
lockdep_assert_irqs_enabled();
might_sleep();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.