Skip to contentSkip to conversation

percpu_rwsem: fix missed wakeup due to reordering of load

PATCH In review Prateek Sood 4 participants 10 messages Nov 30, 2018 – Jan 21, 2019

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 percpu_rwsem: fix missed wakeup due to reordering of load

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

smp_mb()

[L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

This can happen as smp_rmb() in rcuwait_wake_up() will provide ordering of load before barrier with load and store after barrier for arm64 architecture. Here the requirement is to order store before smp_rmb() with load after the smp_rmb().

For the usage of rcuwait_wake_up() in __percpu_up_read() full barrier (smp_mb) is required to complete the constraint of rcuwait_wake_up().

---
 kernel/exit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/exit.c +1 -1
index f1d74f0..a10820d 100644
@@ -306,7 +306,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,
Signature · 2 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., 
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
#1 lore raw
Quoted 28 lines · Prateek Sood

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter()
//gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

                                            smp_mb()

                                           [L] readers_active_check() 

//fails
schedule()

[S] __this_cpu_dec(read_count)

Quoted 9 lines

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

Hmm yeah we don't want rcu_wake_up() to get hoisted over the __this_cpu_dec(read_count). The smp_rmb() does not make sense to me here in the first place. Did you run into this scenario by code inspection or you actually it the issue?

Thanks,
Davidlohr

#2 lore raw
Quoted 37 lines · Davidlohr Bueso
Quoted 26 lines · Prateek Sood

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1                                               P2 percpu_up_read() path                      percpu_down_write() path

                                          rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false        down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb()                                  [S] w->task = current

                                            smp_mb()

                                           [L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Quoted 9 lines

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

     WAIT                WAKE
[S] tsk = current      [S] cond = true
MB (A)                        MB (B)
[L] cond          [L] tsk

Quoted 4 lines

Hmm yeah we don't want rcu_wake_up() to get hoisted over the __this_cpu_dec(read_count). The smp_rmb() does not make sense to me here in the first place. Did you run into this scenario by code inspection or you actually it the issue?

Thanks,
Davidlohr

I have checked one issue where it seems that cpu hotplug code path is not able to get cpu_hotplug_lock in write mode and there is a reader pending for cpu hotplug path to release percpu_rw_semaphore->rwsem to acquire cpu_hotplug_lock. This caused a deadlock.

From code inspection also it seems to be not adhering to arm64 smp_rmb() constraint of load/load-store ordering guarantee.

Thanks,
Prateek

Signature · 3 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
#3 lore raw
Quoted 49 lines · Prateek Sood
Quoted 37 lines · Davidlohr Bueso
Quoted 26 lines · Prateek Sood

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1                                               P2 percpu_up_read() path                      percpu_down_write() path

                                          rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false        down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb()                                  [S] w->task = current

                                            smp_mb()

                                           [L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Quoted 9 lines

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

     WAIT                WAKE
[S] tsk = current      [S] cond = true
MB (A)                        MB (B)
[L] cond          [L] tsk

Quoted 4 lines

Hmm yeah we don't want rcu_wake_up() to get hoisted over the __this_cpu_dec(read_count). The smp_rmb() does not make sense to me here in the first place. Did you run into this scenario by code inspection or you actually it the issue?

Thanks,
Davidlohr

I have checked one issue where it seems that cpu hotplug code path is not able to get cpu_hotplug_lock in write mode and there is a reader pending for cpu hotplug path to release percpu_rw_semaphore->rwsem to acquire cpu_hotplug_lock. This caused a deadlock.

Quoted 7 lines

From code inspection also it seems to be not adhering to arm64 smp_rmb() constraint of load/load-store ordering guarantee.

Thanks,
Prateek

Folks,

Please confirm if the suspicion of smp_rmb is correct. IMO, it should be smp_mb() translating to dmb ish.

Thanks
Prateek

Signature · 3 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
#4 lore raw
Quoted 2 lines · Prateek Sood

Please confirm if the suspicion of smp_rmb is correct. IMO, it should be smp_mb() translating to dmb ish.

Feel free to add my ack. This should also be Cc to stable as of v4.11.

Fixes 8f95c90ceb54 (sched/wait, RCU: Introduce rcuwait machinery)

Thanks,
Davidlohr

#5 lore raw

PATCH 1/1 percpu_rwsem: fix missed wakeup due to reordering of load

P1 is releaseing the cpu_hotplug_lock and P2 is acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

smp_mb()

[L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Since load of task can result in NULL, it can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

This can happen as smp_rmb() in rcuwait_wake_up() will provide ordering of load before barrier with load and store after barrier for arm64 architecture. Here the requirement is to order store before smp_rmb() with load after the smp_rmb().

For the usage of rcuwait_wake_up() in __percpu_up_read() full barrier (smp_mb) is required to complete the constraint of rcuwait_wake_up().

---
 kernel/exit.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/exit.c +2 -2
index ac1a814..696e0e1 100644
@@ -298,7 +298,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	/*
 	 * Order condition vs @task, such that everything prior to the load
 	 * of @task is visible. This is the condition as to why the user called
-	 * rcuwait_trywake() in the first place. Pairs with set_current_state()
+	 * rcuwait_wake_up() in the first place. Pairs with set_current_state()
 	 * barrier (A) in rcuwait_wait_event().
 	 *
 	 *    WAIT                WAKE
@@ -306,7 +306,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,
Signature · 2 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., 
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
#6 lore raw
Quoted 37 lines · Prateek Sood

P1 is releaseing the cpu_hotplug_lock and P2 is acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

smp_mb()

[L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Since load of task can result in NULL, it can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

This can happen as smp_rmb() in rcuwait_wake_up() will provide ordering of load before barrier with load and store after barrier for arm64 architecture. Here the requirement is to order store before smp_rmb() with load after the smp_rmb().

Quoted 5 lines

For the usage of rcuwait_wake_up() in __percpu_up_read() full barrier (smp_mb) is required to complete the constraint of rcuwait_wake_up().

It looks like Peter has already queued this, c.f.,

https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/commit/?h=locking/core&id=73685b8af253cf32b1b41b3045f2828c6fb2439e

with a modified changelog and my Reviewed-by (that I confirm).

I can't tell how/when this is going to be upstreamed (guess via -tip), Peter?

Andrea

Quoted 31 lines
---
 kernel/exit.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/exit.c +2 -2
index ac1a814..696e0e1 100644
@@ -298,7 +298,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	/*
 	 * Order condition vs @task, such that everything prior to the load
 	 * of @task is visible. This is the condition as to why the user called
-	 * rcuwait_trywake() in the first place. Pairs with set_current_state()
+	 * rcuwait_wake_up() in the first place. Pairs with set_current_state()
 	 * barrier (A) in rcuwait_wait_event().
 	 *
 	 *    WAIT                WAKE
@@ -306,7 +306,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,
Signature · 2 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., 
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
Reviewed-by on v1 1/1
#7 lore raw
In reply to Prateek Sood
Quoted 40 lines · Prateek Sood

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

smp_mb()

[L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

This can happen as smp_rmb() in rcuwait_wake_up() will provide ordering of load before barrier with load and store after barrier for arm64 architecture. Here the requirement is to order store before smp_rmb() with load after the smp_rmb().

Quoted 4 lines

For the usage of rcuwait_wake_up() in __percpu_up_read() full barrier (smp_mb) is required to complete the constraint of rcuwait_wake_up().

I know this is going to sound ridiculous (coming from me or from the Italian that I am), but it looks like we could both work on our English. ;-)

But the fix seems correct to me:

It might be a good idea to integrate this fix with fixes to the inline comments/annotations: for example, I see that the comment in rcuwait_wake_up() mentions a non-existing rcuwait_trywake(); moreover, the memory-barrier annotation "B" is used also for the smp_mb() preceding the __this_cpu_dec() in __percpu_up_read().

Andrea

Quoted 21 lines
---
 kernel/exit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/exit.c +1 -1
index f1d74f0..a10820d 100644
@@ -306,7 +306,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,
Signature · 2 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., 
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
#8 lore raw
Quoted 50 lines · Andrea Parri
Quoted 40 lines · Prateek Sood

In a scenario where cpu_hotplug_lock percpu_rw_semaphore is already acquired for read operation by P1 using percpu_down_read().

Now we have P1 in the path of releaseing the cpu_hotplug_lock and P2 is in the process of acquiring cpu_hotplug_lock.

P1 P2
percpu_up_read() path percpu_down_write() path

rcu_sync_enter() //gp_state=GP_PASSED

rcu_sync_is_idle() //returns false down_write(rw_sem)

__percpu_up_read()

[L] task = rcu_dereference(w->task) //NULL

smp_rmb() [S] w->task = current

smp_mb()

[L] readers_active_check() //fails schedule()

[S] __this_cpu_dec(read_count)

Since load of task can result in NULL. This can lead to missed wakeup in rcuwait_wake_up(). Above sequence violated the following constraint in rcuwait_wake_up():

WAIT WAKE
[S] tsk = current [S] cond = true
MB (A) MB (B)
[L] cond [L] tsk

This can happen as smp_rmb() in rcuwait_wake_up() will provide ordering of load before barrier with load and store after barrier for arm64 architecture. Here the requirement is to order store before smp_rmb() with load after the smp_rmb().

Quoted 4 lines

For the usage of rcuwait_wake_up() in __percpu_up_read() full barrier (smp_mb) is required to complete the constraint of rcuwait_wake_up().

I know this is going to sound ridiculous (coming from me or from the Italian that I am), but it looks like we could both work on our English. ;-)

Quoted 7 lines

But the fix seems correct to me:

It might be a good idea to integrate this fix with fixes to the inline comments/annotations: for example, I see that the comment in rcuwait_wake_up() mentions a non-existing rcuwait_trywake();

Ok, I will update the comment in next version of the patch.

Quoted 2 lines

moreover, the memory-barrier annotation "B" is used also for the smp_mb() preceding the __this_cpu_dec() in __percpu_up_read().

In this annotation "B" is corresponding to annotation "A" in rcuwait_wait_event(). So this seems to be correct.

Quoted 25 lines

Andrea

Quoted 21 lines
---
 kernel/exit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/exit.c +1 -1
index f1d74f0..a10820d 100644
@@ -306,7 +306,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,
Signature · 2 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., 
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
Signature · 3 lines
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
#9 lore raw

[tip:locking/core] sched/wait: Fix rcuwait_wake_up() ordering

In reply to Prateek Sood
sched/wait: Fix rcuwait_wake_up() ordering
Commit-ID:  6dc080eeb2ba01973bfff0d79844d7a59e12542e
Gitweb:     https://git.kernel.org/tip/6dc080eeb2ba01973bfff0d79844d7a59e12542e
Author:     Prateek Sood <[email protected]>
AuthorDate: Fri, 30 Nov 2018 20:40:56 +0530
Committer:  Ingo Molnar <[email protected]>
CommitDate: Mon, 21 Jan 2019 11:15:36 +0100

sched/wait: Fix rcuwait_wake_up() ordering

For some peculiar reason rcuwait_wake_up() has the right barrier in the comment, but not in the code.

This mistake has been observed to cause a deadlock in the following situation:

    P1					P2

    percpu_up_read()			percpu_down_write()
      rcu_sync_is_idle() // false
					  rcu_sync_enter()
					  ...
      __percpu_up_read()
[S] ,-  __this_cpu_dec(*sem->read_count)
    |   smp_rmb();
[L] |   task = rcu_dereference(w->task) // NULL
    |
    |				    [S]	    w->task = current
    |					    smp_mb();
    |				    [L]	    readers_active_check() // fail
    `-> <store happens here>

Where the smp_rmb() (obviously) fails to constrain the store.

[ peterz: Added changelog. ]

Cc Linus Torvalds <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Thomas Gleixner <[email protected]>
Fixes 8f95c90ceb54 ("sched/wait, RCU: Introduce rcuwait machinery")
---
 kernel/exit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/exit.c +1 -1
index 284f2fe9a293..3fb7be001964 100644
@@ -307,7 +307,7 @@ void rcuwait_wake_up(struct rcuwait *w)
 	 *        MB (A)	      MB (B)
 	 *    [L] cond		  [L] tsk
 	 */
-	smp_rmb(); /* (B) */
+	smp_mb(); /* (B) */
 
 	/*
 	 * Avoid using task_rcu_dereference() magic as long as we are careful,