Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

s390/etr,stp: fix possible deadlock on machine check

The first level machine check handler for etr and stp machine checks may
call queue_work() while in nmi context. This may deadlock e.g. if the
machine check happened when the interrupted context did hold a lock, that
also will be acquired by queue_work().
Therefore split etr and stp machine check handling into first and second
level handling. The second level handling will then issue the queue_work()
call in process context which avoids the potential deadlock.

Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>

authored by

Heiko Carstens and committed by
Martin Schwidefsky
29b0a825 7cc8944e

+39 -18
+6 -4
arch/s390/include/asm/etr.h
··· 211 211 #define ETR_PTFF_SGS 0x43 /* set gross steering rate */ 212 212 213 213 /* Functions needed by the machine check handler */ 214 - void etr_switch_to_local(void); 215 - void etr_sync_check(void); 214 + int etr_switch_to_local(void); 215 + int etr_sync_check(void); 216 + void etr_queue_work(void); 216 217 217 218 /* notifier for syncs */ 218 219 extern struct atomic_notifier_head s390_epoch_delta_notifier; ··· 254 253 } __attribute__ ((packed)); 255 254 256 255 /* Functions needed by the machine check handler */ 257 - void stp_sync_check(void); 258 - void stp_island_check(void); 256 + int stp_sync_check(void); 257 + int stp_island_check(void); 258 + void stp_queue_work(void); 259 259 260 260 #endif /* __S390_ETR_H */
+12 -4
arch/s390/kernel/nmi.c
··· 28 28 int kill_task; 29 29 int channel_report; 30 30 int warning; 31 + unsigned int etr_queue : 1; 32 + unsigned int stp_queue : 1; 31 33 unsigned long long mcck_code; 32 34 }; 33 35 ··· 83 81 if (xchg(&mchchk_wng_posted, 1) == 0) 84 82 kill_cad_pid(SIGPWR, 1); 85 83 } 84 + if (mcck.etr_queue) 85 + etr_queue_work(); 86 + if (mcck.stp_queue) 87 + stp_queue_work(); 86 88 if (mcck.kill_task) { 87 89 local_irq_enable(); 88 90 printk(KERN_EMERG "mcck: Terminating task because of machine " ··· 329 323 if (mci->ed && mci->ec) { 330 324 /* External damage */ 331 325 if (S390_lowcore.external_damage_code & (1U << ED_ETR_SYNC)) 332 - etr_sync_check(); 326 + mcck->etr_queue |= etr_sync_check(); 333 327 if (S390_lowcore.external_damage_code & (1U << ED_ETR_SWITCH)) 334 - etr_switch_to_local(); 328 + mcck->etr_queue |= etr_switch_to_local(); 335 329 if (S390_lowcore.external_damage_code & (1U << ED_STP_SYNC)) 336 - stp_sync_check(); 330 + mcck->stp_queue |= stp_sync_check(); 337 331 if (S390_lowcore.external_damage_code & (1U << ED_STP_ISLAND)) 338 - stp_island_check(); 332 + mcck->stp_queue |= stp_island_check(); 333 + if (mcck->etr_queue || mcck->stp_queue) 334 + set_cpu_flag(CIF_MCCK_PENDING); 339 335 } 340 336 if (mci->se) 341 337 /* Storage error uncorrected */
+21 -10
arch/s390/kernel/time.c
··· 542 542 * Switch to local machine check. This is called when the last usable 543 543 * ETR port goes inactive. After switch to local the clock is not in sync. 544 544 */ 545 - void etr_switch_to_local(void) 545 + int etr_switch_to_local(void) 546 546 { 547 547 if (!etr_eacr.sl) 548 - return; 548 + return 0; 549 549 disable_sync_clock(NULL); 550 550 if (!test_and_set_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events)) { 551 551 etr_eacr.es = etr_eacr.sl = 0; 552 552 etr_setr(&etr_eacr); 553 - queue_work(time_sync_wq, &etr_work); 553 + return 1; 554 554 } 555 + return 0; 555 556 } 556 557 557 558 /* ··· 561 560 * After a ETR sync check the clock is not in sync. The machine check 562 561 * is broadcasted to all cpus at the same time. 563 562 */ 564 - void etr_sync_check(void) 563 + int etr_sync_check(void) 565 564 { 566 565 if (!etr_eacr.es) 567 - return; 566 + return 0; 568 567 disable_sync_clock(NULL); 569 568 if (!test_and_set_bit(ETR_EVENT_SYNC_CHECK, &etr_events)) { 570 569 etr_eacr.es = 0; 571 570 etr_setr(&etr_eacr); 572 - queue_work(time_sync_wq, &etr_work); 571 + return 1; 573 572 } 573 + return 0; 574 + } 575 + 576 + void etr_queue_work(void) 577 + { 578 + queue_work(time_sync_wq, &etr_work); 574 579 } 575 580 576 581 /* ··· 1511 1504 * After a STP sync check the clock is not in sync. The machine check 1512 1505 * is broadcasted to all cpus at the same time. 1513 1506 */ 1514 - void stp_sync_check(void) 1507 + int stp_sync_check(void) 1515 1508 { 1516 1509 disable_sync_clock(NULL); 1517 - queue_work(time_sync_wq, &stp_work); 1510 + return 1; 1518 1511 } 1519 1512 1520 1513 /* ··· 1523 1516 * have matching CTN ids and have a valid stratum-1 configuration 1524 1517 * but the configurations do not match. 1525 1518 */ 1526 - void stp_island_check(void) 1519 + int stp_island_check(void) 1527 1520 { 1528 1521 disable_sync_clock(NULL); 1529 - queue_work(time_sync_wq, &stp_work); 1522 + return 1; 1530 1523 } 1531 1524 1525 + void stp_queue_work(void) 1526 + { 1527 + queue_work(time_sync_wq, &stp_work); 1528 + } 1532 1529 1533 1530 static int stp_sync_clock(void *data) 1534 1531 {