xref: /haiku/src/libs/compat/freebsd_network/callout.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2018, 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 
11 extern "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 
28 static struct list sTimers;
29 static mutex sLock;
30 static sem_id sWaitSem;
31 static callout* sCurrentCallout;
32 static thread_id sThread;
33 static bigtime_t sTimeout;
34 
35 
36 static status_t
37 callout_thread(void* /*data*/)
38 {
39 	status_t status = B_NO_INIT;
40 
41 	do {
42 		bigtime_t timeout = B_INFINITE_TIMEOUT;
43 
44 		if (status == B_TIMED_OUT || status == B_OK) {
45 			// scan timers for new timeout and/or execute a timer
46 			if ((status = mutex_lock(&sLock)) != B_OK)
47 				continue;
48 
49 			struct callout* c = NULL;
50 			while (true) {
51 				c = (callout*)list_get_next_item(&sTimers, c);
52 				if (c == NULL)
53 					break;
54 
55 				if (c->c_due < system_time()) {
56 					struct mtx *mutex = c->c_mtx;
57 
58 					// execute timer
59 					list_remove_item(&sTimers, c);
60 					c->c_due = -1;
61 					sCurrentCallout = c;
62 
63 					mutex_unlock(&sLock);
64 
65 					if (mutex != NULL)
66 						mtx_lock(mutex);
67 
68 					c->c_func(c->c_arg);
69 
70 					if (mutex != NULL
71 							&& (c->c_flags & CALLOUT_RETURNUNLOCKED) == 0)
72 						mtx_unlock(mutex);
73 
74 					if ((status = mutex_lock(&sLock)) != B_OK)
75 						continue;
76 
77 					sCurrentCallout = NULL;
78 					c = NULL;
79 						// restart scanning as we unlocked the list
80 				} else {
81 					// calculate new timeout
82 					if (c->c_due < timeout)
83 						timeout = c->c_due;
84 				}
85 			}
86 
87 			sTimeout = timeout;
88 			mutex_unlock(&sLock);
89 		}
90 
91 		status = acquire_sem_etc(sWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
92 			// the wait sem normally can't be acquired, so we
93 			// have to look at the status value the call returns:
94 			//
95 			// B_OK - a new timer has been added or canceled
96 			// B_TIMED_OUT - look for timers to be executed
97 			// B_BAD_SEM_ID - we are asked to quit
98 	} while (status != B_BAD_SEM_ID);
99 
100 	return B_OK;
101 }
102 
103 
104 // #pragma mark - private API
105 
106 
107 status_t
108 init_callout(void)
109 {
110 	list_init(&sTimers);
111 	sTimeout = B_INFINITE_TIMEOUT;
112 
113 	status_t status = B_OK;
114 	mutex_init(&sLock, "fbsd callout");
115 
116 	sWaitSem = create_sem(0, "fbsd callout wait");
117 	if (sWaitSem < 0) {
118 		status = sWaitSem;
119 		goto err1;
120 	}
121 
122 	sThread = spawn_kernel_thread(callout_thread, "fbsd callout",
123 		B_DISPLAY_PRIORITY, NULL);
124 	if (sThread < 0) {
125 		status = sThread;
126 		goto err2;
127 	}
128 
129 	return resume_thread(sThread);
130 
131 err2:
132 	delete_sem(sWaitSem);
133 err1:
134 	mutex_destroy(&sLock);
135 	return status;
136 }
137 
138 
139 void
140 uninit_callout(void)
141 {
142 	delete_sem(sWaitSem);
143 
144 	wait_for_thread(sThread, NULL);
145 
146 	mutex_lock(&sLock);
147 	mutex_destroy(&sLock);
148 }
149 
150 
151 // #pragma mark - public API
152 
153 
154 void
155 callout_init(struct callout *callout, int mpsafe)
156 {
157 	if (mpsafe)
158 		callout_init_mtx(callout, NULL, 0);
159 	else
160 		callout_init_mtx(callout, &Giant, 0);
161 }
162 
163 
164 void
165 callout_init_mtx(struct callout *c, struct mtx *mtx, int flags)
166 {
167 	c->c_due = 0;
168 
169 	c->c_arg = NULL;
170 	c->c_func = NULL;
171 	c->c_mtx = mtx;
172 	c->c_flags = flags;
173 }
174 
175 
176 int
177 callout_reset(struct callout *c, int _ticks, void (*func)(void *), void *arg)
178 {
179 	int canceled = callout_stop(c);
180 
181 	MutexLocker locker(sLock);
182 
183 	c->c_func = func;
184 	c->c_arg = arg;
185 
186 	TRACE("callout_reset %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
187 
188 	if (_ticks >= 0) {
189 		// reschedule or add this timer
190 		if (c->c_due <= 0)
191 			list_add_item(&sTimers, c);
192 
193 		c->c_due = system_time() + TICKS_2_USEC(_ticks);
194 
195 		// notify timer about the change if necessary
196 		if (sTimeout > c->c_due)
197 			release_sem(sWaitSem);
198 	}
199 
200 	return canceled;
201 }
202 
203 
204 int
205 callout_schedule(struct callout *callout, int _ticks)
206 {
207 	return callout_reset(callout, _ticks, callout->c_func, callout->c_arg);
208 }
209 
210 
211 int
212 _callout_stop_safe(struct callout *c, int safe)
213 {
214 	if (c == NULL)
215 		return -1;
216 
217 	TRACE("_callout_stop_safe %p, func %p, arg %p\n", c, c->c_func, c->c_arg);
218 
219 	MutexLocker locker(sLock);
220 
221 	if (callout_active(c)) {
222 		if (safe) {
223 			locker.Unlock();
224 			while (callout_active(c))
225 				snooze(100);
226 		}
227 		return 0;
228 	}
229 
230 	if (c->c_due <= 0)
231 		return -1;
232 
233 	// this timer is scheduled, cancel it
234 	list_remove_item(&sTimers, c);
235 	c->c_due = 0;
236 	return 1;
237 }
238 
239 
240 int
241 callout_pending(struct callout *c)
242 {
243 	return c->c_due > 0;
244 }
245 
246 
247 int
248 callout_active(struct callout *c)
249 {
250 	return c == sCurrentCallout;
251 }
252