linux_schedule.c revision 335423
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 335423 2018-06-20 06:47:49Z 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_PARKED | 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_init_wait_entry(wait_queue_t *wq, int flags)
192{
193
194	memset(wq, 0, sizeof(*wq));
195	wq->flags = flags;
196	wq->private = current;
197	wq->func = autoremove_wake_function;
198	INIT_LIST_HEAD(&wq->task_list);
199}
200
201void
202linux_wake_up(wait_queue_head_t *wqh, unsigned int state, int nr, bool locked)
203{
204	wait_queue_t *pos, *next;
205
206	if (!locked)
207		spin_lock(&wqh->lock);
208	list_for_each_entry_safe(pos, next, &wqh->task_list, task_list) {
209		if (pos->func == NULL) {
210			if (wake_up_task(pos->private, state) != 0 && --nr == 0)
211				break;
212		} else {
213			if (pos->func(pos, state, 0, NULL) != 0 && --nr == 0)
214				break;
215		}
216	}
217	if (!locked)
218		spin_unlock(&wqh->lock);
219}
220
221void
222linux_prepare_to_wait(wait_queue_head_t *wqh, wait_queue_t *wq, int state)
223{
224
225	spin_lock(&wqh->lock);
226	if (list_empty(&wq->task_list))
227		__add_wait_queue(wqh, wq);
228	set_task_state(current, state);
229	spin_unlock(&wqh->lock);
230}
231
232void
233linux_finish_wait(wait_queue_head_t *wqh, wait_queue_t *wq)
234{
235
236	spin_lock(&wqh->lock);
237	set_task_state(current, TASK_RUNNING);
238	if (!list_empty(&wq->task_list)) {
239		__remove_wait_queue(wqh, wq);
240		INIT_LIST_HEAD(&wq->task_list);
241	}
242	spin_unlock(&wqh->lock);
243}
244
245bool
246linux_waitqueue_active(wait_queue_head_t *wqh)
247{
248	bool ret;
249
250	spin_lock(&wqh->lock);
251	ret = !list_empty(&wqh->task_list);
252	spin_unlock(&wqh->lock);
253	return (ret);
254}
255
256int
257linux_wait_event_common(wait_queue_head_t *wqh, wait_queue_t *wq, int timeout,
258    unsigned int state, spinlock_t *lock)
259{
260	struct task_struct *task;
261	int ret;
262
263	if (lock != NULL)
264		spin_unlock_irq(lock);
265
266	/* range check timeout */
267	if (timeout < 1)
268		timeout = 1;
269	else if (timeout == MAX_SCHEDULE_TIMEOUT)
270		timeout = 0;
271
272	task = current;
273
274	/*
275	 * Our wait queue entry is on the stack - make sure it doesn't
276	 * get swapped out while we sleep.
277	 */
278	PHOLD(task->task_thread->td_proc);
279	sleepq_lock(task);
280	if (atomic_read(&task->state) != TASK_WAKING) {
281		ret = linux_add_to_sleepqueue(task, task, "wevent", timeout,
282		    state);
283	} else {
284		sleepq_release(task);
285		ret = 0;
286	}
287	PRELE(task->task_thread->td_proc);
288
289	if (lock != NULL)
290		spin_lock_irq(lock);
291	return (ret);
292}
293
294int
295linux_schedule_timeout(int timeout)
296{
297	struct task_struct *task;
298	int ret;
299	int state;
300	int remainder;
301
302	task = current;
303
304	/* range check timeout */
305	if (timeout < 1)
306		timeout = 1;
307	else if (timeout == MAX_SCHEDULE_TIMEOUT)
308		timeout = 0;
309
310	remainder = ticks + timeout;
311
312	sleepq_lock(task);
313	state = atomic_read(&task->state);
314	if (state != TASK_WAKING) {
315		ret = linux_add_to_sleepqueue(task, task, "sched", timeout,
316		    state);
317	} else {
318		sleepq_release(task);
319		ret = 0;
320	}
321	set_task_state(task, TASK_RUNNING);
322
323	if (timeout == 0)
324		return (MAX_SCHEDULE_TIMEOUT);
325
326	/* range check return value */
327	remainder -= ticks;
328
329	/* range check return value */
330	if (ret == -ERESTARTSYS && remainder < 1)
331		remainder = 1;
332	else if (remainder < 0)
333		remainder = 0;
334	else if (remainder > timeout)
335		remainder = timeout;
336	return (remainder);
337}
338
339static void
340wake_up_sleepers(void *wchan)
341{
342	int wakeup_swapper;
343
344	sleepq_lock(wchan);
345	wakeup_swapper = sleepq_signal(wchan, SLEEPQ_SLEEP, 0, 0);
346	sleepq_release(wchan);
347	if (wakeup_swapper)
348		kick_proc0();
349}
350
351#define	bit_to_wchan(word, bit)	((void *)(((uintptr_t)(word) << 6) | (bit)))
352
353void
354linux_wake_up_bit(void *word, int bit)
355{
356
357	wake_up_sleepers(bit_to_wchan(word, bit));
358}
359
360int
361linux_wait_on_bit_timeout(unsigned long *word, int bit, unsigned int state,
362    int timeout)
363{
364	struct task_struct *task;
365	void *wchan;
366	int ret;
367
368	/* range check timeout */
369	if (timeout < 1)
370		timeout = 1;
371	else if (timeout == MAX_SCHEDULE_TIMEOUT)
372		timeout = 0;
373
374	task = current;
375	wchan = bit_to_wchan(word, bit);
376	for (;;) {
377		sleepq_lock(wchan);
378		if ((*word & (1 << bit)) == 0) {
379			sleepq_release(wchan);
380			ret = 0;
381			break;
382		}
383		set_task_state(task, state);
384		ret = linux_add_to_sleepqueue(wchan, task, "wbit", timeout,
385		    state);
386		if (ret != 0)
387			break;
388	}
389	set_task_state(task, TASK_RUNNING);
390
391	return (ret);
392}
393
394void
395linux_wake_up_atomic_t(atomic_t *a)
396{
397
398	wake_up_sleepers(a);
399}
400
401int
402linux_wait_on_atomic_t(atomic_t *a, unsigned int state)
403{
404	struct task_struct *task;
405	void *wchan;
406	int ret;
407
408	task = current;
409	wchan = a;
410	for (;;) {
411		sleepq_lock(wchan);
412		if (atomic_read(a) == 0) {
413			sleepq_release(wchan);
414			ret = 0;
415			break;
416		}
417		set_task_state(task, state);
418		ret = linux_add_to_sleepqueue(wchan, task, "watomic", 0, state);
419		if (ret != 0)
420			break;
421	}
422	set_task_state(task, TASK_RUNNING);
423
424	return (ret);
425}
426
427bool
428linux_wake_up_state(struct task_struct *task, unsigned int state)
429{
430
431	return (wake_up_task(task, state) != 0);
432}
433