diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2017-04-24 19:02:09 -0400 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2017-04-26 19:32:16 -0400 |
commit | 1e9a038b7fe9a8c10ef1238f4e695d5fbe0dd594 (patch) | |
tree | d26c6a12f4a9d34fbb55a1169dabeff447810cc5 | |
parent | 7f6733c3c648ddd6cf459c1b80ad388a95452955 (diff) |
srcu: Expedited grace periods with reduced memory contention
Commit f60d231a87c5 ("srcu: Crude control of expedited grace periods")
introduced a per-srcu_struct atomic counter to track outstanding
requests for grace periods. This works, but represents a memory-contention
bottleneck. This commit therefore uses the srcu_node combining tree
to remove this bottleneck.
This commit adds new ->srcu_gp_seq_needed_exp fields to the
srcu_data, srcu_node, and srcu_struct structures, which track the
farthest-in-the-future grace period that must be expedited, which in
turn requires that all nearer-term grace periods also be expedited.
Requests for expediting start with the srcu_data structure, run up
through the srcu_node tree, and end at the srcu_struct structure.
Note that it may be necessary to expedite a grace period that just
now started, and this is handled by a new srcu_funnel_exp_start()
function, which is invoked when the grace period itself is already
in its way, but when that grace period was not marked as expedited.
A new srcu_get_delay() function returns zero if there is at least one
expedited SRCU grace period in flight, or SRCU_INTERVAL otherwise.
This function is used to calculate delays: Normal grace periods
are allowed to extend in order to cover more requests with a given
grace-period computation, which decreases per-request overhead.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Tested-by: Mike Galbraith <efault@gmx.de>
-rw-r--r-- | include/linux/srcutree.h | 4 | ||||
-rw-r--r-- | kernel/rcu/srcutree.c | 135 |
2 files changed, 98 insertions, 41 deletions
diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h index 3865717df124..86df48d3e97b 100644 --- a/include/linux/srcutree.h +++ b/include/linux/srcutree.h | |||
@@ -43,6 +43,7 @@ struct srcu_data { | |||
43 | spinlock_t lock ____cacheline_internodealigned_in_smp; | 43 | spinlock_t lock ____cacheline_internodealigned_in_smp; |
44 | struct rcu_segcblist srcu_cblist; /* List of callbacks.*/ | 44 | struct rcu_segcblist srcu_cblist; /* List of callbacks.*/ |
45 | unsigned long srcu_gp_seq_needed; /* Furthest future GP needed. */ | 45 | unsigned long srcu_gp_seq_needed; /* Furthest future GP needed. */ |
46 | unsigned long srcu_gp_seq_needed_exp; /* Furthest future exp GP. */ | ||
46 | bool srcu_cblist_invoking; /* Invoking these CBs? */ | 47 | bool srcu_cblist_invoking; /* Invoking these CBs? */ |
47 | struct delayed_work work; /* Context for CB invoking. */ | 48 | struct delayed_work work; /* Context for CB invoking. */ |
48 | struct rcu_head srcu_barrier_head; /* For srcu_barrier() use. */ | 49 | struct rcu_head srcu_barrier_head; /* For srcu_barrier() use. */ |
@@ -63,6 +64,7 @@ struct srcu_node { | |||
63 | /* is > ->srcu_gq_seq. */ | 64 | /* is > ->srcu_gq_seq. */ |
64 | unsigned long srcu_data_have_cbs[4]; /* Which srcu_data structs */ | 65 | unsigned long srcu_data_have_cbs[4]; /* Which srcu_data structs */ |
65 | /* have CBs for given GP? */ | 66 | /* have CBs for given GP? */ |
67 | unsigned long srcu_gp_seq_needed_exp; /* Furthest future exp GP. */ | ||
66 | struct srcu_node *srcu_parent; /* Next up in tree. */ | 68 | struct srcu_node *srcu_parent; /* Next up in tree. */ |
67 | int grplo; /* Least CPU for node. */ | 69 | int grplo; /* Least CPU for node. */ |
68 | int grphi; /* Biggest CPU for node. */ | 70 | int grphi; /* Biggest CPU for node. */ |
@@ -81,7 +83,7 @@ struct srcu_struct { | |||
81 | unsigned int srcu_idx; /* Current rdr array element. */ | 83 | unsigned int srcu_idx; /* Current rdr array element. */ |
82 | unsigned long srcu_gp_seq; /* Grace-period seq #. */ | 84 | unsigned long srcu_gp_seq; /* Grace-period seq #. */ |
83 | unsigned long srcu_gp_seq_needed; /* Latest gp_seq needed. */ | 85 | unsigned long srcu_gp_seq_needed; /* Latest gp_seq needed. */ |
84 | atomic_t srcu_exp_cnt; /* # ongoing expedited GPs. */ | 86 | unsigned long srcu_gp_seq_needed_exp; /* Furthest future exp GP. */ |
85 | struct srcu_data __percpu *sda; /* Per-CPU srcu_data array. */ | 87 | struct srcu_data __percpu *sda; /* Per-CPU srcu_data array. */ |
86 | unsigned long srcu_barrier_seq; /* srcu_barrier seq #. */ | 88 | unsigned long srcu_barrier_seq; /* srcu_barrier seq #. */ |
87 | struct mutex srcu_barrier_mutex; /* Serialize barrier ops. */ | 89 | struct mutex srcu_barrier_mutex; /* Serialize barrier ops. */ |
diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 72b6cce5f591..4b98e6f45166 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c | |||
@@ -72,6 +72,7 @@ static void init_srcu_struct_nodes(struct srcu_struct *sp, bool is_static) | |||
72 | snp->srcu_have_cbs[i] = 0; | 72 | snp->srcu_have_cbs[i] = 0; |
73 | snp->srcu_data_have_cbs[i] = 0; | 73 | snp->srcu_data_have_cbs[i] = 0; |
74 | } | 74 | } |
75 | snp->srcu_gp_seq_needed_exp = 0; | ||
75 | snp->grplo = -1; | 76 | snp->grplo = -1; |
76 | snp->grphi = -1; | 77 | snp->grphi = -1; |
77 | if (snp == &sp->node[0]) { | 78 | if (snp == &sp->node[0]) { |
@@ -102,6 +103,7 @@ static void init_srcu_struct_nodes(struct srcu_struct *sp, bool is_static) | |||
102 | rcu_segcblist_init(&sdp->srcu_cblist); | 103 | rcu_segcblist_init(&sdp->srcu_cblist); |
103 | sdp->srcu_cblist_invoking = false; | 104 | sdp->srcu_cblist_invoking = false; |
104 | sdp->srcu_gp_seq_needed = sp->srcu_gp_seq; | 105 | sdp->srcu_gp_seq_needed = sp->srcu_gp_seq; |
106 | sdp->srcu_gp_seq_needed_exp = sp->srcu_gp_seq; | ||
105 | sdp->mynode = &snp_first[cpu / levelspread[level]]; | 107 | sdp->mynode = &snp_first[cpu / levelspread[level]]; |
106 | for (snp = sdp->mynode; snp != NULL; snp = snp->srcu_parent) { | 108 | for (snp = sdp->mynode; snp != NULL; snp = snp->srcu_parent) { |
107 | if (snp->grplo < 0) | 109 | if (snp->grplo < 0) |
@@ -135,7 +137,6 @@ static int init_srcu_struct_fields(struct srcu_struct *sp, bool is_static) | |||
135 | mutex_init(&sp->srcu_gp_mutex); | 137 | mutex_init(&sp->srcu_gp_mutex); |
136 | sp->srcu_idx = 0; | 138 | sp->srcu_idx = 0; |
137 | sp->srcu_gp_seq = 0; | 139 | sp->srcu_gp_seq = 0; |
138 | atomic_set(&sp->srcu_exp_cnt, 0); | ||
139 | sp->srcu_barrier_seq = 0; | 140 | sp->srcu_barrier_seq = 0; |
140 | mutex_init(&sp->srcu_barrier_mutex); | 141 | mutex_init(&sp->srcu_barrier_mutex); |
141 | atomic_set(&sp->srcu_barrier_cpu_cnt, 0); | 142 | atomic_set(&sp->srcu_barrier_cpu_cnt, 0); |
@@ -143,6 +144,7 @@ static int init_srcu_struct_fields(struct srcu_struct *sp, bool is_static) | |||
143 | if (!is_static) | 144 | if (!is_static) |
144 | sp->sda = alloc_percpu(struct srcu_data); | 145 | sp->sda = alloc_percpu(struct srcu_data); |
145 | init_srcu_struct_nodes(sp, is_static); | 146 | init_srcu_struct_nodes(sp, is_static); |
147 | sp->srcu_gp_seq_needed_exp = 0; | ||
146 | smp_store_release(&sp->srcu_gp_seq_needed, 0); /* Init done. */ | 148 | smp_store_release(&sp->srcu_gp_seq_needed, 0); /* Init done. */ |
147 | return sp->sda ? 0 : -ENOMEM; | 149 | return sp->sda ? 0 : -ENOMEM; |
148 | } | 150 | } |
@@ -307,6 +309,18 @@ static bool srcu_readers_active(struct srcu_struct *sp) | |||
307 | 309 | ||
308 | #define SRCU_INTERVAL 1 | 310 | #define SRCU_INTERVAL 1 |
309 | 311 | ||
312 | /* | ||
313 | * Return grace-period delay, zero if there are expedited grace | ||
314 | * periods pending, SRCU_INTERVAL otherwise. | ||
315 | */ | ||
316 | static unsigned long srcu_get_delay(struct srcu_struct *sp) | ||
317 | { | ||
318 | if (ULONG_CMP_LT(READ_ONCE(sp->srcu_gp_seq), | ||
319 | READ_ONCE(sp->srcu_gp_seq_needed_exp))) | ||
320 | return 0; | ||
321 | return SRCU_INTERVAL; | ||
322 | } | ||
323 | |||
310 | /** | 324 | /** |
311 | * cleanup_srcu_struct - deconstruct a sleep-RCU structure | 325 | * cleanup_srcu_struct - deconstruct a sleep-RCU structure |
312 | * @sp: structure to clean up. | 326 | * @sp: structure to clean up. |
@@ -318,7 +332,8 @@ void cleanup_srcu_struct(struct srcu_struct *sp) | |||
318 | { | 332 | { |
319 | int cpu; | 333 | int cpu; |
320 | 334 | ||
321 | WARN_ON_ONCE(atomic_read(&sp->srcu_exp_cnt)); | 335 | if (WARN_ON(!srcu_get_delay(sp))) |
336 | return; /* Leakage unless caller handles error. */ | ||
322 | if (WARN_ON(srcu_readers_active(sp))) | 337 | if (WARN_ON(srcu_readers_active(sp))) |
323 | return; /* Leakage unless caller handles error. */ | 338 | return; /* Leakage unless caller handles error. */ |
324 | flush_delayed_work(&sp->work); | 339 | flush_delayed_work(&sp->work); |
@@ -444,15 +459,14 @@ static void srcu_schedule_cbs_sdp(struct srcu_data *sdp, unsigned long delay) | |||
444 | * schedule this invocation on the corresponding CPUs. | 459 | * schedule this invocation on the corresponding CPUs. |
445 | */ | 460 | */ |
446 | static void srcu_schedule_cbs_snp(struct srcu_struct *sp, struct srcu_node *snp, | 461 | static void srcu_schedule_cbs_snp(struct srcu_struct *sp, struct srcu_node *snp, |
447 | unsigned long mask) | 462 | unsigned long mask, unsigned long delay) |
448 | { | 463 | { |
449 | int cpu; | 464 | int cpu; |
450 | 465 | ||
451 | for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) { | 466 | for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) { |
452 | if (!(mask & (1 << (cpu - snp->grplo)))) | 467 | if (!(mask & (1 << (cpu - snp->grplo)))) |
453 | continue; | 468 | continue; |
454 | srcu_schedule_cbs_sdp(per_cpu_ptr(sp->sda, cpu), | 469 | srcu_schedule_cbs_sdp(per_cpu_ptr(sp->sda, cpu), delay); |
455 | atomic_read(&sp->srcu_exp_cnt) ? 0 : SRCU_INTERVAL); | ||
456 | } | 470 | } |
457 | } | 471 | } |
458 | 472 | ||
@@ -467,6 +481,7 @@ static void srcu_schedule_cbs_snp(struct srcu_struct *sp, struct srcu_node *snp, | |||
467 | */ | 481 | */ |
468 | static void srcu_gp_end(struct srcu_struct *sp) | 482 | static void srcu_gp_end(struct srcu_struct *sp) |
469 | { | 483 | { |
484 | unsigned long cbdelay; | ||
470 | bool cbs; | 485 | bool cbs; |
471 | unsigned long gpseq; | 486 | unsigned long gpseq; |
472 | int idx; | 487 | int idx; |
@@ -481,8 +496,11 @@ static void srcu_gp_end(struct srcu_struct *sp) | |||
481 | spin_lock_irq(&sp->gp_lock); | 496 | spin_lock_irq(&sp->gp_lock); |
482 | idx = rcu_seq_state(sp->srcu_gp_seq); | 497 | idx = rcu_seq_state(sp->srcu_gp_seq); |
483 | WARN_ON_ONCE(idx != SRCU_STATE_SCAN2); | 498 | WARN_ON_ONCE(idx != SRCU_STATE_SCAN2); |
499 | cbdelay = srcu_get_delay(sp); | ||
484 | rcu_seq_end(&sp->srcu_gp_seq); | 500 | rcu_seq_end(&sp->srcu_gp_seq); |
485 | gpseq = rcu_seq_current(&sp->srcu_gp_seq); | 501 | gpseq = rcu_seq_current(&sp->srcu_gp_seq); |
502 | if (ULONG_CMP_LT(sp->srcu_gp_seq_needed_exp, gpseq)) | ||
503 | sp->srcu_gp_seq_needed_exp = gpseq; | ||
486 | spin_unlock_irq(&sp->gp_lock); | 504 | spin_unlock_irq(&sp->gp_lock); |
487 | mutex_unlock(&sp->srcu_gp_mutex); | 505 | mutex_unlock(&sp->srcu_gp_mutex); |
488 | /* A new grace period can start at this point. But only one. */ | 506 | /* A new grace period can start at this point. But only one. */ |
@@ -497,12 +515,14 @@ static void srcu_gp_end(struct srcu_struct *sp) | |||
497 | cbs = snp->srcu_have_cbs[idx] == gpseq; | 515 | cbs = snp->srcu_have_cbs[idx] == gpseq; |
498 | snp->srcu_have_cbs[idx] = gpseq; | 516 | snp->srcu_have_cbs[idx] = gpseq; |
499 | rcu_seq_set_state(&snp->srcu_have_cbs[idx], 1); | 517 | rcu_seq_set_state(&snp->srcu_have_cbs[idx], 1); |
518 | if (ULONG_CMP_LT(snp->srcu_gp_seq_needed_exp, gpseq)) | ||
519 | snp->srcu_gp_seq_needed_exp = gpseq; | ||
500 | mask = snp->srcu_data_have_cbs[idx]; | 520 | mask = snp->srcu_data_have_cbs[idx]; |
501 | snp->srcu_data_have_cbs[idx] = 0; | 521 | snp->srcu_data_have_cbs[idx] = 0; |
502 | spin_unlock_irq(&snp->lock); | 522 | spin_unlock_irq(&snp->lock); |
503 | if (cbs) { | 523 | if (cbs) { |
504 | smp_mb(); /* GP end before CB invocation. */ | 524 | smp_mb(); /* GP end before CB invocation. */ |
505 | srcu_schedule_cbs_snp(sp, snp, mask); | 525 | srcu_schedule_cbs_snp(sp, snp, mask, cbdelay); |
506 | } | 526 | } |
507 | } | 527 | } |
508 | 528 | ||
@@ -517,25 +537,52 @@ static void srcu_gp_end(struct srcu_struct *sp) | |||
517 | srcu_gp_start(sp); | 537 | srcu_gp_start(sp); |
518 | spin_unlock_irq(&sp->gp_lock); | 538 | spin_unlock_irq(&sp->gp_lock); |
519 | /* Throttle expedited grace periods: Should be rare! */ | 539 | /* Throttle expedited grace periods: Should be rare! */ |
520 | srcu_reschedule(sp, atomic_read(&sp->srcu_exp_cnt) && | 540 | srcu_reschedule(sp, rcu_seq_ctr(gpseq) & 0x3ff |
521 | rcu_seq_ctr(gpseq) & 0xf | 541 | ? 0 : SRCU_INTERVAL); |
522 | ? 0 | ||
523 | : SRCU_INTERVAL); | ||
524 | } else { | 542 | } else { |
525 | spin_unlock_irq(&sp->gp_lock); | 543 | spin_unlock_irq(&sp->gp_lock); |
526 | } | 544 | } |
527 | } | 545 | } |
528 | 546 | ||
529 | /* | 547 | /* |
548 | * Funnel-locking scheme to scalably mediate many concurrent expedited | ||
549 | * grace-period requests. This function is invoked for the first known | ||
550 | * expedited request for a grace period that has already been requested, | ||
551 | * but without expediting. To start a completely new grace period, | ||
552 | * whether expedited or not, use srcu_funnel_gp_start() instead. | ||
553 | */ | ||
554 | static void srcu_funnel_exp_start(struct srcu_struct *sp, struct srcu_node *snp, | ||
555 | unsigned long s) | ||
556 | { | ||
557 | unsigned long flags; | ||
558 | |||
559 | for (; snp != NULL; snp = snp->srcu_parent) { | ||
560 | if (rcu_seq_done(&sp->srcu_gp_seq, s) || | ||
561 | ULONG_CMP_GE(READ_ONCE(snp->srcu_gp_seq_needed_exp), s)) | ||
562 | return; | ||
563 | spin_lock_irqsave(&snp->lock, flags); | ||
564 | if (ULONG_CMP_GE(snp->srcu_gp_seq_needed_exp, s)) { | ||
565 | spin_unlock_irqrestore(&snp->lock, flags); | ||
566 | return; | ||
567 | } | ||
568 | WRITE_ONCE(snp->srcu_gp_seq_needed_exp, s); | ||
569 | spin_unlock_irqrestore(&snp->lock, flags); | ||
570 | } | ||
571 | spin_lock_irqsave(&sp->gp_lock, flags); | ||
572 | if (!ULONG_CMP_LT(sp->srcu_gp_seq_needed_exp, s)) | ||
573 | sp->srcu_gp_seq_needed_exp = s; | ||
574 | spin_unlock_irqrestore(&sp->gp_lock, flags); | ||
575 | } | ||
576 | |||
577 | /* | ||
530 | * Funnel-locking scheme to scalably mediate many concurrent grace-period | 578 | * Funnel-locking scheme to scalably mediate many concurrent grace-period |
531 | * requests. The winner has to do the work of actually starting grace | 579 | * requests. The winner has to do the work of actually starting grace |
532 | * period s. Losers must either ensure that their desired grace-period | 580 | * period s. Losers must either ensure that their desired grace-period |
533 | * number is recorded on at least their leaf srcu_node structure, or they | 581 | * number is recorded on at least their leaf srcu_node structure, or they |
534 | * must take steps to invoke their own callbacks. | 582 | * must take steps to invoke their own callbacks. |
535 | */ | 583 | */ |
536 | static void srcu_funnel_gp_start(struct srcu_struct *sp, | 584 | static void srcu_funnel_gp_start(struct srcu_struct *sp, struct srcu_data *sdp, |
537 | struct srcu_data *sdp, | 585 | unsigned long s, bool do_norm) |
538 | unsigned long s) | ||
539 | { | 586 | { |
540 | unsigned long flags; | 587 | unsigned long flags; |
541 | int idx = rcu_seq_ctr(s) % ARRAY_SIZE(sdp->mynode->srcu_have_cbs); | 588 | int idx = rcu_seq_ctr(s) % ARRAY_SIZE(sdp->mynode->srcu_have_cbs); |
@@ -554,13 +601,20 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, | |||
554 | spin_unlock_irqrestore(&snp->lock, flags); | 601 | spin_unlock_irqrestore(&snp->lock, flags); |
555 | if (snp == sdp->mynode && snp_seq != s) { | 602 | if (snp == sdp->mynode && snp_seq != s) { |
556 | smp_mb(); /* CBs after GP! */ | 603 | smp_mb(); /* CBs after GP! */ |
557 | srcu_schedule_cbs_sdp(sdp, 0); | 604 | srcu_schedule_cbs_sdp(sdp, do_norm |
605 | ? SRCU_INTERVAL | ||
606 | : 0); | ||
607 | return; | ||
558 | } | 608 | } |
609 | if (!do_norm) | ||
610 | srcu_funnel_exp_start(sp, snp, s); | ||
559 | return; | 611 | return; |
560 | } | 612 | } |
561 | snp->srcu_have_cbs[idx] = s; | 613 | snp->srcu_have_cbs[idx] = s; |
562 | if (snp == sdp->mynode) | 614 | if (snp == sdp->mynode) |
563 | snp->srcu_data_have_cbs[idx] |= sdp->grpmask; | 615 | snp->srcu_data_have_cbs[idx] |= sdp->grpmask; |
616 | if (!do_norm && ULONG_CMP_LT(snp->srcu_gp_seq_needed_exp, s)) | ||
617 | snp->srcu_gp_seq_needed_exp = s; | ||
564 | spin_unlock_irqrestore(&snp->lock, flags); | 618 | spin_unlock_irqrestore(&snp->lock, flags); |
565 | } | 619 | } |
566 | 620 | ||
@@ -573,6 +627,8 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, | |||
573 | */ | 627 | */ |
574 | smp_store_release(&sp->srcu_gp_seq_needed, s); /*^^^*/ | 628 | smp_store_release(&sp->srcu_gp_seq_needed, s); /*^^^*/ |
575 | } | 629 | } |
630 | if (!do_norm && ULONG_CMP_LT(sp->srcu_gp_seq_needed_exp, s)) | ||
631 | sp->srcu_gp_seq_needed_exp = s; | ||
576 | 632 | ||
577 | /* If grace period not already done and none in progress, start it. */ | 633 | /* If grace period not already done and none in progress, start it. */ |
578 | if (!rcu_seq_done(&sp->srcu_gp_seq, s) && | 634 | if (!rcu_seq_done(&sp->srcu_gp_seq, s) && |
@@ -580,9 +636,7 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, | |||
580 | WARN_ON_ONCE(ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed)); | 636 | WARN_ON_ONCE(ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed)); |
581 | srcu_gp_start(sp); | 637 | srcu_gp_start(sp); |
582 | queue_delayed_work(system_power_efficient_wq, &sp->work, | 638 | queue_delayed_work(system_power_efficient_wq, &sp->work, |
583 | atomic_read(&sp->srcu_exp_cnt) | 639 | srcu_get_delay(sp)); |
584 | ? 0 | ||
585 | : SRCU_INTERVAL); | ||
586 | } | 640 | } |
587 | spin_unlock_irqrestore(&sp->gp_lock, flags); | 641 | spin_unlock_irqrestore(&sp->gp_lock, flags); |
588 | } | 642 | } |
@@ -597,7 +651,7 @@ static bool try_check_zero(struct srcu_struct *sp, int idx, int trycount) | |||
597 | for (;;) { | 651 | for (;;) { |
598 | if (srcu_readers_active_idx_check(sp, idx)) | 652 | if (srcu_readers_active_idx_check(sp, idx)) |
599 | return true; | 653 | return true; |
600 | if (--trycount + !!atomic_read(&sp->srcu_exp_cnt) <= 0) | 654 | if (--trycount + !srcu_get_delay(sp) <= 0) |
601 | return false; | 655 | return false; |
602 | udelay(SRCU_RETRY_CHECK_DELAY); | 656 | udelay(SRCU_RETRY_CHECK_DELAY); |
603 | } | 657 | } |
@@ -650,10 +704,11 @@ static void srcu_flip(struct srcu_struct *sp) | |||
650 | * srcu_read_lock(), and srcu_read_unlock() that are all passed the same | 704 | * srcu_read_lock(), and srcu_read_unlock() that are all passed the same |
651 | * srcu_struct structure. | 705 | * srcu_struct structure. |
652 | */ | 706 | */ |
653 | void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp, | 707 | void __call_srcu(struct srcu_struct *sp, struct rcu_head *rhp, |
654 | rcu_callback_t func) | 708 | rcu_callback_t func, bool do_norm) |
655 | { | 709 | { |
656 | unsigned long flags; | 710 | unsigned long flags; |
711 | bool needexp = false; | ||
657 | bool needgp = false; | 712 | bool needgp = false; |
658 | unsigned long s; | 713 | unsigned long s; |
659 | struct srcu_data *sdp; | 714 | struct srcu_data *sdp; |
@@ -672,16 +727,28 @@ void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp, | |||
672 | sdp->srcu_gp_seq_needed = s; | 727 | sdp->srcu_gp_seq_needed = s; |
673 | needgp = true; | 728 | needgp = true; |
674 | } | 729 | } |
730 | if (!do_norm && ULONG_CMP_LT(sdp->srcu_gp_seq_needed_exp, s)) { | ||
731 | sdp->srcu_gp_seq_needed_exp = s; | ||
732 | needexp = true; | ||
733 | } | ||
675 | spin_unlock_irqrestore(&sdp->lock, flags); | 734 | spin_unlock_irqrestore(&sdp->lock, flags); |
676 | if (needgp) | 735 | if (needgp) |
677 | srcu_funnel_gp_start(sp, sdp, s); | 736 | srcu_funnel_gp_start(sp, sdp, s, do_norm); |
737 | else if (needexp) | ||
738 | srcu_funnel_exp_start(sp, sdp->mynode, s); | ||
739 | } | ||
740 | |||
741 | void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp, | ||
742 | rcu_callback_t func) | ||
743 | { | ||
744 | __call_srcu(sp, rhp, func, true); | ||
678 | } | 745 | } |
679 | EXPORT_SYMBOL_GPL(call_srcu); | 746 | EXPORT_SYMBOL_GPL(call_srcu); |
680 | 747 | ||
681 | /* | 748 | /* |
682 | * Helper function for synchronize_srcu() and synchronize_srcu_expedited(). | 749 | * Helper function for synchronize_srcu() and synchronize_srcu_expedited(). |
683 | */ | 750 | */ |
684 | static void __synchronize_srcu(struct srcu_struct *sp) | 751 | static void __synchronize_srcu(struct srcu_struct *sp, bool do_norm) |
685 | { | 752 | { |
686 | struct rcu_synchronize rcu; | 753 | struct rcu_synchronize rcu; |
687 | 754 | ||
@@ -697,7 +764,7 @@ static void __synchronize_srcu(struct srcu_struct *sp) | |||
697 | check_init_srcu_struct(sp); | 764 | check_init_srcu_struct(sp); |
698 | init_completion(&rcu.completion); | 765 | init_completion(&rcu.completion); |
699 | init_rcu_head_on_stack(&rcu.head); | 766 | init_rcu_head_on_stack(&rcu.head); |
700 | call_srcu(sp, &rcu.head, wakeme_after_rcu); | 767 | __call_srcu(sp, &rcu.head, wakeme_after_rcu, do_norm); |
701 | wait_for_completion(&rcu.completion); | 768 | wait_for_completion(&rcu.completion); |
702 | destroy_rcu_head_on_stack(&rcu.head); | 769 | destroy_rcu_head_on_stack(&rcu.head); |
703 | } | 770 | } |
@@ -714,18 +781,7 @@ static void __synchronize_srcu(struct srcu_struct *sp) | |||
714 | */ | 781 | */ |
715 | void synchronize_srcu_expedited(struct srcu_struct *sp) | 782 | void synchronize_srcu_expedited(struct srcu_struct *sp) |
716 | { | 783 | { |
717 | bool do_norm = rcu_gp_is_normal(); | 784 | __synchronize_srcu(sp, rcu_gp_is_normal()); |
718 | |||
719 | check_init_srcu_struct(sp); | ||
720 | if (!do_norm) { | ||
721 | atomic_inc(&sp->srcu_exp_cnt); | ||
722 | smp_mb__after_atomic(); /* increment before GP. */ | ||
723 | } | ||
724 | __synchronize_srcu(sp); | ||
725 | if (!do_norm) { | ||
726 | smp_mb__before_atomic(); /* GP before decrement. */ | ||
727 | WARN_ON_ONCE(atomic_dec_return(&sp->srcu_exp_cnt) < 0); | ||
728 | } | ||
729 | } | 785 | } |
730 | EXPORT_SYMBOL_GPL(synchronize_srcu_expedited); | 786 | EXPORT_SYMBOL_GPL(synchronize_srcu_expedited); |
731 | 787 | ||
@@ -773,7 +829,7 @@ void synchronize_srcu(struct srcu_struct *sp) | |||
773 | if (rcu_gp_is_expedited()) | 829 | if (rcu_gp_is_expedited()) |
774 | synchronize_srcu_expedited(sp); | 830 | synchronize_srcu_expedited(sp); |
775 | else | 831 | else |
776 | __synchronize_srcu(sp); | 832 | __synchronize_srcu(sp, true); |
777 | } | 833 | } |
778 | EXPORT_SYMBOL_GPL(synchronize_srcu); | 834 | EXPORT_SYMBOL_GPL(synchronize_srcu); |
779 | 835 | ||
@@ -1008,14 +1064,13 @@ void process_srcu(struct work_struct *work) | |||
1008 | sp = container_of(work, struct srcu_struct, work.work); | 1064 | sp = container_of(work, struct srcu_struct, work.work); |
1009 | 1065 | ||
1010 | srcu_advance_state(sp); | 1066 | srcu_advance_state(sp); |
1011 | srcu_reschedule(sp, atomic_read(&sp->srcu_exp_cnt) ? 0 : SRCU_INTERVAL); | 1067 | srcu_reschedule(sp, srcu_get_delay(sp)); |
1012 | } | 1068 | } |
1013 | EXPORT_SYMBOL_GPL(process_srcu); | 1069 | EXPORT_SYMBOL_GPL(process_srcu); |
1014 | 1070 | ||
1015 | void srcutorture_get_gp_data(enum rcutorture_type test_type, | 1071 | void srcutorture_get_gp_data(enum rcutorture_type test_type, |
1016 | struct srcu_struct *sp, int *flags, | 1072 | struct srcu_struct *sp, int *flags, |
1017 | unsigned long *gpnum, | 1073 | unsigned long *gpnum, unsigned long *completed) |
1018 | unsigned long *completed) | ||
1019 | { | 1074 | { |
1020 | if (test_type != SRCU_FLAVOR) | 1075 | if (test_type != SRCU_FLAVOR) |
1021 | return; | 1076 | return; |