freezer,sched: Rewrite core freezer logic
[sfrench/cifs-2.6.git] / kernel / freezer.c
index 45ab36ffd0e79cf84fe4a47323811e27ae11357d..4fad0e6fca6447d72388e7ea44e90f34924beb03 100644 (file)
 #include <linux/kthread.h>
 
 /* total number of freezing conditions in effect */
-atomic_t system_freezing_cnt = ATOMIC_INIT(0);
-EXPORT_SYMBOL(system_freezing_cnt);
+DEFINE_STATIC_KEY_FALSE(freezer_active);
+EXPORT_SYMBOL(freezer_active);
 
-/* indicate whether PM freezing is in effect, protected by
+/*
+ * indicate whether PM freezing is in effect, protected by
  * system_transition_mutex
  */
 bool pm_freezing;
@@ -29,7 +30,7 @@ static DEFINE_SPINLOCK(freezer_lock);
  * freezing_slow_path - slow path for testing whether a task needs to be frozen
  * @p: task to be tested
  *
- * This function is called by freezing() if system_freezing_cnt isn't zero
+ * This function is called by freezing() if freezer_active isn't zero
  * and tests whether @p needs to enter and stay in frozen state.  Can be
  * called under any context.  The freezers are responsible for ensuring the
  * target tasks see the updated state.
@@ -52,41 +53,40 @@ bool freezing_slow_path(struct task_struct *p)
 }
 EXPORT_SYMBOL(freezing_slow_path);
 
+bool frozen(struct task_struct *p)
+{
+       return READ_ONCE(p->__state) & TASK_FROZEN;
+}
+
 /* Refrigerator is place where frozen processes are stored :-). */
 bool __refrigerator(bool check_kthr_stop)
 {
-       /* Hmm, should we be allowed to suspend when there are realtime
-          processes around? */
+       unsigned int state = get_current_state();
        bool was_frozen = false;
-       unsigned int save = get_current_state();
 
        pr_debug("%s entered refrigerator\n", current->comm);
 
+       WARN_ON_ONCE(state && !(state & TASK_NORMAL));
+
        for (;;) {
-               set_current_state(TASK_UNINTERRUPTIBLE);
+               bool freeze;
+
+               set_current_state(TASK_FROZEN);
 
                spin_lock_irq(&freezer_lock);
-               current->flags |= PF_FROZEN;
-               if (!freezing(current) ||
-                   (check_kthr_stop && kthread_should_stop()))
-                       current->flags &= ~PF_FROZEN;
+               freeze = freezing(current) && !(check_kthr_stop && kthread_should_stop());
                spin_unlock_irq(&freezer_lock);
 
-               if (!(current->flags & PF_FROZEN))
+               if (!freeze)
                        break;
+
                was_frozen = true;
                schedule();
        }
+       __set_current_state(TASK_RUNNING);
 
        pr_debug("%s left refrigerator\n", current->comm);
 
-       /*
-        * Restore saved task state before returning.  The mb'd version
-        * needs to be used; otherwise, it might silently break
-        * synchronization which depends on ordered task state change.
-        */
-       set_current_state(save);
-
        return was_frozen;
 }
 EXPORT_SYMBOL(__refrigerator);
@@ -101,6 +101,44 @@ static void fake_signal_wake_up(struct task_struct *p)
        }
 }
 
+static int __set_task_frozen(struct task_struct *p, void *arg)
+{
+       unsigned int state = READ_ONCE(p->__state);
+
+       if (p->on_rq)
+               return 0;
+
+       if (p != current && task_curr(p))
+               return 0;
+
+       if (!(state & (TASK_FREEZABLE | __TASK_STOPPED | __TASK_TRACED)))
+               return 0;
+
+       /*
+        * Only TASK_NORMAL can be augmented with TASK_FREEZABLE, since they
+        * can suffer spurious wakeups.
+        */
+       if (state & TASK_FREEZABLE)
+               WARN_ON_ONCE(!(state & TASK_NORMAL));
+
+#ifdef CONFIG_LOCKDEP
+       /*
+        * It's dangerous to freeze with locks held; there be dragons there.
+        */
+       if (!(state & __TASK_FREEZABLE_UNSAFE))
+               WARN_ON_ONCE(debug_locks && p->lockdep_depth);
+#endif
+
+       WRITE_ONCE(p->__state, TASK_FROZEN);
+       return TASK_FROZEN;
+}
+
+static bool __freeze_task(struct task_struct *p)
+{
+       /* TASK_FREEZABLE|TASK_STOPPED|TASK_TRACED -> TASK_FROZEN */
+       return task_call_func(p, __set_task_frozen, NULL);
+}
+
 /**
  * freeze_task - send a freeze request to given task
  * @p: task to send the request to
@@ -116,20 +154,8 @@ bool freeze_task(struct task_struct *p)
 {
        unsigned long flags;
 
-       /*
-        * This check can race with freezer_do_not_count, but worst case that
-        * will result in an extra wakeup being sent to the task.  It does not
-        * race with freezer_count(), the barriers in freezer_count() and
-        * freezer_should_skip() ensure that either freezer_count() sees
-        * freezing == true in try_to_freeze() and freezes, or
-        * freezer_should_skip() sees !PF_FREEZE_SKIP and freezes the task
-        * normally.
-        */
-       if (freezer_should_skip(p))
-               return false;
-
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || __freeze_task(p)) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
@@ -137,19 +163,52 @@ bool freeze_task(struct task_struct *p)
        if (!(p->flags & PF_KTHREAD))
                fake_signal_wake_up(p);
        else
-               wake_up_state(p, TASK_INTERRUPTIBLE);
+               wake_up_state(p, TASK_NORMAL);
 
        spin_unlock_irqrestore(&freezer_lock, flags);
        return true;
 }
 
+/*
+ * The special task states (TASK_STOPPED, TASK_TRACED) keep their canonical
+ * state in p->jobctl. If either of them got a wakeup that was missed because
+ * TASK_FROZEN, then their canonical state reflects that and the below will
+ * refuse to restore the special state and instead issue the wakeup.
+ */
+static int __set_task_special(struct task_struct *p, void *arg)
+{
+       unsigned int state = 0;
+
+       if (p->jobctl & JOBCTL_TRACED)
+               state = TASK_TRACED;
+
+       else if (p->jobctl & JOBCTL_STOPPED)
+               state = TASK_STOPPED;
+
+       if (state)
+               WRITE_ONCE(p->__state, state);
+
+       return state;
+}
+
 void __thaw_task(struct task_struct *p)
 {
-       unsigned long flags;
+       unsigned long flags, flags2;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (frozen(p))
-               wake_up_process(p);
+       if (WARN_ON_ONCE(freezing(p)))
+               goto unlock;
+
+       if (lock_task_sighand(p, &flags2)) {
+               /* TASK_FROZEN -> TASK_{STOPPED,TRACED} */
+               bool ret = task_call_func(p, __set_task_special, NULL);
+               unlock_task_sighand(p, &flags2);
+               if (ret)
+                       goto unlock;
+       }
+
+       wake_up_state(p, TASK_FROZEN);
+unlock:
        spin_unlock_irqrestore(&freezer_lock, flags);
 }