From 0fce772df14e7b91af0264adb301a7e50c1e199e Mon Sep 17 00:00:00 2001 From: Philippe Houdoin Date: Tue, 27 Aug 2002 07:34:55 +0000 Subject: [PATCH] Moving the network stack driver (/dev/net/stack) from old /net_kit/source/driver/ to /current repository git-svn-id: file:///srv/svn/repos/haiku/trunk/current@894 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/drivers/Jamfile | 1 + .../kernel/drivers/network/stack/Jamfile | 23 + .../drivers/network/stack/net_stack_driver.c | 800 ++++++++++++++++ .../network/stack/net_userstack_driver.c | 852 ++++++++++++++++++ 4 files changed, 1676 insertions(+) create mode 100644 src/add-ons/kernel/drivers/network/stack/Jamfile create mode 100644 src/add-ons/kernel/drivers/network/stack/net_stack_driver.c create mode 100644 src/add-ons/kernel/drivers/network/stack/net_userstack_driver.c diff --git a/src/add-ons/kernel/drivers/Jamfile b/src/add-ons/kernel/drivers/Jamfile index 1de9eddb32..882e22f1bf 100644 --- a/src/add-ons/kernel/drivers/Jamfile +++ b/src/add-ons/kernel/drivers/Jamfile @@ -4,4 +4,5 @@ SubInclude OBOS_TOP src add-ons kernel drivers arch ; SubInclude OBOS_TOP src add-ons kernel drivers audio ; SubInclude OBOS_TOP src add-ons kernel drivers common ; SubInclude OBOS_TOP src add-ons kernel drivers random ; +SubInclude OBOS_TOP src add-ons kernel drivers net ; diff --git a/src/add-ons/kernel/drivers/network/stack/Jamfile b/src/add-ons/kernel/drivers/network/stack/Jamfile new file mode 100644 index 0000000000..a5be2a5f28 --- /dev/null +++ b/src/add-ons/kernel/drivers/network/stack/Jamfile @@ -0,0 +1,23 @@ +SubDir OBOS_TOP src add-ons kernel drivers net stack ; + +UsePrivateHeaders net ; +UsePublicHeaders posix ; + +R5KernelAddon stack : kernel drivers bin : + net_stack_driver.c +; + +# For OpenBeOS we should be building the driver objects this way. +#KernelObjects +# net_stack_driver.c +# : +# -fno-pic -D_KERNEL_MODE +# ; + +# Link to kernel/drivers/dev/net +{ + local dir = [ FDirName $(OBOS_ADDON_DIR) kernel drivers dev net ] ; + local instDriver = stack ; + MakeLocate $(instDriver) : $(dir) ; + RelSymLink $(instDriver) : stack ; +} diff --git a/src/add-ons/kernel/drivers/network/stack/net_stack_driver.c b/src/add-ons/kernel/drivers/network/stack/net_stack_driver.c new file mode 100644 index 0000000000..cd0a3ba1b8 --- /dev/null +++ b/src/add-ons/kernel/drivers/network/stack/net_stack_driver.c @@ -0,0 +1,800 @@ +/* net_stack_driver.c + * + * This file implements a very simple socket driver that is intended to + * act as an interface to the new networking stack. + */ + +#ifndef _KERNEL_MODE + +#error "This module MUST be built as a kernel driver!" + +#else + +#include +#include +#include +#include +#include +#include // For get_module()/put_module() + +#include "netinet/in_var.h" +#include "sys/select.h" + +#include "protosw.h" +#include "core_module.h" +#include "net_stack_driver.h" + +/* these are missing from KernelExport.h ... */ +#define B_SELECT_READ 1 +#define B_SELECT_WRITE 2 +#define B_SELECT_EXCEPTION 3 + +extern void notify_select_event(selectsync * sync, uint32 ref); + +#define SHOW_INSANE_DEBUGGING (1) +#define SERIAL_DEBUGGING (0) +/* Force the driver to stay loaded in memory */ +#define STAY_LOADED (0) + +/* + * Local definitions + * ----------------- + */ + +#ifndef DRIVER_NAME +#define DRIVER_NAME "net_stack_driver" +#endif + +#ifndef LOGID +#define LOGID DRIVER_NAME ": " +#endif + +#ifndef WARN +#define WARN "Warning: " +#endif + +#ifndef ERR +#define ERR "ERROR: " +#endif + +typedef void (*notify_select_event_function)(selectsync * sync, uint32 ref); + +// this struct will store one select() event to monitor per thread +typedef struct selecter { + struct selecter * next; + thread_id thread; + int event; + selectsync * sync; + uint32 ref; +} selecter; + +// the cookie we attach to each file descriptor opened on our driver entry +typedef struct { + void * socket; // NULL before ioctl(fd, NET_STACK_SOCKET/_ACCEPT) + uint32 open_flags; // the open() flags (mostly for storing O_NONBLOCK mode) + sem_id selecters_lock; // protect the selecters linked-list + selecter * selecters; // the select()'ers lists (thread-aware) +} net_stack_cookie; + +#if STAY_LOADED + /* to unload the driver, simply write UNLOAD_CMD to him: + * $ echo stop > /dev/net/stack + * As soon as last app, via libnet.so, stop using it, it will unload, + * and in turn stop the stack and unload all kernel modules... + */ + #define UNLOAD_CMD "stop" +#endif + +/* Prototypes of device hooks functions */ +static status_t net_stack_open(const char * name, uint32 flags, void ** cookie); +static status_t net_stack_close(void * cookie); +static status_t net_stack_free_cookie(void * cookie); +static status_t net_stack_control(void * cookie, uint32 msg,void * data, size_t datalen); +static status_t net_stack_read(void * cookie, off_t pos, void * data, size_t * datalen); +static status_t net_stack_write(void * cookie, off_t pos, const void * data, size_t * datalen); +static status_t net_stack_select(void *cookie, uint8 event, uint32 ref, selectsync *sync); +static status_t net_stack_deselect(void *cookie, uint8 event, selectsync *sync); +// static status_t net_stack_readv(void * cookie, off_t pos, const iovec * vec, size_t count, size_t * len); +// static status_t net_stack_writev(void * cookie, off_t pos, const iovec * vec, size_t count, size_t * len); + +/* Privates prototypes */ +static void on_socket_event(void * socket, uint32 event, void * cookie); +static void r5_notify_select_event(selectsync * sync, uint32 ref); + +#if STAY_LOADED +static status_t keep_driver_loaded(); +static status_t unload_driver(); +#endif + +/* + * Global variables + * ---------------- + */ + +const char * g_device_names_list[] = { + NET_STACK_DRIVER_PATH, + NULL +}; + +device_hooks g_net_stack_driver_hooks = +{ + net_stack_open, /* -> open entry point */ + net_stack_close, /* -> close entry point */ + net_stack_free_cookie, /* -> free entry point */ + net_stack_control, /* -> control entry point */ + net_stack_read, /* -> read entry point */ + net_stack_write, /* -> write entry point */ + net_stack_select, /* -> select entry point */ + net_stack_deselect, /* -> deselect entry point */ + NULL, /* -> readv entry pint */ + NULL /* ->writev entry point */ +}; + +struct core_module_info * core = NULL; + +// by default, assert we can use kernel select() support +notify_select_event_function g_nse = notify_select_event; + +#if STAY_LOADED +int g_stay_loaded_fd = -1; +#endif + + +/* + * Driver API calls + * ---------------- + */ + +_EXPORT int32 api_version = B_CUR_DRIVER_API_VERSION; + + + +/* Do we init ourselves? If we're in safe mode we'll decline so if things + * screw up we can boot and delete the broken driver! + * After my experiences earlier - a good idea! + * + * Also we'll turn on the serial debugger to aid in our debugging + * experience. + */ +_EXPORT status_t init_hardware(void) +{ + bool safemode = false; + void * sfmptr; + +#if SERIAL_DEBUGGING + int rv; + + // XXX - switch on/off at top of file... + set_dprintf_enabled(true); + rv = load_driver_symbols(DRIVER_NAME); +#endif + + // get a pointer to the driver settings... + sfmptr = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS); + + // only use the pointer if it's valid + if (sfmptr != NULL) { + // we got a pointer, now get setting... + safemode = get_driver_boolean_parameter(sfmptr, B_SAFEMODE_SAFE_MODE, false, false); + // now get rid of settings + unload_driver_settings(sfmptr); + } + if (safemode) { + dprintf(LOGID WARN "init_hardware: declining offer to join the party.\n"); + return B_ERROR; + } + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "init_hardware done.\n"); +#endif + + return B_OK; +} + + + +/* init_driver() + * called every time we're loaded. + */ +_EXPORT status_t init_driver(void) +{ + int rv = 0; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "init_driver, built %s %s\n", __DATE__, __TIME__); +#endif + + rv = get_module(CORE_MODULE_PATH, (module_info **) &core); + if (rv < 0) { + dprintf(LOGID ERR "Argh, can't load " CORE_MODULE_PATH " module: %d\n", rv); + return rv; + } + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "init_driver: core = %p\n", core); +#endif + + // start the network stack! + core->start(); + + return B_OK; +} + + + +/* uninit_driver() + * called every time the driver is unloaded + */ +_EXPORT void uninit_driver(void) +{ +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "uninit_driver\n"); +#endif + + if (core) { +#if STAY_LOADED + // shutdown the network stack + core->stop(); +#endif + put_module(CORE_MODULE_PATH); + }; +} + + + +/* publish_devices() + * called to publish our device. + */ +_EXPORT const char ** publish_devices() +{ + return g_device_names_list; +} + + + +_EXPORT device_hooks * find_device(const char* device_name) +{ + return &g_net_stack_driver_hooks; +} + +// #pragma mark - + + +/* + * Device hooks + * ------------ + */ + +// the network stack functions - mainly just pass throughs... +static status_t net_stack_open(const char * name, + uint32 flags, + void ** cookie) +{ + net_stack_cookie * nsc; + + nsc = (net_stack_cookie *) malloc(sizeof(*nsc)); + if (!nsc) + return B_NO_MEMORY; + + memset(nsc, 0, sizeof(*nsc)); + nsc->socket = NULL; // the socket will be allocated in NET_STACK_SOCKET ioctl + nsc->open_flags = flags; + nsc->selecters_lock = create_sem(1, "socket_selecters_lock"); + nsc->selecters = NULL; + + // attach this new net_socket_cookie to file descriptor + *cookie = nsc; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_open(%s, %s%s) return this cookie: %p\n", name, + ( ((flags & O_RWMASK) == O_RDONLY) ? "O_RDONLY" : + ((flags & O_RWMASK) == O_WRONLY) ? "O_WRONLY" : "O_RDWR"), + (flags & O_NONBLOCK) ? " O_NONBLOCK" : "", + *cookie); +#endif + + return B_OK; +} + + + +static status_t net_stack_close(void *cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + int rv; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_close(%p)\n", nsc); +#endif + + if (nsc->socket) { + // if a socket was opened on this fd, close it now. + rv = core->soclose(nsc->socket); + nsc->socket = NULL; + } + + return rv; +} + + + +static status_t net_stack_free_cookie(void *cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + selecter * s; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_free_cookie(%p)\n", cookie); +#endif + + // free the selecters list + delete_sem(nsc->selecters_lock); + s = nsc->selecters; + while (s) { + selecter * tmp = s; + s = s->next; + free(tmp); + }; + + // free the cookie + free(cookie); + return B_OK; +} + + + +static status_t net_stack_control(void *cookie, uint32 op, void * data, size_t len) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_control(%p, 0x%lX, %p, %ld)\n", cookie, op, data, len); +#endif + +#if STAY_LOADED + keep_driver_loaded(); +#endif + + switch (op) { + case NET_STACK_SOCKET: { + struct socket_args * args = (struct socket_args *) data; + int rv; + + // okay, now try to open a real socket behind this fd/net_stack_cookie pair + rv = core->initsocket(&nsc->socket); + if (rv == 0) + rv = core->socreate(args->family, nsc->socket, args->type, args->proto); + // TODO: This is where the open flags need to be addressed + return rv; + } + case NET_STACK_CONNECT: { + struct sockaddr_args * args = (struct sockaddr_args *) data; + // args->addr == sockaddr to connect to + return core->soconnect(nsc->socket, (caddr_t) args->addr, args->addrlen); + } + case NET_STACK_BIND: { + struct sockaddr_args * args = (struct sockaddr_args *) data; + // args->addr == sockaddr to try and bind to + return core->sobind(nsc->socket, (caddr_t) args->addr, args->addrlen); + } + case NET_STACK_LISTEN: { + struct int_args * args = (struct int_args *) data; + // args->value == backlog to set + return core->solisten(nsc->socket, args->value); + } + case NET_STACK_GET_COOKIE: { + /* this is needed by accept() call, to be able to pass back + * in NET_STACK_ACCEPT opcode the cookie (aka the net_stack_cookie!) + * of the filedescriptor to use for the new accepted socket + */ + *((void **) data) = cookie; + return B_OK; + } + case NET_STACK_ACCEPT: { + struct accept_args * args = (struct accept_args *) data; + net_stack_cookie * ansc = (net_stack_cookie *) args->cookie; + /* args->cookie == net_stack_cookie * of the already opened fd to use to the + * newly accepted socket + */ + return core->soaccept(nsc->socket, &ansc->socket, (void *)args->addr, &args->addrlen); + } + case NET_STACK_SEND: { + struct data_xfer_args * args = (struct data_xfer_args *) data; + // TODO: flags gets ignored here... + return net_stack_write(cookie, 0, args->data, &args->datalen); + } + case NET_STACK_RECV: { + struct data_xfer_args * args = (struct data_xfer_args *) data; + // TODO: flags gets ignored here... + return net_stack_read(cookie, 0, args->data, &args->datalen); + } + case NET_STACK_RECVFROM: { + struct msghdr * mh = (struct msghdr *) data; + int retsize, error; + + error = core->recvit(nsc->socket, mh, (caddr_t)&mh->msg_namelen, + &retsize); + if (error == 0) + return retsize; + return error; + } + case NET_STACK_SENDTO: { + struct msghdr * mh = (struct msghdr *) data; + int retsize, error; + + error = core->sendit(nsc->socket, mh, mh->msg_flags, + &retsize); + if (error == 0) + return retsize; + return error; + } + case NET_STACK_SYSCTL: { + struct sysctl_args * args = (struct sysctl_args *) data; + return core->net_sysctl(args->name, args->namelen, + args->oldp, args->oldlenp, + args->newp, args->newlen); + } + case NET_STACK_GETSOCKOPT: { + struct sockopt_args * args = (struct sockopt_args *) data; + return core->sogetopt(nsc->socket, args->level, args->option, + args->optval, (size_t *) &args->optlen); + } + case NET_STACK_SETSOCKOPT: { + struct sockopt_args * args = (struct sockopt_args *)data; + + return core->sosetopt(nsc->socket, args->level, args->option, + (const void *) args->optval, args->optlen); + } + case NET_STACK_GETSOCKNAME: { + struct sockaddr_args * args = (struct sockaddr_args *) data; + // args->addr == sockaddr to accept the sockname + return core->sogetsockname(nsc->socket, args->addr, &args->addrlen); + } + case NET_STACK_GETPEERNAME: { + struct sockaddr_args * args = (struct sockaddr_args *) data; + // args->addr == sockaddr to accept the peername + return core->sogetpeername(nsc->socket, args->addr, &args->addrlen); + } + case NET_STACK_STOP: { + core->stop(); + return B_OK; + } + case B_SET_BLOCKING_IO: { + nsc->open_flags &= ~O_NONBLOCK; + return B_OK; + } + case B_SET_NONBLOCKING_IO: { + nsc->open_flags |= O_NONBLOCK; + return B_OK; + } + case NET_STACK_SELECT: { + struct select_args *args = (struct select_args *) data; + + /* if we get this opcode, we are using the r5 kernel select() call, + * so we can't use his notify_select_event(), but our own implementation! */ + g_nse = r5_notify_select_event; + return net_stack_select(cookie, (args->ref & 0x0F), args->ref, args->sync); + } + case NET_STACK_DESELECT: { + struct select_args * args = (struct select_args *) data; + return net_stack_deselect(cookie, (args->ref & 0x0F), args->sync); + } + default: + if (nsc->socket) + // pass any unhandled opcode to the stack + return core->soo_ioctl(nsc->socket, op, data); + return B_BAD_VALUE; + } +} + + + +static status_t net_stack_read(void *cookie, + off_t position, + void *buffer, + size_t *readlen) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + struct iovec iov; + int error; + int flags = 0; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_read(%p, %Ld, %p, %ld)\n", cookie, position, buffer, *readlen); +#endif + +#if STAY_LOADED +# if SHOW_INSANE_DEBUGGING + dprintf(LOGID "Calling keep_driver_loaded()...\n"); +# endif + keep_driver_loaded(); +#endif + + if (! nsc->socket) + return B_BAD_VALUE; + + iov.iov_base = buffer; + iov.iov_len = *readlen; + + error = core->readit(nsc->socket, &iov, &flags); + *readlen = error; + return error; +} + + + +static status_t net_stack_write(void *cookie, + off_t position, + const void *buffer, + size_t *writelen) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + struct iovec iov; + int error; + int flags = 0; +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_write(%p, %Ld, %p, %ld)\n", cookie, position, buffer, *writelen); +#endif + +#if STAY_LOADED + keep_driver_loaded(); +#endif + + if (! nsc->socket) { +#if STAY_LOADED + if (*writelen >= strlen(UNLOAD_CMD) && + strncmp(buffer, UNLOAD_CMD, strlen(UNLOAD_CMD)) == 0) + // someone write/send/tell us to unload this driver, so do it! + return unload_driver(); +#endif + return B_BAD_VALUE; + }; + + iov.iov_base = (void*)buffer; + iov.iov_len = *writelen; + + error = core->writeit(nsc->socket, &iov, flags); + *writelen = error; + return error; +} + + + +static status_t net_stack_select(void *cookie, uint8 event, uint32 ref, selectsync *sync) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + selecter * s; + status_t status; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_select(%p, %d, %ld, %p)\n", cookie, event, ref, sync); +#endif + + if (! nsc->socket) + return B_BAD_VALUE; + + s = (selecter *) malloc(sizeof(selecter)); + if (! s) + return B_NO_MEMORY; + + s->thread = find_thread(NULL); // current thread id + s->event = event; + s->sync = sync; + s->ref = ref; + + // lock the selecters list + status = acquire_sem(nsc->selecters_lock); + if (status != B_OK) { + free(s); + return status; + }; + + // add it to selecters list + s->next = nsc->selecters; + nsc->selecters = s; + + // unlock the selecters list + release_sem(nsc->selecters_lock); + + // start (or continue) to monitor for socket event + return core->set_socket_event_callback(nsc->socket, on_socket_event, nsc, event); +} + + + +static status_t net_stack_deselect(void *cookie, uint8 event, selectsync *sync) +{ + net_stack_cookie *nsc = (net_stack_cookie *) cookie; + selecter * previous; + selecter * s; + thread_id current_thread; + status_t status; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "net_stack_deselect(%p, %d, %p)\n", cookie, event, sync); +#endif + + if (!nsc || !nsc->socket) + return B_BAD_VALUE; + + current_thread = find_thread(NULL); + + // lock the selecters list + status = acquire_sem(nsc->selecters_lock); + if (status != B_OK) + return status; + + previous = NULL; + s = nsc->selecters; + while (s) { + if (s->thread == current_thread && + s->event == event && s->sync == sync) + // selecter found! + break; + + previous = s; + s = s->next; + }; + + if (s != NULL) { + // remove it from selecters list + if (previous) + previous->next = s->next; + else + nsc->selecters = s->next; + free(s); + }; + + if (nsc->selecters == NULL) + // selecters list is empty: no need to monitor socket events anymore + core->set_socket_event_callback(nsc->socket, NULL, NULL, event); + + // unlock the selecters list + return release_sem(nsc->selecters_lock); +} + + +// #pragma mark - + +static void on_socket_event(void * socket, uint32 event, void * cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + selecter * s; + + if (!nsc) + return; + + if (nsc->socket != socket) { + #if SHOW_INSANE_DEBUGGING + dprintf(LOGID "on_socket_event(%p, %ld, %p): socket is higly suspect! Aborting.\n", socket, event, cookie); + #endif + return; + } + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "on_socket_event(%p, %ld, %p)\n", socket, event, cookie); +#endif + + // lock the selecters list + if (acquire_sem(nsc->selecters_lock) != B_OK) + return; + + s = nsc->selecters; + while (s) { + if (s->event == event) + // notify this selecter (thread/event pair) + g_nse(s->sync, s->ref); + s = s->next; + }; + + // unlock the selecters list + release_sem(nsc->selecters_lock); + return; +} + +/* + Under vanilla R5, we can't use the kernel notify_select_event(), + as select() kernel implementation is too buggy to be usefull. + So, here is our own notify_select_event() implementation, the driver-side pair + of the our libnet.so select() implementation... +*/ +static void r5_notify_select_event(selectsync * sync, uint32 ref) +{ + area_id area; + struct r5_selectsync * rss; + int fd; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "r5_notify_select_event(%p, %ld)\n", sync, ref); +#endif + + rss = NULL; + area = clone_area("r5_selectsync_area (driver)", (void **) &rss, + B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA, (area_id) sync); + if (area < B_OK) { +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "r5_notify_select_event: clone_area(%d) failed -> %d!\n", (area_id) sync, area); +#endif + return; + }; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "r5_selectsync at %p (area %ld, clone from %ld):\n" + "lock %ld\n" + "wakeup %ld\n", rss, area, (area_id) sync, rss->lock, rss->wakeup); +#endif + + if (acquire_sem(rss->lock) != B_OK) + // if we can't (anymore?) lock the shared r5_selectsync, select() party is done + goto error; + + fd = ref >> 8; + switch (ref & 0xFF) { + case B_SELECT_READ: + FD_SET(fd, &rss->rbits); + break; + case B_SELECT_WRITE: + FD_SET(fd, &rss->wbits); + break; + case B_SELECT_EXCEPTION: + FD_SET(fd, &rss->ebits); + break; + }; + + // wakeup select() + release_sem(rss->wakeup); + + release_sem(rss->lock); + +error: + delete_area(area); +} + + +// #pragma mark - + + +#if STAY_LOADED +static status_t keep_driver_loaded() +{ + if ( g_stay_loaded_fd != -1 ) + return B_OK; + + /* force the driver to stay loaded by opening himself */ +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "keep_driver_loaded: internaly opening /dev/" NET_STACK_DRIVER_PATH " to stay loaded in memory...\n"); +#endif + + g_stay_loaded_fd = open("/dev/" NET_STACK_DRIVER_PATH, 0); + +#if SHOW_INSANE_DEBUGGING + if (g_stay_loaded_fd < 0) + dprintf(LOGID ERR "keep_driver_loaded: couldn't open(/dev/" NET_STACK_DRIVER_PATH")!\n"); +#endif + + return B_OK; +} + +static status_t unload_driver() +{ + if ( g_stay_loaded_fd >= 0 ) + { + int tmp_fd; + + /* we need to set g_stay_loaded_fd to < 0 if we don't want + * the next close enter again in this case, and so on... + */ +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "unload_driver: unload requested.\n"); +#endif + tmp_fd = g_stay_loaded_fd; + g_stay_loaded_fd = -1; + + close(tmp_fd); + }; + + return B_OK; +} +#endif /* STAY_LOADED */ + +#endif /* _KERNEL_MODE */ diff --git a/src/add-ons/kernel/drivers/network/stack/net_userstack_driver.c b/src/add-ons/kernel/drivers/network/stack/net_userstack_driver.c new file mode 100644 index 0000000000..32cb80005f --- /dev/null +++ b/src/add-ons/kernel/drivers/network/stack/net_userstack_driver.c @@ -0,0 +1,852 @@ +/* net_userstack_driver - This file implements a very simple socket driver +** that is intended to act as an interface to the networking stack when +** it is loaded in userland. +** The communication is slow, and could probably be much better, but it's +** working, and that should be enough for now. +** +** Initial version by Axel Dörfler, axeld@pinc-software.de +** This file may be used under the terms of the OpenBeOS License. +*/ + + +//#include +#include +#include + +#include +#include + +#include "net_stack_driver.h" +#include "userland_ipc.h" +#include "lock.h" +#include "sys/sockio.h" +#include "sys/socket.h" +#include "net/if.h" +#include "sys/select.h" + +/* these are missing from KernelExport.h ... */ +#define B_SELECT_READ 1 +#define B_SELECT_WRITE 2 +#define B_SELECT_EXCEPTION 3 + +extern void notify_select_event(selectsync * sync, uint32 ref); + +//*****************************************************/ +// Debug output +//*****************************************************/ + +#define DEBUG_PREFIX "net_driver: " +#define __out dprintf + +#define DEBUG 1 +#ifdef DEBUG + #define PRINT(x) { __out(DEBUG_PREFIX); __out x; } + #define REPORT_ERROR(status) __out(DEBUG_PREFIX "%s:%ld: %s\n",__FUNCTION__,__LINE__,strerror(status)); + #define RETURN_ERROR(err) { status_t _status = err; if (_status < B_OK) REPORT_ERROR(_status); return _status;} + #define FATAL(x) { __out(DEBUG_PREFIX); __out x; } + #define INFORM(x) { __out(DEBUG_PREFIX); __out x; } + #define FUNCTION() __out(DEBUG_PREFIX "%s()\n",__FUNCTION__); + #define FUNCTION_START(x) { __out(DEBUG_PREFIX "%s() ",__FUNCTION__); __out x; } +// #define FUNCTION() ; +// #define FUNCTION_START(x) ; + #define D(x) {x;}; +#else + #define PRINT(x) ; + #define REPORT_ERROR(status) ; + #define RETURN_ERROR(status) return status; + #define FATAL(x) { __out(DEBUG_PREFIX); __out x; } + #define INFORM(x) { __out(DEBUG_PREFIX); __out x; } + #define FUNCTION() ; + #define FUNCTION_START(x) ; + #define D(x) ; +#endif + + +//*****************************************************/ +// Structure definitions +//*****************************************************/ + +#ifndef DRIVER_NAME +# define DRIVER_NAME "net_stack_driver" +#endif + +/* wait one second when waiting on the stack */ +#define STACK_TIMEOUT 1000000LL + +typedef void (*notify_select_event_function)(selectsync * sync, uint32 ref); + +// this struct will store one select() event to monitor per thread +typedef struct selecter { + struct selecter * next; + thread_id thread; + int event; + selectsync * sync; + uint32 ref; +} selecter; + + +// the cookie we attach to each file descriptor opened on our driver entry +typedef struct { + port_id local_port; + port_id remote_port; + area_id area; + + sem_id command_sem; + net_command *commands; + int32 command_index; + int32 nb_commands; + + sem_id selecters_lock; // protect the selecters linked-list + selecter * selecters; // the select()'ers lists (thread-aware) +} net_stack_cookie; + + +//*****************************************************/ +// Prototypes +//*****************************************************/ + +/* device hooks */ +static status_t net_stack_open(const char * name, uint32 flags, void ** cookie); +static status_t net_stack_close(void * cookie); +static status_t net_stack_free_cookie(void * cookie); +static status_t net_stack_control(void * cookie, uint32 msg,void * data, size_t datalen); +static status_t net_stack_read(void * cookie, off_t pos, void * data, size_t * datalen); +static status_t net_stack_write(void * cookie, off_t pos, const void * data, size_t * datalen); +static status_t net_stack_select(void *cookie, uint8 event, uint32 ref, selectsync *sync); +static status_t net_stack_deselect(void *cookie, uint8 event, selectsync *sync); +// static status_t net_stack_readv(void * cookie, off_t pos, const iovec * vec, size_t count, size_t * len); +// static status_t net_stack_writev(void * cookie, off_t pos, const iovec * vec, size_t count, size_t * len); + +/* select() support */ +static int32 socket_event_listener(void * data); +static void on_socket_event(void *socket, uint32 event, void *cookie); +static void r5_notify_select_event(selectsync *sync, uint32 ref); + +/* command queue */ +static status_t init_connection(void **cookie); +static void shutdown_connection(net_stack_cookie *nsc); +static net_command *get_command(net_stack_cookie *nsc, int32 *index); +static status_t execute_command(net_stack_cookie *nsc, int32 op, void *data, uint32 length); + +/* + * Global variables + * ---------------- + */ + +const char * g_device_names_list[] = { + NET_STACK_DRIVER_PATH, + NULL +}; + +device_hooks g_net_stack_driver_hooks = +{ + net_stack_open, /* -> open entry point */ + net_stack_close, /* -> close entry point */ + net_stack_free_cookie, /* -> free entry point */ + net_stack_control, /* -> control entry point */ + net_stack_read, /* -> read entry point */ + net_stack_write, /* -> write entry point */ + net_stack_select, /* -> select entry point */ + net_stack_deselect, /* -> deselect entry point */ + NULL, /* -> readv entry pint */ + NULL /* -> writev entry point */ +}; + +// by default, assert we can use kernel select() support +notify_select_event_function g_nse = notify_select_event; +thread_id g_socket_event_listener_thread = -1; +port_id g_socket_event_listener_port = -1; + +port_id g_stack_port = -1; + + +/* + * Driver API calls + * ---------------- + */ + +_EXPORT int32 api_version = B_CUR_DRIVER_API_VERSION; + + +_EXPORT status_t +init_hardware(void) +{ + return B_OK; +} + + +_EXPORT status_t +init_driver (void) +{ + thread_id thread; + port_id port; + + FUNCTION(); + + port = create_port(32, "socket_event_listener"); + if (port < B_OK) + return port; + set_port_owner(port, B_SYSTEM_TEAM); + + thread = spawn_kernel_thread(socket_event_listener, "socket_event_listener", + B_NORMAL_PRIORITY, NULL); + if (thread < B_OK) { + delete_port(port); + return thread; + }; + + g_socket_event_listener_thread = thread; + g_socket_event_listener_port = port; + + return resume_thread(thread); +} + + +_EXPORT void +uninit_driver (void) +{ + status_t dummy; + + FUNCTION(); + + delete_port(g_socket_event_listener_port); + wait_for_thread(g_socket_event_listener_thread, &dummy); +} + + +_EXPORT const char ** +publish_devices() +{ + FUNCTION(); + return g_device_names_list; +} + + +_EXPORT device_hooks * +find_device(const char *deviceName) +{ + FUNCTION(); + return &g_net_stack_driver_hooks; +} + + +// #pragma mark - +//*****************************************************/ +// Device hooks +//*****************************************************/ + + +static status_t +net_stack_open(const char *name, uint32 flags, void ** cookie) +{ + net_stack_cookie * nsc; + + status_t status = init_connection(cookie); + if (status < B_OK) + return status; + + nsc = *cookie; + + status = execute_command(nsc, NET_STACK_OPEN, &flags, sizeof(uint32)); + if (status < B_OK) + net_stack_free_cookie(nsc); + + return status; +} + + +static status_t +net_stack_close(void *cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + if (nsc == NULL) + return B_BAD_VALUE; + + // we don't care here if the stack isn't alive anymore - + // from the kernel's point of view, the device can always + // be closed + execute_command(nsc, NET_STACK_CLOSE, NULL, 0); + return B_OK; +} + + +static status_t +net_stack_free_cookie(void *cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + if (nsc == NULL) + return B_BAD_VALUE; + + shutdown_connection(nsc); + return B_OK; +} + + +static status_t +net_stack_control(void *cookie, uint32 op, void *data, size_t length) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + + //FUNCTION_START(("cookie = %p, op = %lx, data = %p, length = %ld\n", cookie, op, data, length)); + + if (nsc == NULL) + return B_BAD_VALUE; + + switch (op) { + case NET_STACK_SELECT: { + struct select_args *args = (struct select_args *) data; + + // if we get this call via ioctl() we are obviously called from an + // R5 compatible libnet: we are using the r5 kernel select() call, + // So, we can't use the kernel notify_select_event(), but our own implementation! + g_nse = r5_notify_select_event; + return net_stack_select(cookie, (args->ref & 0x0F), args->ref, args->sync); + } + case NET_STACK_DESELECT: { + struct select_args * args = (struct select_args *) data; + return net_stack_deselect(cookie, (args->ref & 0x0F), args->sync); + } + default: + return execute_command(nsc, op, data, -1); + }; +} + + +static status_t +net_stack_read(void *cookie, off_t pos, void *buffer, size_t *length) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + struct data_xfer_args args; + int status; + + memset(&args, 0, sizeof(args)); + args.data = buffer; + args.datalen = *length; + + status = execute_command(nsc, NET_STACK_RECV, &args, sizeof(args)); + if (status > 0) { + *length = status; + return B_OK; + } + return status; +} + + +static status_t +net_stack_write(void *cookie, off_t pos, const void *buffer, size_t *length) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + struct data_xfer_args args; + int status; + + memset(&args, 0, sizeof(args)); + args.data = (void *) buffer; + args.datalen = *length; + + status = execute_command(nsc, NET_STACK_SEND, &args, sizeof(args)); + if (status > 0) { + *length = status; + return B_OK; + } + return status; +} + + +static status_t +net_stack_select(void *cookie, uint8 event, uint32 ref, selectsync *sync) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + struct notify_socket_event_args args; + selecter * s; + status_t status; + + FUNCTION_START(("cookie = %p, event = %d, ref = %lx, sync =%p\n", cookie, event, ref, sync)); + + s = (selecter *) malloc(sizeof(selecter)); + if (!s) + return B_NO_MEMORY; + + + // lock the selecters list + status = acquire_sem(nsc->selecters_lock); + if (status != B_OK) { + free(s); + return status; + }; + + s->thread = find_thread(NULL); // current thread id + s->event = event; + s->sync = sync; + s->ref = ref; + + // add it to selecters list + s->next = nsc->selecters; + nsc->selecters = s; + + // unlock the selecters list + release_sem(nsc->selecters_lock); + + // Tell the net_server to notify event(s) for this socket + memset(&args, 0, sizeof(args)); + args.notify_port = g_socket_event_listener_port; + args.cookie = cookie;; + + return execute_command(nsc, NET_STACK_NOTIFY_SOCKET_EVENT, &args, sizeof(args)); +} + + +static status_t +net_stack_deselect(void *cookie, uint8 event, selectsync *sync) +{ + net_stack_cookie *nsc = (net_stack_cookie *) cookie; + selecter * previous; + selecter * s; + thread_id current_thread; + status_t status; + + FUNCTION_START(("cookie = %p, event = %d, sync =%p\n", cookie, event, sync)); + + if (!nsc) + return B_BAD_VALUE; + + current_thread = find_thread(NULL); + + // lock the selecters list + status = acquire_sem(nsc->selecters_lock); + if (status != B_OK) + return status; + + previous = NULL; + s = nsc->selecters; + while (s) { + if (s->thread == current_thread && + s->event == event && s->sync == sync) + // selecter found! + break; + + previous = s; + s = s->next; + }; + + if (s != NULL) { + // remove it from selecters list + if (previous) + previous->next = s->next; + else + nsc->selecters = s->next; + free(s); + }; + + status = B_OK; + if (nsc->selecters == NULL) { + struct notify_socket_event_args args; + + // Selecters list is empty: no need to monitor socket events anymore + // Tell the net_server to stop notifying event(s) for this socket + memset(&args, 0, sizeof(args)); + args.notify_port = -1; // stop notifying + args.cookie = cookie; // for sanity check + status = execute_command(nsc, NET_STACK_NOTIFY_SOCKET_EVENT, NULL, -1); + }; + + // unlock the selecters list + release_sem(nsc->selecters_lock); + + return status; +} + + +// #pragma mark - +//*****************************************************/ +// select() support +//*****************************************************/ + +/* + This is started as separate thread at init_driver() time to wait for + socket event notification. + + One port to listen them all. + One port to find them. + One port to get them and in [r5_]notify_select_event() send them. + + Okay, I should stop watch this movie *now*. +*/ + +static int32 socket_event_listener(void * data) +{ + struct socket_event_data sed; + int32 msg; + ssize_t bytes; + + while(true) { + bytes = read_port(g_socket_event_listener_port, &msg, &sed, sizeof(sed)); + if (bytes < B_OK) + return bytes; + + if (msg == NET_STACK_SOCKET_EVENT_NOTIFICATION) + // yep, we pass a NULL "socket" pointer here, but don't worry, we don't use anyway + on_socket_event(NULL, sed.event, sed.cookie); + }; + return 0; +} + + +static void on_socket_event(void * socket, uint32 event, void * cookie) +{ + net_stack_cookie * nsc = (net_stack_cookie *) cookie; + selecter * s; + + if (!nsc) + return; + + FUNCTION_START(("socket = %p, event = %ld, cookie = %p\n", socket, event, cookie)); + + // lock the selecters list + if (acquire_sem(nsc->selecters_lock) != B_OK) + return; + + s = nsc->selecters; + while (s) { + if (s->event == event) + // notify this selecter (thread/event pair) + g_nse(s->sync, s->ref); + s = s->next; + }; + + // unlock the selecters list + release_sem(nsc->selecters_lock); + return; +} + + +/* + Under vanilla R5, we can't use the kernel notify_select_event(), + as select() kernel implementation is too buggy to be usefull. + So, here is our own notify_select_event() implementation, the driver-side pair + of the our libnet.so select() implementation... +*/ +static void r5_notify_select_event(selectsync * sync, uint32 ref) +{ + area_id area; + struct r5_selectsync * rss; + int fd; + + FUNCTION_START(("sync = %p, ref = %lx\n", sync, ref)); + + rss = NULL; + area = clone_area("r5_selectsync_area (driver)", (void **) &rss, + B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA, (area_id) sync); + if (area < B_OK) { +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "r5_notify_select_event: clone_area(%d) failed -> %d!\n", (area_id) sync, area); +#endif + return; + }; + +#if SHOW_INSANE_DEBUGGING + dprintf(LOGID "r5_selectsync at %p (area %ld, clone from %ld):\n" + "lock %ld\n" + "wakeup %ld\n", rss, area, (area_id) sync, rss->lock, rss->wakeup); +#endif + + if (acquire_sem(rss->lock) != B_OK) + // if we can't (anymore?) lock the shared r5_selectsync, select() party is done + goto error; + + fd = ref >> 8; + switch (ref & 0xFF) { + case B_SELECT_READ: + FD_SET(fd, &rss->rbits); + break; + case B_SELECT_WRITE: + FD_SET(fd, &rss->wbits); + break; + case B_SELECT_EXCEPTION: + FD_SET(fd, &rss->ebits); + break; + }; + + // wakeup select() + release_sem(rss->wakeup); + + release_sem(rss->lock); + +error: + delete_area(area); +} + + +// #pragma mark - + + +//*****************************************************/ +// The Command Queue +//*****************************************************/ + + +static net_command * +get_command(net_stack_cookie *nsc, int32 *index) +{ + int32 i, count = 0; + net_command *command; + + while (count < nsc->nb_commands*2) { + i = atomic_add(&nsc->command_index,1) & (nsc->nb_commands - 1); + command = nsc->commands + i; + + if (command->op == 0) { + // command is free to use + *index = i; + return command; + } + count++; + } + return NULL; +} + + +static status_t +get_area_from_address(net_area_info *info, void *data) +{ + area_info areaInfo; + + if (data == NULL) + return B_OK; + + info->id = area_for(data); + if (info->id < B_OK) + return info->id; + + if (get_area_info(info->id,&areaInfo) != B_OK) + return B_BAD_VALUE; + + info->offset = areaInfo.address; + return B_OK; +} + + +static void +set_command_areas(net_command *command) +{ + void *data = command->data; + if (data == NULL) + return; + + if (get_area_from_address(&command->area[0],data) < B_OK) + return; + + switch (command->op) { + case NET_STACK_GETSOCKOPT: + case NET_STACK_SETSOCKOPT: + { + struct sockopt_args *sockopt = (struct sockopt_args *)data; + + get_area_from_address(&command->area[1],sockopt->optval); + break; + } + + case NET_STACK_CONNECT: + case NET_STACK_BIND: + case NET_STACK_GETSOCKNAME: + case NET_STACK_GETPEERNAME: + { + struct sockaddr_args *args = (struct sockaddr_args *)data; + + get_area_from_address(&command->area[1],args->addr); + break; + } + + case NET_STACK_RECV: + case NET_STACK_SEND: + { + struct data_xfer_args *args = (struct data_xfer_args *)data; + get_area_from_address(&command->area[1],args->data); + get_area_from_address(&command->area[2],args->addr); + break; + } + case NET_STACK_RECVFROM: + case NET_STACK_SENDTO: + { + struct msghdr *mh = (struct msghdr *)data; + get_area_from_address(&command->area[1],mh->msg_name); + get_area_from_address(&command->area[2],mh->msg_iov); + get_area_from_address(&command->area[3],mh->msg_control); + break; + } + + case NET_STACK_ACCEPT: + { + struct accept_args *args = (struct accept_args *)data; + /* accept_args.cookie is always in the address space of the server */ + get_area_from_address(&command->area[1],args->addr); + break; + } + + case NET_STACK_SYSCTL: + { + struct sysctl_args *sysctl = (struct sysctl_args *)data; + + get_area_from_address(&command->area[1],sysctl->name); + get_area_from_address(&command->area[2],sysctl->oldp); + get_area_from_address(&command->area[3],sysctl->oldlenp); + get_area_from_address(&command->area[4],sysctl->newp); + break; + } + + case OSIOCGIFCONF: + case SIOCGIFCONF: + { + struct ifconf *ifc = (struct ifconf *)data; + + get_area_from_address(&command->area[1],ifc->ifc_buf); + break; + } + } +} + + +static status_t +execute_command(net_stack_cookie *nsc, int32 op, void *data, uint32 length) +{ + uint32 command_index; + net_command *command = get_command(nsc, (long *) &command_index); + int32 max_tries = 200; + status_t status; + ssize_t bytes; + + if (command == NULL) { + FATAL(("execute: command queue is full\n")); + return B_ERROR; + } + + memset(command, 0, sizeof(net_command)); + + command->op = op; + command->data = data; + set_command_areas(command); + + bytes = write_port(nsc->remote_port, command_index, NULL, 0); + if (bytes < B_OK) { + FATAL(("execute %ld: couldn't contact stack (id = %ld): %s\n",op, nsc->remote_port, strerror(bytes))); + return bytes; + } + + // if we're closing the connection, there is no need to + // wait for a result + if (op == NET_STACK_CLOSE) + return B_OK; + + while (true) { + // wait until we get the results back from our command + if ((status = acquire_sem(nsc->command_sem)) == B_OK) { + if (command->op != 0) { + if (--max_tries <= 0) { + FATAL(("command is not freed after 200 tries!\n")); + return B_ERROR; + } + release_sem(nsc->command_sem); + continue; + } + return command->result; + } + FATAL(("command couldn't be executed: %s\n", strerror(status))); + return status; + } +} + + +static status_t +init_connection(void **cookie) +{ + net_connection connection; + ssize_t bytes; + int32 msg; + + net_stack_cookie * nsc = (net_stack_cookie *) malloc(sizeof(net_stack_cookie)); + if (nsc == NULL) + return B_NO_MEMORY; + + // create a new port and get a connection from the stack + + nsc->local_port = create_port(CONNECTION_QUEUE_LENGTH, "net_driver connection"); + if (nsc->local_port < B_OK) { + FATAL(("open: couldn't create port: %s\n", strerror(nsc->local_port))); + free(cookie); + return bytes; + } + set_port_owner(nsc->local_port, B_SYSTEM_TEAM); + + bytes = write_port(g_stack_port, NET_STACK_NEW_CONNECTION, &nsc->local_port, sizeof(port_id)); + if (bytes == B_BAD_PORT_ID) { + g_stack_port = find_port(NET_STACK_PORTNAME); + PRINT(("try to get net_server's port id: %ld\n", g_stack_port)); + bytes = write_port(g_stack_port, NET_STACK_NEW_CONNECTION, &nsc->local_port, sizeof(port_id)); + } + if (bytes < B_OK) { + FATAL(("open: couldn't contact stack: %s\n",strerror(bytes))); + delete_port(nsc->local_port); + free(nsc); + return bytes; + } + + bytes = read_port_etc(nsc->local_port, &msg, &connection, sizeof(net_connection), B_TIMEOUT,STACK_TIMEOUT); + if (bytes < B_OK) { + FATAL(("open: didn't hear back from stack: %s\n",strerror(bytes))); + delete_port(nsc->local_port); + free(nsc); + return bytes; + } + if (msg != NET_STACK_NEW_CONNECTION) { + FATAL(("open: received wrong answer: %ld\n",msg)); + delete_port(nsc->local_port); + free(nsc); + return B_ERROR; + } + + // set up communication channels + + // ToDo: close server connection if anything fails + // -> could also be done by the server, if it doesn't receive NET_STACK_OPEN + + nsc->remote_port = connection.port; + nsc->command_sem = connection.commandSemaphore; + nsc->area = clone_area("net connection buffer", (void **) &nsc->commands, + B_CLONE_ADDRESS, B_READ_AREA | B_WRITE_AREA, connection.area); + if (nsc->area < B_OK) { + FATAL(("could clone command queue: %s\n", strerror(nsc->area))); + delete_port(nsc->local_port); + free(nsc); + return nsc->area; + } + + nsc->nb_commands = connection.numCommands; + nsc->command_index = 0; + + nsc->selecters_lock = create_sem(1, "socket_selecters_lock"); + nsc->selecters = NULL; + + *cookie = nsc; + return B_OK; +} + + +static void +shutdown_connection(net_stack_cookie *nsc) +{ + selecter * s; + + delete_area(nsc->area); + delete_port(nsc->local_port); + + // free the selecters list + delete_sem(nsc->selecters_lock); + s = nsc->selecters; + while (s) { + selecter * tmp = s; + s = s->next; + free(tmp); + }; + + free(nsc); +} +