s390/etr,stp: fix possible deadlock on machine check
authorHeiko Carstens <heiko.carstens@de.ibm.com>
Fri, 9 Oct 2015 11:48:03 +0000 (13:48 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Wed, 14 Oct 2015 12:32:18 +0000 (14:32 +0200)
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>
arch/s390/include/asm/etr.h
arch/s390/kernel/nmi.c
arch/s390/kernel/time.c

index f7e5c36..105f90e 100644 (file)
@@ -211,8 +211,9 @@ static inline int etr_ptff(void *ptff_block, unsigned int func)
 #define ETR_PTFF_SGS   0x43    /* set gross steering rate */
 
 /* Functions needed by the machine check handler */
-void etr_switch_to_local(void);
-void etr_sync_check(void);
+int etr_switch_to_local(void);
+int etr_sync_check(void);
+void etr_queue_work(void);
 
 /* notifier for syncs */
 extern struct atomic_notifier_head s390_epoch_delta_notifier;
@@ -253,7 +254,8 @@ struct stp_sstpi {
 } __attribute__ ((packed));
 
 /* Functions needed by the machine check handler */
-void stp_sync_check(void);
-void stp_island_check(void);
+int stp_sync_check(void);
+int stp_island_check(void);
+void stp_queue_work(void);
 
 #endif /* __S390_ETR_H */
index 0ae6f8e..4082885 100644 (file)
@@ -28,6 +28,8 @@ struct mcck_struct {
        int kill_task;
        int channel_report;
        int warning;
+       unsigned int etr_queue : 1;
+       unsigned int stp_queue : 1;
        unsigned long long mcck_code;
 };
 
@@ -81,6 +83,10 @@ void s390_handle_mcck(void)
                if (xchg(&mchchk_wng_posted, 1) == 0)
                        kill_cad_pid(SIGPWR, 1);
        }
+       if (mcck.etr_queue)
+               etr_queue_work();
+       if (mcck.stp_queue)
+               stp_queue_work();
        if (mcck.kill_task) {
                local_irq_enable();
                printk(KERN_EMERG "mcck: Terminating task because of machine "
@@ -323,13 +329,15 @@ void notrace s390_do_machine_check(struct pt_regs *regs)
        if (mci->ed && mci->ec) {
                /* External damage */
                if (S390_lowcore.external_damage_code & (1U << ED_ETR_SYNC))
-                       etr_sync_check();
+                       mcck->etr_queue |= etr_sync_check();
                if (S390_lowcore.external_damage_code & (1U << ED_ETR_SWITCH))
-                       etr_switch_to_local();
+                       mcck->etr_queue |= etr_switch_to_local();
                if (S390_lowcore.external_damage_code & (1U << ED_STP_SYNC))
-                       stp_sync_check();
+                       mcck->stp_queue |= stp_sync_check();
                if (S390_lowcore.external_damage_code & (1U << ED_STP_ISLAND))
-                       stp_island_check();
+                       mcck->stp_queue |= stp_island_check();
+               if (mcck->etr_queue || mcck->stp_queue)
+                       set_cpu_flag(CIF_MCCK_PENDING);
        }
        if (mci->se)
                /* Storage error uncorrected */
index 017c3a9..99f84ac 100644 (file)
@@ -542,16 +542,17 @@ arch_initcall(etr_init);
  * Switch to local machine check. This is called when the last usable
  * ETR port goes inactive. After switch to local the clock is not in sync.
  */
-void etr_switch_to_local(void)
+int etr_switch_to_local(void)
 {
        if (!etr_eacr.sl)
-               return;
+               return 0;
        disable_sync_clock(NULL);
        if (!test_and_set_bit(ETR_EVENT_SWITCH_LOCAL, &etr_events)) {
                etr_eacr.es = etr_eacr.sl = 0;
                etr_setr(&etr_eacr);
-               queue_work(time_sync_wq, &etr_work);
+               return 1;
        }
+       return 0;
 }
 
 /*
@@ -560,16 +561,22 @@ void etr_switch_to_local(void)
  * After a ETR sync check the clock is not in sync. The machine check
  * is broadcasted to all cpus at the same time.
  */
-void etr_sync_check(void)
+int etr_sync_check(void)
 {
        if (!etr_eacr.es)
-               return;
+               return 0;
        disable_sync_clock(NULL);
        if (!test_and_set_bit(ETR_EVENT_SYNC_CHECK, &etr_events)) {
                etr_eacr.es = 0;
                etr_setr(&etr_eacr);
-               queue_work(time_sync_wq, &etr_work);
+               return 1;
        }
+       return 0;
+}
+
+void etr_queue_work(void)
+{
+       queue_work(time_sync_wq, &etr_work);
 }
 
 /*
@@ -1504,10 +1511,10 @@ static void stp_timing_alert(struct stp_irq_parm *intparm)
  * After a STP sync check the clock is not in sync. The machine check
  * is broadcasted to all cpus at the same time.
  */
-void stp_sync_check(void)
+int stp_sync_check(void)
 {
        disable_sync_clock(NULL);
-       queue_work(time_sync_wq, &stp_work);
+       return 1;
 }
 
 /*
@@ -1516,12 +1523,16 @@ void stp_sync_check(void)
  * have matching CTN ids and have a valid stratum-1 configuration
  * but the configurations do not match.
  */
-void stp_island_check(void)
+int stp_island_check(void)
 {
        disable_sync_clock(NULL);
-       queue_work(time_sync_wq, &stp_work);
+       return 1;
 }
 
+void stp_queue_work(void)
+{
+       queue_work(time_sync_wq, &stp_work);
+}
 
 static int stp_sync_clock(void *data)
 {