root/sys/dev/misc/tbridge/tbridge.c
/*
 * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <machine/md_var.h>
#include <sys/ctype.h>
#include <sys/kthread.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <sys/module.h>
#include <libprop/proplib.h>
#include <sys/tbridge.h>

static cdev_t           tbridge_dev;
static d_open_t         tbridge_dev_open;
static d_close_t        tbridge_dev_close;
static d_ioctl_t        tbridge_dev_ioctl;


static struct dev_ops tbridge_dev_ops = {
        { "tbridge", 0, 0 },
        .d_open = tbridge_dev_open,
        .d_close = tbridge_dev_close,
        .d_ioctl = tbridge_dev_ioctl
};

static struct tbridge_testcase *tbridge_curtest = NULL;
static prop_dictionary_t tbridge_testcase = NULL;
static int tbridge_result_ready = 0;
static char tbridge_msgbuf[131072]; /* 128 kB message buffer */
static char *tbridge_msgbuf_ptr;
size_t tbridge_msgbuf_remsz;

static prop_dictionary_t
testcase_get_result_dict(prop_dictionary_t testcase)
{
        prop_dictionary_t result_dict;
        int r;

        result_dict = prop_dictionary_get(testcase, "result");
        if (result_dict == NULL) {
                result_dict = prop_dictionary_create();
                if (result_dict == NULL) {
                        kprintf("could not allocate new result dict");
                        return NULL;
                }

                r = prop_dictionary_set(testcase, "result", result_dict);
                if (r == 0) {
                        kprintf("prop_dictionary operation failed");
                        return NULL;
                }
        }

        return result_dict;
}

static int
testcase_get_timeout(prop_dictionary_t testcase)
{
        int32_t val;
        int r;

        r = prop_dictionary_get_int32(prop_dictionary_get(testcase, "opts"),
            "timeout_in_secs", &val);
        if (r == 0)
                val = 600/* default timeout = 10min */;
                
        return val;
}

static int
testcase_set_stdout_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "stdout_buf", buf);
}

static int
testcase_set_result(prop_dictionary_t testcase, int result)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_int32(dict, "result", result);
}

static const char *
testcase_get_name(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(testcase, "name", &str);
        if (r == 0)
                str = "???";

        return str;
}

int
tbridge_printf(const char *fmt, ...)
{
        __va_list args;
        int i;

        __va_start(args,fmt);
        i = kvsnprintf(tbridge_msgbuf_ptr, tbridge_msgbuf_remsz, fmt, args);
        __va_end(args);

        tbridge_msgbuf_ptr += i;
        tbridge_msgbuf_remsz -= i;

        return i;
}

static int
tbridge_register(struct tbridge_testcase *test)
{
        if (tbridge_curtest != NULL)
                return EBUSY;

        tbridge_curtest = test;

        return 0;
}

static int
tbridge_unregister(void)
{
        tbridge_curtest = NULL;

        return 0;
}

int
tbridge_testcase_modhandler(module_t mod, int type, void *arg)
{
        struct tbridge_testcase *tcase = (struct tbridge_testcase *)arg;
        int error = 0;

        switch (type) {
        case MOD_LOAD:
                error = tbridge_register(tcase);
                break;

        case MOD_UNLOAD:
                if (tcase->tb_abort != NULL)
                        error = tcase->tb_abort();

                if (!error)
                        tbridge_unregister();
                break;

        default:
                break;
        }

        return error;
}

void
tbridge_test_done(int result)
{
        KKASSERT(tbridge_testcase != NULL);

        kprintf("testcase '%s' called test_done with result=%d\n",
            testcase_get_name(tbridge_testcase), result);

        testcase_set_result(tbridge_testcase, result);
        testcase_set_stdout_buf(tbridge_testcase, tbridge_msgbuf);

        tbridge_result_ready = 1;
        wakeup(&tbridge_result_ready);
}

static void
tbridge_reset(void)
{
        tbridge_msgbuf_ptr = tbridge_msgbuf;
        tbridge_msgbuf_remsz = sizeof(tbridge_msgbuf);

        if (tbridge_testcase != NULL) {
                prop_object_release(tbridge_testcase);
                tbridge_testcase = NULL;
        }

        safemem_reset_error_count();
        tbridge_result_ready = 0;
}


/*
 * dev stuff
 */
static int
tbridge_dev_open(struct dev_open_args *ap)
{
        return 0;
}

static int
tbridge_dev_close(struct dev_close_args *ap)
{
        return 0;
}

static int
tbridge_dev_ioctl(struct dev_ioctl_args *ap)
{
        struct plistref *pref;
        struct thread *td;
        int error, r;

        error = 0;

        /* Use proplib(3) for userspace/kernel communication */
        pref = (struct plistref *)ap->a_data;

        switch(ap->a_cmd) {
        case TBRIDGE_LOADTEST:
                tbridge_reset();

                if (tbridge_curtest == NULL)
                        return EINVAL;

                error = prop_dictionary_copyin_ioctl(pref, ap->a_cmd,
                    &tbridge_testcase);
                if (error)
                        return error;

                break;

        case TBRIDGE_GETRESULT:
                error = kthread_create(tbridge_curtest->tb_run, NULL, &td,
                    "testrunner");
                if (error)
                        tbridge_test_done(RESULT_PREFAIL);

                /* The following won't be called if the thread wasn't created */
                if (!tbridge_result_ready) {
                        r = tsleep(&tbridge_result_ready, 0, "tbridgeres",
                            hz * testcase_get_timeout(tbridge_testcase));
                        if (r != 0) {
                                if (tbridge_curtest->tb_abort != NULL)
                                        tbridge_curtest->tb_abort();

                                tbridge_test_done(RESULT_TIMEOUT);
                        }
                }

                if (safemem_get_error_count() != 0) {
                        /*
                         * If there were any double-frees or buffer over- or
                         * underflows, we mark the result as 'signalled', i.e.
                         * the equivalent of a SIGSEGV/SIGBUS in userland.
                         */
                        testcase_set_result(tbridge_testcase, RESULT_SIGNALLED);
                }

                error = prop_dictionary_copyout_ioctl(pref, ap->a_cmd,
                    tbridge_testcase);
                if (error)
                        return error;

                break;
        default:
                error = ENOTTY; /* Inappropriate ioctl for device */
                break;
        }

        return(error);
}


static int
testbridge_modevent(module_t mod, int type, void *unused)
{
        switch (type) {
        case MOD_LOAD:
                tbridge_dev = make_dev(&tbridge_dev_ops,
                    0,
                    UID_ROOT,
                    GID_WHEEL,
                    0600,
                    "tbridge");

                tbridge_testcase = NULL;
                tbridge_reset();

                kprintf("dfregress kernel test bridge ready!\n");
                return 0;

        case MOD_UNLOAD:
                destroy_dev(tbridge_dev);
                kprintf("dfregress kernel test bridge unloaded.\n");
                return 0;
        }

        return EINVAL;
}

static moduledata_t testbridge_mod = {
        "testbridge",
        testbridge_modevent,
        0
};

DECLARE_MODULE(testbridge, testbridge_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(testbridge, 1);