#include "utility.h"
#include <ByteOrder.h>
#include <KernelExport.h>
#include <condition_variable.h>
#include <net_buffer.h>
#include <syscall_restart.h>
#include <util/AutoLock.h>
#include "stack_private.h"
#ifdef TRACE_UTILITY
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...) ;
#endif
static struct list sTimers;
static mutex sTimerLock;
static sem_id sTimerWaitSem;
static ConditionVariable sWaitForTimerCondition;
static net_timer* sCurrentTimer;
static thread_id sTimerThread;
static bigtime_t sTimerTimeout;
void*
UserBuffer::Push(void* source, size_t length)
{
if (fStatus != B_OK)
return NULL;
if (fAvailable < length) {
fStatus = ENOBUFS;
return NULL;
}
#ifdef _KERNEL_MODE
fStatus = user_memcpy(fBuffer, source, length);
if (fStatus != B_OK)
return NULL;
#else
memcpy(fBuffer, source, length);
#endif
void* current = fBuffer;
fAvailable -= length;
fBuffer += length;
return current;
}
status_t
UserBuffer::Pad(size_t length)
{
if (fStatus != B_OK)
return fStatus;
if (fAvailable < length)
return fStatus = ENOBUFS;
fStatus = user_memset(fBuffer, 0, length);
if (fStatus != B_OK)
return fStatus;
fAvailable -= length;
fBuffer += length;
return B_OK;
}
status_t
UserBuffer::PadToNext(size_t length)
{
return Pad((BytesConsumed() + length - 1) / length - BytesConsumed());
}
uint16
compute_checksum(uint8* _buffer, size_t length)
{
uint16* buffer = (uint16*)_buffer;
uint32 sum = 0;
while (length >= 2) {
sum += *buffer++;
length -= 2;
}
if (length) {
#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);
}
status_t
notify_socket(net_socket* socket, uint8 event, int32 value)
{
return gNetSocketModule.notify(socket, event, value);
}
static inline void
fifo_notify_one_reader(int32& waiting, sem_id sem)
{
if (waiting > 0) {
waiting--;
release_sem_etc(sem, 1, B_DO_NOT_RESCHEDULE);
}
}
status_t
init_fifo(net_fifo* fifo, const char* name, size_t maxBytes)
{
mutex_init_etc(&fifo->lock, name, MUTEX_FLAG_CLONE_NAME);
fifo->notify = create_sem(0, name);
if (fifo->notify < B_OK) {
mutex_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);
mutex_destroy(&fifo->lock);
delete_sem(fifo->notify);
}
static inline status_t
base_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;
fifo_notify_one_reader(fifo->waiting, fifo->notify);
return B_OK;
}
status_t
fifo_enqueue_buffer(net_fifo* fifo, net_buffer* buffer)
{
MutexLocker locker(fifo->lock);
return base_fifo_enqueue_buffer(fifo, buffer);
}
ssize_t
fifo_dequeue_buffer(net_fifo* fifo, uint32 flags, bigtime_t timeout,
net_buffer** _buffer)
{
if ((flags & ~(MSG_DONTWAIT | MSG_PEEK)) != 0)
return EOPNOTSUPP;
MutexLocker locker(fifo->lock);
const 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) {
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++;
locker.Unlock();
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;
locker.Lock();
}
if (flags & MSG_PEEK)
fifo_notify_one_reader(fifo->waiting, fifo->notify);
return status;
}
status_t
clear_fifo(net_fifo* fifo)
{
MutexLocker 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;
MutexLocker locker(fifo->lock);
status_t status = base_fifo_enqueue_buffer(fifo, buffer);
if (status < B_OK)
gNetBufferModule.free(buffer);
else
notify_socket(socket, event, fifo->current_bytes);
return status;
}
static status_t
timer_thread(void* )
{
status_t status = B_OK;
do {
bigtime_t timeout = B_INFINITE_TIMEOUT;
if (status == B_TIMED_OUT || status == B_OK) {
mutex_lock(&sTimerLock);
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()) {
list_remove_item(&sTimers, timer);
timer->due = -1;
sCurrentTimer = timer;
mutex_unlock(&sTimerLock);
timer->hook(timer, timer->data);
mutex_lock(&sTimerLock);
sCurrentTimer = NULL;
sWaitForTimerCondition.NotifyAll();
timer = NULL;
} else {
if (timer->due < timeout)
timeout = timer->due;
}
}
sTimerTimeout = timeout;
mutex_unlock(&sTimerLock);
}
status = acquire_sem_etc(sTimerWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
} while (status != B_BAD_SEM_ID);
return B_OK;
}
void
init_timer(net_timer* timer, net_timer_func hook, void* data)
{
timer->hook = hook;
timer->data = data;
timer->due = 0;
timer->flags = 0;
}
void
set_timer(net_timer* timer, bigtime_t delay)
{
MutexLocker locker(sTimerLock);
TRACE("set_timer %p, hook %p, data %p\n", timer, timer->hook, timer->data);
if (timer->due > 0 && delay < 0) {
list_remove_item(&sTimers, timer);
timer->due = 0;
}
if (delay >= 0) {
if (timer->due <= 0)
list_add_item(&sTimers, timer);
timer->due = system_time() + delay;
if (sTimerTimeout > timer->due)
release_sem(sTimerWaitSem);
}
}
bool
cancel_timer(struct net_timer* timer)
{
MutexLocker locker(sTimerLock);
TRACE("cancel_timer %p, hook %p, data %p\n", timer, timer->hook,
timer->data);
if (timer->due <= 0)
return false;
list_remove_item(&sTimers, timer);
timer->due = 0;
return true;
}
status_t
wait_for_timer(struct net_timer* timer)
{
if (find_thread(NULL) == sTimerThread) {
return B_BAD_VALUE;
}
while (true) {
MutexLocker locker(sTimerLock);
if (timer->due <= 0 && sCurrentTimer != timer)
return B_OK;
ConditionVariableEntry entry;
sWaitForTimerCondition.Add(&entry);
locker.Unlock();
entry.Wait();
}
return B_OK;
}
bool
is_timer_active(net_timer* timer)
{
return timer->due > 0;
}
bool
is_timer_running(net_timer* timer)
{
return timer == sCurrentTimer;
}
static int
dump_timer(int argc, char** argv)
{
kprintf("timer hook data due in\n");
struct net_timer* timer = NULL;
while (true) {
timer = (net_timer*)list_get_next_item(&sTimers, timer);
if (timer == NULL)
break;
kprintf("%p %p %p %" B_PRId64 "\n", timer, timer->hook, timer->data,
timer->due > 0 ? timer->due - system_time() : -1);
}
return 0;
}
status_t
init_timers(void)
{
list_init(&sTimers);
sTimerTimeout = B_INFINITE_TIMEOUT;
status_t status = B_OK;
mutex_init(&sTimerLock, "net timer");
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;
}
sWaitForTimerCondition.Init(NULL, "wait for net timer");
add_debugger_command("net_timer", dump_timer,
"Lists all active network timer");
return resume_thread(sTimerThread);
err1:
mutex_destroy(&sTimerLock);
err2:
delete_sem(sTimerWaitSem);
return status;
}
void
uninit_timers(void)
{
delete_sem(sTimerWaitSem);
status_t status;
wait_for_thread(sTimerThread, &status);
mutex_lock(&sTimerLock);
mutex_destroy(&sTimerLock);
remove_debugger_command("net_timer", dump_timer);
}
bool
is_syscall(void)
{
return is_called_via_syscall();
}
bool
is_restarted_syscall(void)
{
return syscall_restart_is_restarted();
}
void
store_syscall_restart_timeout(bigtime_t timeout)
{
Thread* thread = thread_get_current_thread();
if ((thread->flags & THREAD_FLAGS_SYSCALL) != 0)
*(bigtime_t*)thread->syscall_restart.parameters = timeout;
}
bigtime_t
restore_syscall_restart_timeout(void)
{
Thread* thread = thread_get_current_thread();
return *(bigtime_t*)thread->syscall_restart.parameters;
}