Files
haiku-beta6/src/add-ons/kernel/network/stack/utility.cpp
T

439 lines
8.8 KiB
C++
Raw Normal View History

/*
* Copyright 2006-2007, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, [email protected]
*/
#include "stack_private.h"
#include "utility.h"
#include <net_buffer.h>
#include <util/AutoLock.h>
#include <ByteOrder.h>
#include <KernelExport.h>
static struct list sTimers;
static benaphore sTimerLock;
static sem_id sTimerWaitSem;
static thread_id sTimerThread;
static bigtime_t sTimerTimeout;
void *
UserBuffer::Copy(void *source, size_t length)
{
if (fStatus != B_OK)
return NULL;
if (fAvailable < length) {
fStatus = ENOBUFS;
return NULL;
}
fStatus = user_memcpy(fBuffer, source, length);
if (fStatus < B_OK)
return NULL;
void *current = fBuffer;
fAvailable -= length;
fBuffer += length;
return current;
}
uint16
compute_checksum(uint8 *_buffer, size_t length)
{
uint16 *buffer = (uint16 *)_buffer;
uint32 sum = 0;
// TODO: unfold loop for speed
// TODO: write processor dependent version for speed
while (length >= 2) {
sum += *buffer++;
length -= 2;
}
if (length) {
// give the last byte it's proper endian-aware treatment
#if B_HOST_IS_LENDIAN
sum += *(uint8 *)buffer;
#else
uint8 ordered[2];
ordered[0] = *(uint8 *)buffer;
ordered[1] = 0;
sum += *(uint16 *)ordered;
#endif
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
return sum;
}
uint16
checksum(uint8 *buffer, size_t length)
{
return ~compute_checksum(buffer, length);
}
// #pragma mark - Notifications
status_t
notify_socket(net_socket *socket, uint8 event, int32 value)
{
return gNetSocketModule.notify(socket, event, value);
}
// #pragma mark - FIFOs
status_t
init_fifo(net_fifo *fifo, const char *name, size_t maxBytes)
{
status_t status = benaphore_init(&fifo->lock, name);
if (status < B_OK)
return status;
fifo->notify = create_sem(1, name);
if (fifo->notify < B_OK) {
benaphore_destroy(&fifo->lock);
return fifo->notify;
}
fifo->max_bytes = maxBytes;
fifo->current_bytes = 0;
fifo->waiting = 0;
list_init(&fifo->buffers);
return B_OK;
}
void
uninit_fifo(net_fifo *fifo)
{
clear_fifo(fifo);
benaphore_destroy(&fifo->lock);
delete_sem(fifo->notify);
}
static status_t
_fifo_enqueue_buffer(net_fifo *fifo, net_buffer *buffer)
{
if (fifo->max_bytes > 0 && fifo->current_bytes + buffer->size > fifo->max_bytes)
return ENOBUFS;
list_add_item(&fifo->buffers, buffer);
fifo->current_bytes += buffer->size;
if (fifo->waiting > 0) {
fifo->waiting--;
release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE);
// we still hold the benaphore lock, so it makes no sense
// to reschedule after having released the sync semaphore
}
return B_OK;
}
status_t
fifo_enqueue_buffer(net_fifo *fifo, net_buffer *buffer)
{
BenaphoreLocker locker(fifo->lock);
return _fifo_enqueue_buffer(fifo, buffer);
}
/*!
Gets the first buffer from the FIFO. If there is no buffer, it
will wait depending on the \a flags and \a timeout.
The following flags are supported (the rest is ignored):
MSG_DONTWAIT - ignores the timeout and never wait for a buffer; if your
socket is O_NONBLOCK, you should specify this flag. A \a timeout of
zero is equivalent to this flag, though.
MSG_PEEK - returns a clone of the buffer and keep the original
in the FIFO.
*/
ssize_t
fifo_dequeue_buffer(net_fifo *fifo, uint32 flags, bigtime_t timeout,
net_buffer **_buffer)
{
benaphore_lock(&fifo->lock);
bool dontWait = (flags & MSG_DONTWAIT) != 0 || timeout == 0;
status_t status;
while (true) {
net_buffer *buffer = (net_buffer *)list_get_first_item(&fifo->buffers);
if (buffer != NULL) {
if ((flags & MSG_PEEK) != 0) {
// we need to clone the buffer for inspection; we can't give a
// handle to a buffer that we're still using
buffer = gNetBufferModule.clone(buffer, false);
if (buffer == NULL) {
status = B_NO_MEMORY;
break;
}
} else {
list_remove_item(&fifo->buffers, buffer);
fifo->current_bytes -= buffer->size;
}
*_buffer = buffer;
status = B_OK;
break;
}
if (!dontWait)
fifo->waiting++;
// we need to wait until a new buffer becomes available
benaphore_unlock(&fifo->lock);
if (dontWait)
return B_WOULD_BLOCK;
status = acquire_sem_etc(fifo->notify, 1,
B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, timeout);
if (status < B_OK)
return status;
// try again
benaphore_lock(&fifo->lock);
}
if ((flags & MSG_PEEK) != 0 && fifo->waiting > 0) {
// another thread is waiting for data, since we didn't eat the
// buffer, it gets it
fifo->waiting--;
release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE);
}
benaphore_unlock(&fifo->lock);
return status;
}
status_t
clear_fifo(net_fifo *fifo)
{
BenaphoreLocker locker(fifo->lock);
while (true) {
net_buffer *buffer = (net_buffer *)list_remove_head_item(&fifo->buffers);
if (buffer == NULL)
break;
gNetBufferModule.free(buffer);
}
fifo->current_bytes = 0;
return B_OK;
}
status_t
fifo_socket_enqueue_buffer(net_fifo *fifo, net_socket *socket, uint8 event,
net_buffer *_buffer)
{
net_buffer *buffer = gNetBufferModule.clone(_buffer, false);
if (buffer == NULL)
return B_NO_MEMORY;
BenaphoreLocker locker(fifo->lock);
status_t status = _fifo_enqueue_buffer(fifo, buffer);
if (status < B_OK)
gNetBufferModule.free(buffer);
else
notify_socket(socket, event, fifo->current_bytes);
return status;
}
// #pragma mark - Timer
static status_t
timer_thread(void * /*data*/)
{
status_t status = B_OK;
do {
bigtime_t timeout = B_INFINITE_TIMEOUT;
if (status == B_TIMED_OUT || status == B_OK) {
// scan timers for new timeout and/or execute a timer
if (benaphore_lock(&sTimerLock) < B_OK)
return B_OK;
struct net_timer *timer = NULL;
while (true) {
timer = (net_timer *)list_get_next_item(&sTimers, timer);
if (timer == NULL)
break;
if (timer->due < system_time()) {
// execute timer
list_remove_item(&sTimers, timer);
timer->due = -1;
benaphore_unlock(&sTimerLock);
timer->hook(timer, timer->data);
benaphore_lock(&sTimerLock);
timer = NULL;
// restart scanning as we unlocked the list
} else {
// calculate new timeout
if (timer->due < timeout)
timeout = timer->due;
}
}
sTimerTimeout = timeout;
benaphore_unlock(&sTimerLock);
}
status = acquire_sem_etc(sTimerWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
// the wait sem normally can't be acquired, so we
// have to look at the status value the call returns:
//
// B_OK - a new timer has been added or canceled
// B_TIMED_OUT - look for timers to be executed
// B_BAD_SEM_ID - we are asked to quit
} while (status != B_BAD_SEM_ID);
return B_OK;
}
/*!
Initializes a timer before use. You can also use this function to change
a timer later on, but make sure you have canceled it before using set_timer().
*/
void
init_timer(net_timer *timer, net_timer_func hook, void *data)
{
timer->hook = hook;
timer->data = data;
timer->due = 0;
}
/*!
Sets or cancels a timer. When the \a delay is below zero, an eventually running
timer is canceled, if not, it is scheduled to be executed after the specified
\a delay.
You need to have initialized the timer before calling this function.
In case you need to change a running timer, you have to cancel it first, before
making any changes.
*/
void
set_timer(net_timer *timer, bigtime_t delay)
{
BenaphoreLocker locker(sTimerLock);
if (timer->due > 0 && delay < 0) {
// this timer is scheduled, cancel it
list_remove_item(&sTimers, timer);
timer->due = 0;
}
if (delay >= 0) {
// reschedule or add this timer
if (timer->due <= 0)
list_add_item(&sTimers, timer);
timer->due = system_time() + delay;
// notify timer about the change if necessary
if (sTimerTimeout > timer->due)
release_sem(sTimerWaitSem);
}
}
bool
cancel_timer(struct net_timer *timer)
{
BenaphoreLocker locker(sTimerLock);
if (timer->due <= 0)
return false;
// this timer is scheduled, cancel it
list_remove_item(&sTimers, timer);
timer->due = 0;
return true;
}
bool
is_timer_active(net_timer *timer)
{
return timer->due > 0;
}
status_t
init_timers(void)
{
list_init(&sTimers);
sTimerTimeout = B_INFINITE_TIMEOUT;
status_t status = benaphore_init(&sTimerLock, "net timer");
if (status < B_OK)
return status;
sTimerWaitSem = create_sem(0, "net timer wait");
if (sTimerWaitSem < B_OK) {
status = sTimerWaitSem;
goto err1;
}
sTimerThread = spawn_kernel_thread(timer_thread, "net timer",
B_NORMAL_PRIORITY, NULL);
if (sTimerThread < B_OK) {
status = sTimerThread;
goto err2;
}
return resume_thread(sTimerThread);
err1:
benaphore_destroy(&sTimerLock);
err2:
delete_sem(sTimerWaitSem);
return status;
}
void
uninit_timers(void)
{
benaphore_destroy(&sTimerLock);
delete_sem(sTimerWaitSem);
status_t status;
wait_for_thread(sTimerThread, &status);
}