linux_schedule.c revision 331996
1/*-
2 * Copyright (c) 2017 Mark Johnston <markj@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conds
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conds, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conds and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: stable/11/sys/compat/linuxkpi/common/src/linux_schedule.c 331996 2018-04-04 08:41:10Z hselasky $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/proc.h>
33#include <sys/signalvar.h>
34#include <sys/sleepqueue.h>
35
36#include <linux/delay.h>
37#include <linux/errno.h>
38#include <linux/kernel.h>
39#include <linux/list.h>
40#include <linux/sched.h>
41#include <linux/spinlock.h>
42#include <linux/wait.h>
43
44static int
45linux_add_to_sleepqueue(void *wchan, struct task_struct *task,
46    const char *wmesg, int timeout, int state)
47{
48	int flags, ret;
49
50	MPASS((state & ~TASK_NORMAL) == 0);
51
52	flags = SLEEPQ_SLEEP | ((state & TASK_INTERRUPTIBLE) != 0 ?
53	    SLEEPQ_INTERRUPTIBLE : 0);
54
55	sleepq_add(wchan, NULL, wmesg, flags, 0);
56	if (timeout != 0)
57		sleepq_set_timeout(wchan, timeout);
58
59	DROP_GIANT();
60	if ((state & TASK_INTERRUPTIBLE) != 0) {
61		if (timeout == 0)
62			ret = -sleepq_wait_sig(wchan, 0);
63		else
64			ret = -sleepq_timedwait_sig(wchan, 0);
65	} else {
66		if (timeout == 0) {
67			sleepq_wait(wchan, 0);
68			ret = 0;
69		} else
70			ret = -sleepq_timedwait(wchan, 0);
71	}
72	PICKUP_GIANT();
73
74	/* filter return value */
75	if (ret != 0 && ret != -EWOULDBLOCK) {
76		linux_schedule_save_interrupt_value(task, ret);
77		ret = -ERESTARTSYS;
78	}
79	return (ret);
80}
81
82unsigned int
83linux_msleep_interruptible(unsigned int ms)
84{
85	int ret;
86
87	/* guard against invalid values */
88	if (ms == 0)
89		ms = 1;
90	ret = -pause_sbt("lnxsleep", mstosbt(ms), 0, C_HARDCLOCK | C_CATCH);
91
92	switch (ret) {
93	case -EWOULDBLOCK:
94		return (0);
95	default:
96		linux_schedule_save_interrupt_value(current, ret);
97		return (ms);
98	}
99}
100
101static int
102wake_up_task(struct task_struct *task, unsigned int state)
103{
104	int ret, wakeup_swapper;
105
106	ret = wakeup_swapper = 0;
107	sleepq_lock(task);
108	if ((atomic_read(&task->state) & state) != 0) {
109		set_task_state(task, TASK_WAKING);
110		wakeup_swapper = sleepq_signal(task, SLEEPQ_SLEEP, 0, 0);
111		ret = 1;
112	}
113	sleepq_release(task);
114	if (wakeup_swapper)
115		kick_proc0();
116	return (ret);
117}
118
119bool
120linux_signal_pending(struct task_struct *task)
121{
122	struct thread *td;
123	sigset_t pending;
124
125	td = task->task_thread;
126	PROC_LOCK(td->td_proc);
127	pending = td->td_siglist;
128	SIGSETOR(pending, td->td_proc->p_siglist);
129	SIGSETNAND(pending, td->td_sigmask);
130	PROC_UNLOCK(td->td_proc);
131	return (!SIGISEMPTY(pending));
132}
133
134bool
135linux_fatal_signal_pending(struct task_struct *task)
136{
137	struct thread *td;
138	bool ret;
139
140	td = task->task_thread;
141	PROC_LOCK(td->td_proc);
142	ret = SIGISMEMBER(td->td_siglist, SIGKILL) ||
143	    SIGISMEMBER(td->td_proc->p_siglist, SIGKILL);
144	PROC_UNLOCK(td->td_proc);
145	return (ret);
146}
147
148bool
149linux_signal_pending_state(long state, struct task_struct *task)
150{
151
152	MPASS((state & ~TASK_NORMAL) == 0);
153
154	if ((state & TASK_INTERRUPTIBLE) == 0)
155		return (false);
156	return (linux_signal_pending(task));
157}
158
159void
160linux_send_sig(int signo, struct task_struct *task)
161{
162	struct thread *td;
163
164	td = task->task_thread;
165	PROC_LOCK(td->td_proc);
166	tdsignal(td, signo);
167	PROC_UNLOCK(td->td_proc);
168}
169
170int
171autoremove_wake_function(wait_queue_t *wq, unsigned int state, int flags,
172    void *key __unused)
173{
174	struct task_struct *task;
175	int ret;
176
177	task = wq->private;
178	if ((ret = wake_up_task(task, state)) != 0)
179		list_del_init(&wq->task_list);
180	return (ret);
181}
182
183int
184default_wake_function(wait_queue_t *wq, unsigned int state, int flags,
185    void *key __unused)
186{
187	return (wake_up_task(wq->private, state));
188}
189
190void
191linux_wake_up(wait_queue_head_t *wqh, unsigned int state, int nr, bool locked)
192{
193	wait_queue_t *pos, *next;
194
195	if (!locked)
196		spin_lock(&wqh->lock);
197	list_for_each_entry_safe(pos, next, &wqh->task_list, task_list) {
198		if (pos->func == NULL) {
199			if (wake_up_task(pos->private, state) != 0 && --nr == 0)
200				break;
201		} else {
202			if (pos->func(pos, state, 0, NULL) != 0 && --nr == 0)
203				break;
204		}
205	}
206	if (!locked)
207		spin_unlock(&wqh->lock);
208}
209
210void
211linux_prepare_to_wait(wait_queue_head_t *wqh, wait_queue_t *wq, int state)
212{
213
214	spin_lock(&wqh->lock);
215	if (list_empty(&wq->task_list))
216		__add_wait_queue(wqh, wq);
217	set_task_state(current, state);
218	spin_unlock(&wqh->lock);
219}
220
221void
222linux_finish_wait(wait_queue_head_t *wqh, wait_queue_t *wq)
223{
224
225	spin_lock(&wqh->lock);
226	set_task_state(current, TASK_RUNNING);
227	if (!list_empty(&wq->task_list)) {
228		__remove_wait_queue(wqh, wq);
229		INIT_LIST_HEAD(&wq->task_list);
230	}
231	spin_unlock(&wqh->lock);
232}
233
234bool
235linux_waitqueue_active(wait_queue_head_t *wqh)
236{
237	bool ret;
238
239	spin_lock(&wqh->lock);
240	ret = !list_empty(&wqh->task_list);
241	spin_unlock(&wqh->lock);
242	return (ret);
243}
244
245int
246linux_wait_event_common(wait_queue_head_t *wqh, wait_queue_t *wq, int timeout,
247    unsigned int state, spinlock_t *lock)
248{
249	struct task_struct *task;
250	int ret;
251
252	if (lock != NULL)
253		spin_unlock_irq(lock);
254
255	/* range check timeout */
256	if (timeout < 1)
257		timeout = 1;
258	else if (timeout == MAX_SCHEDULE_TIMEOUT)
259		timeout = 0;
260
261	task = current;
262
263	/*
264	 * Our wait queue entry is on the stack - make sure it doesn't
265	 * get swapped out while we sleep.
266	 */
267	PHOLD(task->task_thread->td_proc);
268	sleepq_lock(task);
269	if (atomic_read(&task->state) != TASK_WAKING) {
270		ret = linux_add_to_sleepqueue(task, task, "wevent", timeout, state);
271	} else {
272		sleepq_release(task);
273		ret = 0;
274	}
275	PRELE(task->task_thread->td_proc);
276
277	if (lock != NULL)
278		spin_lock_irq(lock);
279	return (ret);
280}
281
282int
283linux_schedule_timeout(int timeout)
284{
285	struct task_struct *task;
286	int ret;
287	int state;
288	int remainder;
289
290	task = current;
291
292	/* range check timeout */
293	if (timeout < 1)
294		timeout = 1;
295	else if (timeout == MAX_SCHEDULE_TIMEOUT)
296		timeout = 0;
297
298	remainder = ticks + timeout;
299
300	sleepq_lock(task);
301	state = atomic_read(&task->state);
302	if (state != TASK_WAKING) {
303		ret = linux_add_to_sleepqueue(task, task, "sched", timeout, state);
304	} else {
305		sleepq_release(task);
306		ret = 0;
307	}
308	set_task_state(task, TASK_RUNNING);
309
310	if (timeout == 0)
311		return (MAX_SCHEDULE_TIMEOUT);
312
313	/* range check return value */
314	remainder -= ticks;
315
316	/* range check return value */
317	if (ret == -ERESTARTSYS && remainder < 1)
318		remainder = 1;
319	else if (remainder < 0)
320		remainder = 0;
321	else if (remainder > timeout)
322		remainder = timeout;
323	return (remainder);
324}
325
326static void
327wake_up_sleepers(void *wchan)
328{
329	int wakeup_swapper;
330
331	sleepq_lock(wchan);
332	wakeup_swapper = sleepq_signal(wchan, SLEEPQ_SLEEP, 0, 0);
333	sleepq_release(wchan);
334	if (wakeup_swapper)
335		kick_proc0();
336}
337
338#define	bit_to_wchan(word, bit)	((void *)(((uintptr_t)(word) << 6) | (bit)))
339
340void
341linux_wake_up_bit(void *word, int bit)
342{
343
344	wake_up_sleepers(bit_to_wchan(word, bit));
345}
346
347int
348linux_wait_on_bit_timeout(unsigned long *word, int bit, unsigned int state,
349    int timeout)
350{
351	struct task_struct *task;
352	void *wchan;
353	int ret;
354
355	/* range check timeout */
356	if (timeout < 1)
357		timeout = 1;
358	else if (timeout == MAX_SCHEDULE_TIMEOUT)
359		timeout = 0;
360
361	task = current;
362	wchan = bit_to_wchan(word, bit);
363	for (;;) {
364		sleepq_lock(wchan);
365		if ((*word & (1 << bit)) == 0) {
366			sleepq_release(wchan);
367			ret = 0;
368			break;
369		}
370		set_task_state(task, state);
371		ret = linux_add_to_sleepqueue(wchan, task, "wbit", timeout, state);
372		if (ret != 0)
373			break;
374	}
375	set_task_state(task, TASK_RUNNING);
376
377	return (ret);
378}
379
380void
381linux_wake_up_atomic_t(atomic_t *a)
382{
383
384	wake_up_sleepers(a);
385}
386
387int
388linux_wait_on_atomic_t(atomic_t *a, unsigned int state)
389{
390	struct task_struct *task;
391	void *wchan;
392	int ret;
393
394	task = current;
395	wchan = a;
396	for (;;) {
397		sleepq_lock(wchan);
398		if (atomic_read(a) == 0) {
399			sleepq_release(wchan);
400			ret = 0;
401			break;
402		}
403		set_task_state(task, state);
404		ret = linux_add_to_sleepqueue(wchan, task, "watomic", 0, state);
405		if (ret != 0)
406			break;
407	}
408	set_task_state(task, TASK_RUNNING);
409
410	return (ret);
411}
412
413bool
414linux_wake_up_state(struct task_struct *task, unsigned int state)
415{
416
417	return (wake_up_task(task, state) != 0);
418}
419