1/*
2 * Copyright 2010, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2018-2023, Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT license.
5 */
6
7
8#include <lock.h>
9#include <thread.h>
10
11extern "C" {
12#	include "device.h"
13#	include <sys/callout.h>
14#	include <sys/mutex.h>
15}
16
17#include <util/AutoLock.h>
18
19
20//#define TRACE_CALLOUT
21#ifdef TRACE_CALLOUT
22#	define TRACE(x...) dprintf(x)
23#else
24#	define TRACE(x...) ;
25#endif
26
27
28static struct list sTimers;
29static mutex sLock;
30static sem_id sWaitSem;
31static callout* sCurrentCallout;
32static thread_id sThread;
33static bigtime_t sTimeout;
34
35
36static void
37invoke_callout(callout *c, struct mtx *c_mtx)
38{
39	if (c_mtx != NULL) {
40		mtx_lock(c_mtx);
41
42		if (c->c_due < 0 || c->c_due > 0) {
43			mtx_unlock(c_mtx);
44			return;
45		}
46		c->c_due = -1;
47	}
48
49	c->c_func(c->c_arg);
50
51	if (c_mtx != NULL && (c->c_flags & CALLOUT_RETURNUNLOCKED) == 0)
52		mtx_unlock(c_mtx);
53}
54
55
56static status_t
57callout_thread(void* /*data*/)
58{
59	status_t status = B_NO_INIT;
60
61	do {
62		bigtime_t timeout = B_INFINITE_TIMEOUT;
63
64		if (status == B_TIMED_OUT || status == B_OK) {
65			// scan timers for new timeout and/or execute a timer
66			if ((status = mutex_lock(&sLock)) != B_OK)
67				continue;
68
69			struct callout* c = NULL;
70			while (true) {
71				c = (callout*)list_get_next_item(&sTimers, c);
72				if (c == NULL)
73					break;
74
75				if (c->c_due > system_time()) {
76					// calculate new timeout
77					if (timeout > c->c_due)
78						timeout = c->c_due;
79					continue;
80				}
81
82				// execute timer
83				list_remove_item(&sTimers, c);
84				struct mtx *c_mtx = c->c_mtx;
85				c->c_due = 0;
86				sCurrentCallout = c;
87
88				mutex_unlock(&sLock);
89
90				invoke_callout(c, c_mtx);
91
92				if ((status = mutex_lock(&sLock)) != B_OK)
93					break;
94
95				sCurrentCallout = NULL;
96				c = NULL;
97					// restart scanning as we unlocked the list
98			}
99
100			sTimeout = timeout;
101			mutex_unlock(&sLock);
102		}
103
104		status = acquire_sem_etc(sWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
105			// the wait sem normally can't be acquired, so we
106			// have to look at the status value the call returns:
107			//
108			// B_OK - a new timer has been added or canceled
109			// B_TIMED_OUT - look for timers to be executed
110			// B_BAD_SEM_ID - we are asked to quit
111	} while (status != B_BAD_SEM_ID);
112
113	return B_OK;
114}
115
116
117// #pragma mark - private API
118
119
120status_t
121init_callout(void)
122{
123	list_init_etc(&sTimers, offsetof(struct callout, c_link));
124	sTimeout = B_INFINITE_TIMEOUT;
125
126	status_t status = B_OK;
127	mutex_init(&sLock, "fbsd callout");
128
129	sWaitSem = create_sem(0, "fbsd callout wait");
130	if (sWaitSem < 0) {
131		status = sWaitSem;
132		goto err1;
133	}
134
135	sThread = spawn_kernel_thread(callout_thread, "fbsd callout",
136		B_DISPLAY_PRIORITY, NULL);
137	if (sThread < 0) {
138		status = sThread;
139		goto err2;
140	}
141
142	return resume_thread(sThread);
143
144err2:
145	delete_sem(sWaitSem);
146err1:
147	mutex_destroy(&sLock);
148	return status;
149}
150
151
152void
153uninit_callout(void)
154{
155	delete_sem(sWaitSem);
156
157	wait_for_thread(sThread, NULL);
158
159	mutex_lock(&sLock);
160	mutex_destroy(&sLock);
161}
162
163
164// #pragma mark - public API
165
166
167void
168callout_init(struct callout *callout, int mpsafe)
169{
170	if (mpsafe)
171		callout_init_mtx(callout, NULL, 0);
172	else
173		callout_init_mtx(callout, &Giant, 0);
174}
175
176
177void
178callout_init_mtx(struct callout *c, struct mtx *mtx, int flags)
179{
180	c->c_due = 0;
181
182	c->c_arg = NULL;
183	c->c_func = NULL;
184	c->c_mtx = mtx;
185	c->c_flags = flags;
186}
187
188
189static int
190_callout_stop(struct callout *c, bool drain, bool locked = false)
191{
192	TRACE("_callout_stop %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
193
194	MutexLocker locker;
195	if (!locked)
196		locker.SetTo(sLock, false);
197
198	bool lockHeld = false;
199	if (!drain && c->c_mtx != NULL) {
200		if (c->c_mtx != &Giant) {
201			// The documentation for callout_stop() confirms any associated locks
202			// must be held when invoking it. We depend on this behavior for
203			// synchronization with the callout thread, which can modify c_due
204			// with only the callout's lock held.
205			mtx_assert(c->c_mtx, MA_OWNED);
206			lockHeld = true;
207		} else {
208			// FreeBSD is lenient and does not assert if the callout mutex is &Giant.
209			lockHeld = mtx_owned(&Giant);
210		}
211	}
212
213	int ret = -1;
214	if (callout_active(c)) {
215		ret = 0;
216		if (!drain && lockHeld && c->c_due == 0) {
217			// The callout is active, but c_due == 0 and we hold the locks: this
218			// means the callout thread has dequeued it and is waiting for c_mtx.
219			// Clear c_due to signal the callout thread.
220			c->c_due = -1;
221			ret = 1;
222		}
223		if (drain) {
224			locker.Unlock();
225			while (callout_active(c))
226				snooze(100);
227			locker.Lock();
228		}
229	}
230
231	if (c->c_due <= 0)
232		return ret;
233
234	// this timer is scheduled, cancel it
235	list_remove_item(&sTimers, c);
236	c->c_due = -1;
237	return (ret == -1) ? 1 : ret;
238}
239
240
241int
242callout_reset(struct callout *c, int _ticks, void (*func)(void *), void *arg)
243{
244	MutexLocker locker(sLock);
245
246	TRACE("callout_reset %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
247
248	c->c_func = func;
249	c->c_arg = arg;
250
251	if (_ticks < 0) {
252		int stopped = -1;
253		if (c->c_due > 0)
254			stopped = _callout_stop(c, 0, true);
255		return (stopped == -1) ? 0 : 1;
256	}
257
258	int rescheduled = 0;
259	if (_ticks >= 0) {
260		// reschedule or add this timer
261		if (c->c_due <= 0) {
262			list_add_item(&sTimers, c);
263		} else {
264			rescheduled = 1;
265		}
266
267		c->c_due = system_time() + TICKS_2_USEC(_ticks);
268
269		// notify timer about the change if necessary
270		if (sTimeout > c->c_due)
271			release_sem(sWaitSem);
272	}
273
274	return rescheduled;
275}
276
277
278int
279_callout_stop_safe(struct callout *c, int safe)
280{
281	if (c == NULL)
282		return -1;
283
284	return _callout_stop(c, safe);
285}
286
287
288int
289callout_schedule(struct callout *callout, int _ticks)
290{
291	return callout_reset(callout, _ticks, callout->c_func, callout->c_arg);
292}
293
294
295int
296callout_pending(struct callout *c)
297{
298	return c->c_due > 0;
299}
300
301
302int
303callout_active(struct callout *c)
304{
305	return c == sCurrentCallout;
306}
307