xref: /haiku/src/add-ons/kernel/network/stack/utility.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
1 /*
2  * Copyright 2006, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include "stack_private.h"
11 #include "utility.h"
12 
13 #include <net_buffer.h>
14 #include <util/AutoLock.h>
15 
16 #include <ByteOrder.h>
17 #include <KernelExport.h>
18 
19 
20 static struct list sTimers;
21 static benaphore sTimerLock;
22 static sem_id sTimerWaitSem;
23 static thread_id sTimerThread;
24 
25 
26 uint16
27 compute_checksum(uint8 *_buffer, size_t length)
28 {
29 	uint16 *buffer = (uint16 *)_buffer;
30 	uint32 sum = 0;
31 
32 	// TODO: unfold loop for speed
33 	// TODO: write processor dependent version for speed
34 	while (length >= 2) {
35 		sum += *buffer++;
36 		length -= 2;
37 	}
38 
39 	if (length) {
40 		// give the last byte it's proper endian-aware treatment
41 #if B_HOST_IS_LENDIAN
42 		sum += *(uint8 *)buffer;
43 #else
44 		uint8 ordered[2];
45 		ordered[0] = *(uint8 *)buffer;
46 		ordered[1] = 0;
47 		sum += *(uint16 *)ordered;
48 #endif
49 	}
50 
51 	while (sum >> 16) {
52 		sum = (sum & 0xffff) + (sum >> 16);
53 	}
54 
55 	return sum;
56 }
57 
58 
59 uint16
60 checksum(uint8 *buffer, size_t length)
61 {
62 	return ~compute_checksum(buffer, length);
63 }
64 
65 
66 //	#pragma mark - FIFOs
67 
68 
69 status_t
70 init_fifo(net_fifo *fifo, const char *name, size_t maxBytes)
71 {
72 	status_t status = benaphore_init(&fifo->lock, name);
73 	if (status < B_OK)
74 		return status;
75 
76 	fifo->notify = create_sem(1, name);
77 	if (fifo->notify < B_OK) {
78 		benaphore_destroy(&fifo->lock);
79 		return fifo->notify;
80 	}
81 
82 	fifo->max_bytes = maxBytes;
83 	fifo->current_bytes = 0;
84 	fifo->waiting = 0;
85 
86 	list_init(&fifo->buffers);
87 
88 	return B_OK;
89 }
90 
91 
92 void
93 uninit_fifo(net_fifo *fifo)
94 {
95 	clear_fifo(fifo);
96 
97 	benaphore_destroy(&fifo->lock);
98 	delete_sem(fifo->notify);
99 }
100 
101 
102 status_t
103 fifo_enqueue_buffer(net_fifo *fifo, net_buffer *buffer)
104 {
105 	BenaphoreLocker locker(fifo->lock);
106 
107 	if (fifo->max_bytes > 0 && fifo->current_bytes + buffer->size > fifo->max_bytes)
108 		return ENOBUFS;
109 
110 	list_add_item(&fifo->buffers, buffer);
111 	fifo->current_bytes += buffer->size;
112 
113 	if (fifo->waiting > 0) {
114 		fifo->waiting--;
115 		release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE);
116 			// we still hold the benaphore lock, so it makes no sense
117 			// to reschedule after having released the sync semaphore
118 	}
119 
120 	return B_OK;
121 }
122 
123 
124 /*!
125 	Gets the first buffer from the FIFO. If there is no buffer, it
126 	will wait depending on the \a flags and \a timeout.
127 	The following flags are supported (the rest is ignored):
128 		MSG_DONTWAIT - ignores the timeout and never wait for a buffer; if your
129 			socket is O_NONBLOCK, you should specify this flag. A \a timeout of
130 			zero is equivalent to this flag, though.
131 		MSG_PEEK - returns a clone of the buffer and keep the original
132 			in the FIFO.
133 */
134 ssize_t
135 fifo_dequeue_buffer(net_fifo *fifo, uint32 flags, bigtime_t timeout,
136 	net_buffer **_buffer)
137 {
138 	benaphore_lock(&fifo->lock);
139 	bool dontWait = (flags & MSG_DONTWAIT) != 0 || timeout == 0;
140 	status_t status;
141 
142 	while (true) {
143 		net_buffer *buffer = (net_buffer *)list_get_first_item(&fifo->buffers);
144 		if (buffer != NULL) {
145 			if ((flags & MSG_PEEK) != 0) {
146 				// we need to clone the buffer for inspection; we can't give a
147 				// handle to a buffer that we're still using
148 				buffer = gNetBufferModule.clone(buffer, false);
149 				if (buffer == NULL) {
150 					status = B_NO_MEMORY;
151 					break;
152 				}
153 			} else
154 				list_remove_item(&fifo->buffers, buffer);
155 
156 			*_buffer = buffer;
157 			status = B_OK;
158 			break;
159 		}
160 
161 		if (!dontWait)
162 			fifo->waiting++;
163 
164 		// we need to wait until a new buffer becomes available
165 		benaphore_unlock(&fifo->lock);
166 
167 		if (dontWait)
168 			return B_WOULD_BLOCK;
169 
170 		status = acquire_sem_etc(fifo->notify, 1,
171 			B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, timeout);
172 		if (status < B_OK)
173 			return status;
174 
175 		// try again
176 		benaphore_lock(&fifo->lock);
177 	}
178 
179 	if ((flags & MSG_PEEK) != 0 && fifo->waiting > 0) {
180 		// another thread is waiting for data, since we didn't eat the
181 		// buffer, it gets it
182 		fifo->waiting--;
183 		release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE);
184 	}
185 
186 	benaphore_unlock(&fifo->lock);
187 	return status;
188 }
189 
190 
191 status_t
192 clear_fifo(net_fifo *fifo)
193 {
194 	BenaphoreLocker locker(fifo->lock);
195 
196 	while (true) {
197 		net_buffer *buffer = (net_buffer *)list_remove_head_item(&fifo->buffers);
198 		if (buffer == NULL)
199 			break;
200 
201 		gNetBufferModule.free(buffer);
202 	}
203 
204 	fifo->current_bytes = 0;
205 	return B_OK;
206 }
207 
208 
209 //	#pragma mark - Timer
210 
211 
212 static status_t
213 timer_thread(void * /*data*/)
214 {
215 	status_t status = B_OK;
216 
217 	do {
218 		bigtime_t timeout = B_INFINITE_TIMEOUT;
219 
220 		if (status == B_TIMED_OUT || status == B_OK) {
221 			// scan timers for new timeout and/or execute a timer
222 			if (benaphore_lock(&sTimerLock) < B_OK)
223 				return B_OK;
224 
225 			struct net_timer *timer = NULL;
226 			while (true) {
227 				timer = (net_timer *)list_get_next_item(&sTimers, timer);
228 				if (timer == NULL)
229 					break;
230 
231 				if (timer->due < system_time()) {
232 					// execute timer
233 					list_remove_item(&sTimers, timer);
234 					timer->due = -1;
235 
236 					benaphore_unlock(&sTimerLock);
237 					timer->hook(timer, timer->data);
238 					benaphore_lock(&sTimerLock);
239 
240 					timer = NULL;
241 						// restart scanning as we unlocked the list
242 				} else {
243 					// calculate new timeout
244 					if (timer->due < timeout)
245 						timeout = timer->due;
246 				}
247 			}
248 
249 			benaphore_unlock(&sTimerLock);
250 		}
251 
252 		status = acquire_sem_etc(sTimerWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
253 			// the wait sem normally can't be acquired, so we
254 			// have to look at the status value the call returns:
255 			//
256 			// B_OK - a new timer has been added or canceled
257 			// B_TIMED_OUT - look for timers to be executed
258 			// B_BAD_SEM_ID - we are asked to quit
259 	} while (status != B_BAD_SEM_ID);
260 
261 	return B_OK;
262 }
263 
264 
265 /*!
266 	Initializes a timer before use. You can also use this function to change
267 	a timer later on, but make sure you have canceled it before using set_timer().
268 */
269 void
270 init_timer(net_timer *timer, net_timer_func hook, void *data)
271 {
272 	timer->hook = hook;
273 	timer->data = data;
274 	timer->due = 0;
275 }
276 
277 
278 /*!
279 	Sets or cancels a timer. When the \a delay is below zero, an eventually running
280 	timer is canceled, if not, it is scheduled to be executed after the specified
281 	\a delay.
282 	You need to have initialized the timer before calling this function.
283 	In case you need to change a running timer, you have to cancel it first, before
284 	making any changes.
285 */
286 void
287 set_timer(net_timer *timer, bigtime_t delay)
288 {
289 	BenaphoreLocker locker(sTimerLock);
290 
291 	if (timer->due > 0) {
292 		// this timer is already scheduled, cancel it
293 		list_remove_item(&sTimers, timer);
294 	}
295 
296 	if (delay >= 0) {
297 		// add this timer
298 		timer->due = system_time() + delay;
299 		list_add_item(&sTimers, timer);
300 	}
301 
302 	// notify timer about the change
303 	release_sem(sTimerWaitSem);
304 }
305 
306 
307 status_t
308 init_timers(void)
309 {
310 	list_init(&sTimers);
311 
312 	status_t status = benaphore_init(&sTimerLock, "net timer");
313 	if (status < B_OK)
314 		return status;
315 
316 	sTimerWaitSem = create_sem(0, "net timer wait");
317 	if (sTimerWaitSem < B_OK) {
318 		status = sTimerWaitSem;
319 		goto err1;
320 	}
321 
322 	sTimerThread = spawn_kernel_thread(timer_thread, "net timer",
323 		B_NORMAL_PRIORITY, NULL);
324 	if (sTimerThread < B_OK) {
325 		status = sTimerThread;
326 		goto err2;
327 	}
328 
329 	return resume_thread(sTimerThread);
330 
331 err1:
332 	benaphore_destroy(&sTimerLock);
333 err2:
334 	delete_sem(sTimerWaitSem);
335 	return status;
336 }
337 
338 
339 void
340 uninit_timers(void)
341 {
342 	benaphore_destroy(&sTimerLock);
343 	delete_sem(sTimerWaitSem);
344 
345 	status_t status;
346 	wait_for_thread(sTimerThread, &status);
347 }
348 
349