.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * linux/kernel/softirq.c |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 1992 Linus Torvalds |
---|
5 | | - * |
---|
6 | | - * Distribute under GPLv2. |
---|
7 | 6 | * |
---|
8 | 7 | * Rewritten. Old one was good in 2.2, but in 2.3 it was immoral. --ANK (990903) |
---|
9 | 8 | */ |
---|
.. | .. |
---|
29 | 28 | |
---|
30 | 29 | #define CREATE_TRACE_POINTS |
---|
31 | 30 | #include <trace/events/irq.h> |
---|
| 31 | + |
---|
| 32 | +EXPORT_TRACEPOINT_SYMBOL_GPL(irq_handler_entry); |
---|
| 33 | +EXPORT_TRACEPOINT_SYMBOL_GPL(irq_handler_exit); |
---|
32 | 34 | |
---|
33 | 35 | /* |
---|
34 | 36 | - No shared variables, all the data are CPU local. |
---|
.. | .. |
---|
56 | 58 | static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; |
---|
57 | 59 | |
---|
58 | 60 | DEFINE_PER_CPU(struct task_struct *, ksoftirqd); |
---|
| 61 | +EXPORT_PER_CPU_SYMBOL_GPL(ksoftirqd); |
---|
| 62 | + |
---|
| 63 | +/* |
---|
| 64 | + * active_softirqs -- per cpu, a mask of softirqs that are being handled, |
---|
| 65 | + * with the expectation that approximate answers are acceptable and therefore |
---|
| 66 | + * no synchronization. |
---|
| 67 | + */ |
---|
| 68 | +DEFINE_PER_CPU(__u32, active_softirqs); |
---|
59 | 69 | |
---|
60 | 70 | const char * const softirq_to_name[NR_SOFTIRQS] = { |
---|
61 | 71 | "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL", |
---|
.. | .. |
---|
78 | 88 | } |
---|
79 | 89 | |
---|
80 | 90 | /* |
---|
81 | | - * If ksoftirqd is scheduled, we do not want to process pending softirqs |
---|
82 | | - * right now. Let ksoftirqd handle this at its own rate, to get fairness, |
---|
83 | | - * unless we're doing some of the synchronous softirqs. |
---|
84 | | - */ |
---|
85 | | -#define SOFTIRQ_NOW_MASK ((1 << HI_SOFTIRQ) | (1 << TASKLET_SOFTIRQ)) |
---|
86 | | -static bool ksoftirqd_running(unsigned long pending) |
---|
87 | | -{ |
---|
88 | | - struct task_struct *tsk = __this_cpu_read(ksoftirqd); |
---|
89 | | - |
---|
90 | | - if (pending & SOFTIRQ_NOW_MASK) |
---|
91 | | - return false; |
---|
92 | | - return tsk && (tsk->state == TASK_RUNNING); |
---|
93 | | -} |
---|
94 | | - |
---|
95 | | -/* |
---|
96 | 91 | * preempt_count and SOFTIRQ_OFFSET usage: |
---|
97 | 92 | * - preempt_count is changed by SOFTIRQ_OFFSET on entering or leaving |
---|
98 | 93 | * softirq processing. |
---|
.. | .. |
---|
107 | 102 | * where hardirqs are disabled legitimately: |
---|
108 | 103 | */ |
---|
109 | 104 | #ifdef CONFIG_TRACE_IRQFLAGS |
---|
| 105 | + |
---|
| 106 | +DEFINE_PER_CPU(int, hardirqs_enabled); |
---|
| 107 | +DEFINE_PER_CPU(int, hardirq_context); |
---|
| 108 | +EXPORT_PER_CPU_SYMBOL_GPL(hardirqs_enabled); |
---|
| 109 | +EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context); |
---|
| 110 | + |
---|
110 | 111 | void __local_bh_disable_ip(unsigned long ip, unsigned int cnt) |
---|
111 | 112 | { |
---|
112 | 113 | unsigned long flags; |
---|
.. | .. |
---|
126 | 127 | * Were softirqs turned off above: |
---|
127 | 128 | */ |
---|
128 | 129 | if (softirq_count() == (cnt & SOFTIRQ_MASK)) |
---|
129 | | - trace_softirqs_off(ip); |
---|
| 130 | + lockdep_softirqs_off(ip); |
---|
130 | 131 | raw_local_irq_restore(flags); |
---|
131 | 132 | |
---|
132 | 133 | if (preempt_count() == cnt) { |
---|
.. | .. |
---|
147 | 148 | trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip()); |
---|
148 | 149 | |
---|
149 | 150 | if (softirq_count() == (cnt & SOFTIRQ_MASK)) |
---|
150 | | - trace_softirqs_on(_RET_IP_); |
---|
| 151 | + lockdep_softirqs_on(_RET_IP_); |
---|
151 | 152 | |
---|
152 | 153 | __preempt_count_sub(cnt); |
---|
153 | 154 | } |
---|
.. | .. |
---|
174 | 175 | * Are softirqs going to be turned on now: |
---|
175 | 176 | */ |
---|
176 | 177 | if (softirq_count() == SOFTIRQ_DISABLE_OFFSET) |
---|
177 | | - trace_softirqs_on(ip); |
---|
| 178 | + lockdep_softirqs_on(ip); |
---|
178 | 179 | /* |
---|
179 | 180 | * Keep preemption disabled until we are done with |
---|
180 | 181 | * softirq processing: |
---|
.. | .. |
---|
224 | 225 | { |
---|
225 | 226 | bool in_hardirq = false; |
---|
226 | 227 | |
---|
227 | | - if (trace_hardirq_context(current)) { |
---|
| 228 | + if (lockdep_hardirq_context()) { |
---|
228 | 229 | in_hardirq = true; |
---|
229 | | - trace_hardirq_exit(); |
---|
| 230 | + lockdep_hardirq_exit(); |
---|
230 | 231 | } |
---|
231 | 232 | |
---|
232 | 233 | lockdep_softirq_enter(); |
---|
.. | .. |
---|
239 | 240 | lockdep_softirq_exit(); |
---|
240 | 241 | |
---|
241 | 242 | if (in_hardirq) |
---|
242 | | - trace_hardirq_enter(); |
---|
| 243 | + lockdep_hardirq_enter(); |
---|
243 | 244 | } |
---|
244 | 245 | #else |
---|
245 | 246 | static inline bool lockdep_softirq_start(void) { return false; } |
---|
246 | 247 | static inline void lockdep_softirq_end(bool in_hardirq) { } |
---|
247 | 248 | #endif |
---|
| 249 | + |
---|
| 250 | +#define softirq_deferred_for_rt(pending) \ |
---|
| 251 | +({ \ |
---|
| 252 | + __u32 deferred = 0; \ |
---|
| 253 | + if (cpupri_check_rt()) { \ |
---|
| 254 | + deferred = pending & LONG_SOFTIRQ_MASK; \ |
---|
| 255 | + pending &= ~LONG_SOFTIRQ_MASK; \ |
---|
| 256 | + } \ |
---|
| 257 | + deferred; \ |
---|
| 258 | +}) |
---|
248 | 259 | |
---|
249 | 260 | asmlinkage __visible void __softirq_entry __do_softirq(void) |
---|
250 | 261 | { |
---|
.. | .. |
---|
253 | 264 | int max_restart = MAX_SOFTIRQ_RESTART; |
---|
254 | 265 | struct softirq_action *h; |
---|
255 | 266 | bool in_hardirq; |
---|
| 267 | + __u32 deferred; |
---|
256 | 268 | __u32 pending; |
---|
257 | 269 | int softirq_bit; |
---|
258 | 270 | |
---|
259 | 271 | /* |
---|
260 | | - * Mask out PF_MEMALLOC s current task context is borrowed for the |
---|
261 | | - * softirq. A softirq handled such as network RX might set PF_MEMALLOC |
---|
262 | | - * again if the socket is related to swap |
---|
| 272 | + * Mask out PF_MEMALLOC as the current task context is borrowed for the |
---|
| 273 | + * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC |
---|
| 274 | + * again if the socket is related to swapping. |
---|
263 | 275 | */ |
---|
264 | 276 | current->flags &= ~PF_MEMALLOC; |
---|
265 | 277 | |
---|
266 | 278 | pending = local_softirq_pending(); |
---|
| 279 | + deferred = softirq_deferred_for_rt(pending); |
---|
267 | 280 | account_irq_enter_time(current); |
---|
268 | | - |
---|
269 | 281 | __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); |
---|
270 | 282 | in_hardirq = lockdep_softirq_start(); |
---|
271 | 283 | |
---|
272 | 284 | restart: |
---|
273 | 285 | /* Reset the pending bitmask before enabling irqs */ |
---|
274 | | - set_softirq_pending(0); |
---|
| 286 | + set_softirq_pending(deferred); |
---|
| 287 | + __this_cpu_write(active_softirqs, pending); |
---|
275 | 288 | |
---|
276 | 289 | local_irq_enable(); |
---|
277 | 290 | |
---|
.. | .. |
---|
301 | 314 | pending >>= softirq_bit; |
---|
302 | 315 | } |
---|
303 | 316 | |
---|
304 | | - rcu_bh_qs(); |
---|
| 317 | + __this_cpu_write(active_softirqs, 0); |
---|
| 318 | + if (__this_cpu_read(ksoftirqd) == current) |
---|
| 319 | + rcu_softirq_qs(); |
---|
305 | 320 | local_irq_disable(); |
---|
306 | 321 | |
---|
307 | 322 | pending = local_softirq_pending(); |
---|
| 323 | + deferred = softirq_deferred_for_rt(pending); |
---|
| 324 | + |
---|
308 | 325 | if (pending) { |
---|
309 | 326 | if (time_before(jiffies, end) && !need_resched() && |
---|
310 | 327 | --max_restart) |
---|
311 | 328 | goto restart; |
---|
312 | 329 | |
---|
| 330 | +#ifndef CONFIG_RT_SOFTINT_OPTIMIZATION |
---|
313 | 331 | wakeup_softirqd(); |
---|
| 332 | +#endif |
---|
314 | 333 | } |
---|
315 | 334 | |
---|
| 335 | +#ifdef CONFIG_RT_SOFTINT_OPTIMIZATION |
---|
| 336 | + if (pending | deferred) |
---|
| 337 | + wakeup_softirqd(); |
---|
| 338 | +#endif |
---|
316 | 339 | lockdep_softirq_end(in_hardirq); |
---|
317 | 340 | account_irq_exit_time(current); |
---|
318 | 341 | __local_bh_enable(SOFTIRQ_OFFSET); |
---|
.. | .. |
---|
332 | 355 | |
---|
333 | 356 | pending = local_softirq_pending(); |
---|
334 | 357 | |
---|
335 | | - if (pending && !ksoftirqd_running(pending)) |
---|
| 358 | + if (pending) |
---|
336 | 359 | do_softirq_own_stack(); |
---|
337 | 360 | |
---|
338 | 361 | local_irq_restore(flags); |
---|
339 | 362 | } |
---|
340 | 363 | |
---|
341 | | -/* |
---|
342 | | - * Enter an interrupt context. |
---|
| 364 | +/** |
---|
| 365 | + * irq_enter_rcu - Enter an interrupt context with RCU watching |
---|
343 | 366 | */ |
---|
344 | | -void irq_enter(void) |
---|
| 367 | +void irq_enter_rcu(void) |
---|
345 | 368 | { |
---|
346 | | - rcu_irq_enter(); |
---|
347 | 369 | if (is_idle_task(current) && !in_interrupt()) { |
---|
348 | 370 | /* |
---|
349 | 371 | * Prevent raise_softirq from needlessly waking up ksoftirqd |
---|
.. | .. |
---|
353 | 375 | tick_irq_enter(); |
---|
354 | 376 | _local_bh_enable(); |
---|
355 | 377 | } |
---|
356 | | - |
---|
357 | 378 | __irq_enter(); |
---|
| 379 | +} |
---|
| 380 | + |
---|
| 381 | +/** |
---|
| 382 | + * irq_enter - Enter an interrupt context including RCU update |
---|
| 383 | + */ |
---|
| 384 | +void irq_enter(void) |
---|
| 385 | +{ |
---|
| 386 | + rcu_irq_enter(); |
---|
| 387 | + irq_enter_rcu(); |
---|
358 | 388 | } |
---|
359 | 389 | |
---|
360 | 390 | static inline void invoke_softirq(void) |
---|
361 | 391 | { |
---|
362 | | - if (ksoftirqd_running(local_softirq_pending())) |
---|
363 | | - return; |
---|
364 | | - |
---|
365 | 392 | if (!force_irqthreads) { |
---|
366 | 393 | #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK |
---|
367 | 394 | /* |
---|
.. | .. |
---|
396 | 423 | #endif |
---|
397 | 424 | } |
---|
398 | 425 | |
---|
399 | | -/* |
---|
400 | | - * Exit an interrupt context. Process softirqs if needed and possible: |
---|
401 | | - */ |
---|
402 | | -void irq_exit(void) |
---|
| 426 | +static inline void __irq_exit_rcu(void) |
---|
403 | 427 | { |
---|
404 | 428 | #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED |
---|
405 | 429 | local_irq_disable(); |
---|
.. | .. |
---|
412 | 436 | invoke_softirq(); |
---|
413 | 437 | |
---|
414 | 438 | tick_irq_exit(); |
---|
| 439 | +} |
---|
| 440 | + |
---|
| 441 | +/** |
---|
| 442 | + * irq_exit_rcu() - Exit an interrupt context without updating RCU |
---|
| 443 | + * |
---|
| 444 | + * Also processes softirqs if needed and possible. |
---|
| 445 | + */ |
---|
| 446 | +void irq_exit_rcu(void) |
---|
| 447 | +{ |
---|
| 448 | + __irq_exit_rcu(); |
---|
| 449 | + /* must be last! */ |
---|
| 450 | + lockdep_hardirq_exit(); |
---|
| 451 | +} |
---|
| 452 | + |
---|
| 453 | +/** |
---|
| 454 | + * irq_exit - Exit an interrupt context, update RCU and lockdep |
---|
| 455 | + * |
---|
| 456 | + * Also processes softirqs if needed and possible. |
---|
| 457 | + */ |
---|
| 458 | +void irq_exit(void) |
---|
| 459 | +{ |
---|
| 460 | + __irq_exit_rcu(); |
---|
415 | 461 | rcu_irq_exit(); |
---|
416 | | - trace_hardirq_exit(); /* must be last! */ |
---|
| 462 | + /* must be last! */ |
---|
| 463 | + lockdep_hardirq_exit(); |
---|
417 | 464 | } |
---|
418 | 465 | |
---|
419 | 466 | /* |
---|
.. | .. |
---|
447 | 494 | |
---|
448 | 495 | void __raise_softirq_irqoff(unsigned int nr) |
---|
449 | 496 | { |
---|
| 497 | + lockdep_assert_irqs_disabled(); |
---|
450 | 498 | trace_softirq_raise(nr); |
---|
451 | 499 | or_softirq_pending(1UL << nr); |
---|
452 | 500 | } |
---|
.. | .. |
---|
519 | 567 | if (!test_and_clear_bit(TASKLET_STATE_SCHED, |
---|
520 | 568 | &t->state)) |
---|
521 | 569 | BUG(); |
---|
522 | | - t->func(t->data); |
---|
| 570 | + if (t->use_callback) { |
---|
| 571 | + trace_tasklet_entry(t->callback); |
---|
| 572 | + t->callback(t); |
---|
| 573 | + trace_tasklet_exit(t->callback); |
---|
| 574 | + } else { |
---|
| 575 | + trace_tasklet_entry(t->func); |
---|
| 576 | + t->func(t->data); |
---|
| 577 | + trace_tasklet_exit(t->func); |
---|
| 578 | + } |
---|
523 | 579 | tasklet_unlock(t); |
---|
524 | 580 | continue; |
---|
525 | 581 | } |
---|
.. | .. |
---|
545 | 601 | tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ); |
---|
546 | 602 | } |
---|
547 | 603 | |
---|
| 604 | +void tasklet_setup(struct tasklet_struct *t, |
---|
| 605 | + void (*callback)(struct tasklet_struct *)) |
---|
| 606 | +{ |
---|
| 607 | + t->next = NULL; |
---|
| 608 | + t->state = 0; |
---|
| 609 | + atomic_set(&t->count, 0); |
---|
| 610 | + t->callback = callback; |
---|
| 611 | + t->use_callback = true; |
---|
| 612 | + t->data = 0; |
---|
| 613 | +} |
---|
| 614 | +EXPORT_SYMBOL(tasklet_setup); |
---|
| 615 | + |
---|
548 | 616 | void tasklet_init(struct tasklet_struct *t, |
---|
549 | 617 | void (*func)(unsigned long), unsigned long data) |
---|
550 | 618 | { |
---|
.. | .. |
---|
552 | 620 | t->state = 0; |
---|
553 | 621 | atomic_set(&t->count, 0); |
---|
554 | 622 | t->func = func; |
---|
| 623 | + t->use_callback = false; |
---|
555 | 624 | t->data = data; |
---|
556 | 625 | } |
---|
557 | 626 | EXPORT_SYMBOL(tasklet_init); |
---|
.. | .. |
---|
570 | 639 | clear_bit(TASKLET_STATE_SCHED, &t->state); |
---|
571 | 640 | } |
---|
572 | 641 | EXPORT_SYMBOL(tasklet_kill); |
---|
573 | | - |
---|
574 | | -/* |
---|
575 | | - * tasklet_hrtimer |
---|
576 | | - */ |
---|
577 | | - |
---|
578 | | -/* |
---|
579 | | - * The trampoline is called when the hrtimer expires. It schedules a tasklet |
---|
580 | | - * to run __tasklet_hrtimer_trampoline() which in turn will call the intended |
---|
581 | | - * hrtimer callback, but from softirq context. |
---|
582 | | - */ |
---|
583 | | -static enum hrtimer_restart __hrtimer_tasklet_trampoline(struct hrtimer *timer) |
---|
584 | | -{ |
---|
585 | | - struct tasklet_hrtimer *ttimer = |
---|
586 | | - container_of(timer, struct tasklet_hrtimer, timer); |
---|
587 | | - |
---|
588 | | - tasklet_hi_schedule(&ttimer->tasklet); |
---|
589 | | - return HRTIMER_NORESTART; |
---|
590 | | -} |
---|
591 | | - |
---|
592 | | -/* |
---|
593 | | - * Helper function which calls the hrtimer callback from |
---|
594 | | - * tasklet/softirq context |
---|
595 | | - */ |
---|
596 | | -static void __tasklet_hrtimer_trampoline(unsigned long data) |
---|
597 | | -{ |
---|
598 | | - struct tasklet_hrtimer *ttimer = (void *)data; |
---|
599 | | - enum hrtimer_restart restart; |
---|
600 | | - |
---|
601 | | - restart = ttimer->function(&ttimer->timer); |
---|
602 | | - if (restart != HRTIMER_NORESTART) |
---|
603 | | - hrtimer_restart(&ttimer->timer); |
---|
604 | | -} |
---|
605 | | - |
---|
606 | | -/** |
---|
607 | | - * tasklet_hrtimer_init - Init a tasklet/hrtimer combo for softirq callbacks |
---|
608 | | - * @ttimer: tasklet_hrtimer which is initialized |
---|
609 | | - * @function: hrtimer callback function which gets called from softirq context |
---|
610 | | - * @which_clock: clock id (CLOCK_MONOTONIC/CLOCK_REALTIME) |
---|
611 | | - * @mode: hrtimer mode (HRTIMER_MODE_ABS/HRTIMER_MODE_REL) |
---|
612 | | - */ |
---|
613 | | -void tasklet_hrtimer_init(struct tasklet_hrtimer *ttimer, |
---|
614 | | - enum hrtimer_restart (*function)(struct hrtimer *), |
---|
615 | | - clockid_t which_clock, enum hrtimer_mode mode) |
---|
616 | | -{ |
---|
617 | | - hrtimer_init(&ttimer->timer, which_clock, mode); |
---|
618 | | - ttimer->timer.function = __hrtimer_tasklet_trampoline; |
---|
619 | | - tasklet_init(&ttimer->tasklet, __tasklet_hrtimer_trampoline, |
---|
620 | | - (unsigned long)ttimer); |
---|
621 | | - ttimer->function = function; |
---|
622 | | -} |
---|
623 | | -EXPORT_SYMBOL_GPL(tasklet_hrtimer_init); |
---|
624 | 642 | |
---|
625 | 643 | void __init softirq_init(void) |
---|
626 | 644 | { |
---|
.. | .. |
---|
699 | 717 | /* Find end, append list for that CPU. */ |
---|
700 | 718 | if (&per_cpu(tasklet_vec, cpu).head != per_cpu(tasklet_vec, cpu).tail) { |
---|
701 | 719 | *__this_cpu_read(tasklet_vec.tail) = per_cpu(tasklet_vec, cpu).head; |
---|
702 | | - this_cpu_write(tasklet_vec.tail, per_cpu(tasklet_vec, cpu).tail); |
---|
| 720 | + __this_cpu_write(tasklet_vec.tail, per_cpu(tasklet_vec, cpu).tail); |
---|
703 | 721 | per_cpu(tasklet_vec, cpu).head = NULL; |
---|
704 | 722 | per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head; |
---|
705 | 723 | } |
---|