diff options
author | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2010-07-27 13:29:38 -0400 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2010-07-27 13:29:42 -0400 |
commit | 33fea794b9deeb8ffb77e284eb37375b8f45a2c4 (patch) | |
tree | 4a9a1cbad368c1f6c2c79e334c68f8ca04721b98 | |
parent | f5cdac274c62ab61874374abb60f2310ab979295 (diff) |
[S390] etr: fix clock synchronization race
The etr events switch-to-local and sync-check disable the synchronous clock
and schedule a work queue that tries to get the clock back into sync.
If another switch-to-local or sync-check event occurs while the work queue
function etr_work_fn still runs the eacr.es bit and the clock_sync_word can
become inconsistent because check_sync_clock only uses the clock_sync_word
to determine if the clock is in sync or not. The second pass of the
etr_work_fn will reset the eacr.es bit but will leave the clock_sync_word
intact. Fix this race by moving the reset of the eacr.es bit into the
switch-to-local and sync-check functions and by checking the eacr.es bit
as well to decide if the clock needs to be synced.
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
-rw-r--r-- | arch/s390/kernel/time.c | 18 |
1 files changed, 12 insertions, 6 deletions
diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c index a2163c95eb98..15a7536452d5 100644 --- a/arch/s390/kernel/time.c +++ b/arch/s390/kernel/time.c | |||
@@ -524,8 +524,11 @@ void etr_switch_to_local(void) | |||
524 | if (!etr_eacr.sl) | 524 | if (!etr_eacr.sl) |
525 | return; | 525 | return; |
526 | disable_sync_clock(NULL); | 526 | disable_sync_clock(NULL); |
527 | set_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events); | 527 | if (!test_and_set_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events)) { |
528 | queue_work(time_sync_wq, &etr_work); | 528 | etr_eacr.es = etr_eacr.sl = 0; |
529 | etr_setr(&etr_eacr); | ||
530 | queue_work(time_sync_wq, &etr_work); | ||
531 | } | ||
529 | } | 532 | } |
530 | 533 | ||
531 | /* | 534 | /* |
@@ -539,8 +542,11 @@ void etr_sync_check(void) | |||
539 | if (!etr_eacr.es) | 542 | if (!etr_eacr.es) |
540 | return; | 543 | return; |
541 | disable_sync_clock(NULL); | 544 | disable_sync_clock(NULL); |
542 | set_bit(ETR_EVENT_SYNC_CHECK, &etr_events); | 545 | if (!test_and_set_bit(ETR_EVENT_SYNC_CHECK, &etr_events)) { |
543 | queue_work(time_sync_wq, &etr_work); | 546 | etr_eacr.es = 0; |
547 | etr_setr(&etr_eacr); | ||
548 | queue_work(time_sync_wq, &etr_work); | ||
549 | } | ||
544 | } | 550 | } |
545 | 551 | ||
546 | /* | 552 | /* |
@@ -902,7 +908,7 @@ static struct etr_eacr etr_handle_update(struct etr_aib *aib, | |||
902 | * Do not try to get the alternate port aib if the clock | 908 | * Do not try to get the alternate port aib if the clock |
903 | * is not in sync yet. | 909 | * is not in sync yet. |
904 | */ | 910 | */ |
905 | if (!check_sync_clock()) | 911 | if (!eacr.es || !check_sync_clock()) |
906 | return eacr; | 912 | return eacr; |
907 | 913 | ||
908 | /* | 914 | /* |
@@ -1064,7 +1070,7 @@ static void etr_work_fn(struct work_struct *work) | |||
1064 | * If the clock is in sync just update the eacr and return. | 1070 | * If the clock is in sync just update the eacr and return. |
1065 | * If there is no valid sync port wait for a port update. | 1071 | * If there is no valid sync port wait for a port update. |
1066 | */ | 1072 | */ |
1067 | if (check_sync_clock() || sync_port < 0) { | 1073 | if ((eacr.es && check_sync_clock()) || sync_port < 0) { |
1068 | etr_update_eacr(eacr); | 1074 | etr_update_eacr(eacr); |
1069 | etr_set_tolec_timeout(now); | 1075 | etr_set_tolec_timeout(now); |
1070 | goto out_unlock; | 1076 | goto out_unlock; |