xref: /linux-6.15/kernel/freezer.c (revision 03afed8b)
18174f150SMatt Helsley /*
28174f150SMatt Helsley  * kernel/freezer.c - Function to freeze a process
38174f150SMatt Helsley  *
48174f150SMatt Helsley  * Originally from kernel/power/process.c
58174f150SMatt Helsley  */
68174f150SMatt Helsley 
78174f150SMatt Helsley #include <linux/interrupt.h>
88174f150SMatt Helsley #include <linux/suspend.h>
99984de1aSPaul Gortmaker #include <linux/export.h>
108174f150SMatt Helsley #include <linux/syscalls.h>
118174f150SMatt Helsley #include <linux/freezer.h>
128a32c441STejun Heo #include <linux/kthread.h>
138174f150SMatt Helsley 
140c9af092STejun Heo /* protects freezing and frozen transitions */
150c9af092STejun Heo static DEFINE_SPINLOCK(freezer_lock);
168174f150SMatt Helsley 
178174f150SMatt Helsley /* Refrigerator is place where frozen processes are stored :-). */
188a32c441STejun Heo bool __refrigerator(bool check_kthr_stop)
198174f150SMatt Helsley {
208174f150SMatt Helsley 	/* Hmm, should we be allowed to suspend when there are realtime
218174f150SMatt Helsley 	   processes around? */
22a0acae0eSTejun Heo 	bool was_frozen = false;
238174f150SMatt Helsley 	long save;
248174f150SMatt Helsley 
256907483bSTejun Heo 	/*
266907483bSTejun Heo 	 * Enter FROZEN.  If NOFREEZE, schedule immediate thawing by
276907483bSTejun Heo 	 * clearing freezing.
286907483bSTejun Heo 	 */
290c9af092STejun Heo 	spin_lock_irq(&freezer_lock);
306907483bSTejun Heo repeat:
310c9af092STejun Heo 	if (!freezing(current)) {
320c9af092STejun Heo 		spin_unlock_irq(&freezer_lock);
33a0acae0eSTejun Heo 		return was_frozen;
348174f150SMatt Helsley 	}
356907483bSTejun Heo 	if (current->flags & PF_NOFREEZE)
360c9af092STejun Heo 		clear_freeze_flag(current);
376907483bSTejun Heo 	current->flags |= PF_FROZEN;
380c9af092STejun Heo 	spin_unlock_irq(&freezer_lock);
390c9af092STejun Heo 
408174f150SMatt Helsley 	save = current->state;
418174f150SMatt Helsley 	pr_debug("%s entered refrigerator\n", current->comm);
428174f150SMatt Helsley 
438174f150SMatt Helsley 	spin_lock_irq(&current->sighand->siglock);
448174f150SMatt Helsley 	recalc_sigpending(); /* We sent fake signal, clean it up */
458174f150SMatt Helsley 	spin_unlock_irq(&current->sighand->siglock);
468174f150SMatt Helsley 
478174f150SMatt Helsley 	for (;;) {
488174f150SMatt Helsley 		set_current_state(TASK_UNINTERRUPTIBLE);
496907483bSTejun Heo 		if (!freezing(current) ||
508a32c441STejun Heo 		    (check_kthr_stop && kthread_should_stop()))
518174f150SMatt Helsley 			break;
52a0acae0eSTejun Heo 		was_frozen = true;
538174f150SMatt Helsley 		schedule();
548174f150SMatt Helsley 	}
556301cb95SThomas Gleixner 
566907483bSTejun Heo 	/* leave FROZEN */
576907483bSTejun Heo 	spin_lock_irq(&freezer_lock);
586907483bSTejun Heo 	if (freezing(current))
596907483bSTejun Heo 		goto repeat;
606907483bSTejun Heo 	current->flags &= ~PF_FROZEN;
616907483bSTejun Heo 	spin_unlock_irq(&freezer_lock);
626907483bSTejun Heo 
638174f150SMatt Helsley 	pr_debug("%s left refrigerator\n", current->comm);
6450fb4f7fSTejun Heo 
6550fb4f7fSTejun Heo 	/*
6650fb4f7fSTejun Heo 	 * Restore saved task state before returning.  The mb'd version
6750fb4f7fSTejun Heo 	 * needs to be used; otherwise, it might silently break
6850fb4f7fSTejun Heo 	 * synchronization which depends on ordered task state change.
6950fb4f7fSTejun Heo 	 */
7050fb4f7fSTejun Heo 	set_current_state(save);
71a0acae0eSTejun Heo 
72a0acae0eSTejun Heo 	return was_frozen;
738174f150SMatt Helsley }
74a0acae0eSTejun Heo EXPORT_SYMBOL(__refrigerator);
758174f150SMatt Helsley 
768174f150SMatt Helsley static void fake_signal_wake_up(struct task_struct *p)
778174f150SMatt Helsley {
788174f150SMatt Helsley 	unsigned long flags;
798174f150SMatt Helsley 
808174f150SMatt Helsley 	spin_lock_irqsave(&p->sighand->siglock, flags);
81d6cc7685STejun Heo 	signal_wake_up(p, 0);
828174f150SMatt Helsley 	spin_unlock_irqrestore(&p->sighand->siglock, flags);
838174f150SMatt Helsley }
848174f150SMatt Helsley 
858174f150SMatt Helsley /**
868174f150SMatt Helsley  *	freeze_task - send a freeze request to given task
878174f150SMatt Helsley  *	@p: task to send the request to
888174f150SMatt Helsley  *	@sig_only: if set, the request will only be sent if the task has the
898174f150SMatt Helsley  *		PF_FREEZER_NOSIG flag unset
908174f150SMatt Helsley  *	Return value: 'false', if @sig_only is set and the task has
918174f150SMatt Helsley  *		PF_FREEZER_NOSIG set or the task is frozen, 'true', otherwise
928174f150SMatt Helsley  *
938174f150SMatt Helsley  *	The freeze request is sent by setting the tasks's TIF_FREEZE flag and
948174f150SMatt Helsley  *	either sending a fake signal to it or waking it up, depending on whether
958174f150SMatt Helsley  *	or not it has PF_FREEZER_NOSIG set.  If @sig_only is set and the task
968174f150SMatt Helsley  *	has PF_FREEZER_NOSIG set (ie. it is a typical kernel thread), its
978174f150SMatt Helsley  *	TIF_FREEZE flag will not be set.
988174f150SMatt Helsley  */
998174f150SMatt Helsley bool freeze_task(struct task_struct *p, bool sig_only)
1008174f150SMatt Helsley {
1010c9af092STejun Heo 	unsigned long flags;
1020c9af092STejun Heo 	bool ret = false;
1038174f150SMatt Helsley 
1040c9af092STejun Heo 	spin_lock_irqsave(&freezer_lock, flags);
1050c9af092STejun Heo 
10685f1d476STejun Heo 	if ((p->flags & PF_NOFREEZE) ||
10785f1d476STejun Heo 	    (sig_only && !should_send_signal(p)))
1080c9af092STejun Heo 		goto out_unlock;
1090c9af092STejun Heo 
1100c9af092STejun Heo 	if (frozen(p))
1110c9af092STejun Heo 		goto out_unlock;
1120c9af092STejun Heo 
1138174f150SMatt Helsley 	set_freeze_flag(p);
1148174f150SMatt Helsley 
1158174f150SMatt Helsley 	if (should_send_signal(p)) {
1168174f150SMatt Helsley 		fake_signal_wake_up(p);
1178cfe400cSTejun Heo 		/*
1188cfe400cSTejun Heo 		 * fake_signal_wake_up() goes through p's scheduler
1198cfe400cSTejun Heo 		 * lock and guarantees that TASK_STOPPED/TRACED ->
1208cfe400cSTejun Heo 		 * TASK_RUNNING transition can't race with task state
1218cfe400cSTejun Heo 		 * testing in try_to_freeze_tasks().
1228cfe400cSTejun Heo 		 */
1238174f150SMatt Helsley 	} else {
1248174f150SMatt Helsley 		wake_up_state(p, TASK_INTERRUPTIBLE);
1258174f150SMatt Helsley 	}
1260c9af092STejun Heo 	ret = true;
1270c9af092STejun Heo out_unlock:
1280c9af092STejun Heo 	spin_unlock_irqrestore(&freezer_lock, flags);
1290c9af092STejun Heo 	return ret;
1308174f150SMatt Helsley }
1318174f150SMatt Helsley 
132a5be2d0dSTejun Heo void __thaw_task(struct task_struct *p)
133dc52ddc0SMatt Helsley {
1340c9af092STejun Heo 	unsigned long flags;
135a5be2d0dSTejun Heo 
1366907483bSTejun Heo 	/*
1376907483bSTejun Heo 	 * Clear freezing and kick @p if FROZEN.  Clearing is guaranteed to
1386907483bSTejun Heo 	 * be visible to @p as waking up implies wmb.  Waking up inside
1396907483bSTejun Heo 	 * freezer_lock also prevents wakeups from leaking outside
1406907483bSTejun Heo 	 * refrigerator.
141*03afed8bSTejun Heo 	 *
142*03afed8bSTejun Heo 	 * If !FROZEN, @p hasn't reached refrigerator, recalc sigpending to
143*03afed8bSTejun Heo 	 * avoid leaving dangling TIF_SIGPENDING behind.
1446907483bSTejun Heo 	 */
1450c9af092STejun Heo 	spin_lock_irqsave(&freezer_lock, flags);
1460c9af092STejun Heo 	clear_freeze_flag(p);
147*03afed8bSTejun Heo 	if (frozen(p)) {
1486907483bSTejun Heo 		wake_up_process(p);
149*03afed8bSTejun Heo 	} else {
150*03afed8bSTejun Heo 		spin_lock(&p->sighand->siglock);
151*03afed8bSTejun Heo 		recalc_sigpending_and_wake(p);
152*03afed8bSTejun Heo 		spin_unlock(&p->sighand->siglock);
153*03afed8bSTejun Heo 	}
1540c9af092STejun Heo 	spin_unlock_irqrestore(&freezer_lock, flags);
155dc52ddc0SMatt Helsley }
156