Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*-
 * Copyright (c) 2015
 *	Netflix Incorporated, 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 REGENTS 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 REGENTS 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/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sdt.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>
#include <sys/queue.h>
#include <tests/kern_testfrwk.h>
#ifdef SMP
#include <machine/cpu.h>
#endif

struct kern_test_list {
	TAILQ_ENTRY(kern_test_list) next;
	char name[TEST_NAME_LEN];
	kerntfunc func;
};

TAILQ_HEAD(ktestlist, kern_test_list);

struct kern_test_entry {
	TAILQ_ENTRY(kern_test_entry) next;
	struct kern_test_list *kt_e;
	struct kern_test kt_data;
};

TAILQ_HEAD(ktestqueue, kern_test_entry);

MALLOC_DEFINE(M_KTFRWK, "kern_tfrwk", "Kernel Test Framework");
struct kern_totfrwk {
	struct taskqueue *kfrwk_tq;
	struct task kfrwk_que;
	struct ktestlist kfrwk_testlist;
	struct ktestqueue kfrwk_testq;
	struct mtx kfrwk_mtx;
	int kfrwk_waiting;
};

struct kern_totfrwk kfrwk;
static int ktest_frwk_inited = 0;

#define KTFRWK_MUTEX_INIT() mtx_init(&kfrwk.kfrwk_mtx, "kern_test_frwk", "tfrwk", MTX_DEF)

#define KTFRWK_DESTROY() mtx_destroy(&kfrwk.kfrwk_mtx)

#define KTFRWK_LOCK() mtx_lock(&kfrwk.kfrwk_mtx)

#define KTFRWK_UNLOCK()	mtx_unlock(&kfrwk.kfrwk_mtx)

static void
kfrwk_task(void *context, int pending)
{
	struct kern_totfrwk *tf;
	struct kern_test_entry *wk;
	int free_mem = 0;
	struct kern_test kt_data;
	kerntfunc ktf;

	memset(&kt_data, 0, sizeof(kt_data));
	ktf = NULL;
	tf = (struct kern_totfrwk *)context;
	KTFRWK_LOCK();
	wk = TAILQ_FIRST(&tf->kfrwk_testq);
	if (wk) {
		wk->kt_data.tot_threads_running--;
		tf->kfrwk_waiting--;
		memcpy(&kt_data, &wk->kt_data, sizeof(kt_data));
		if (wk->kt_data.tot_threads_running == 0) {
			TAILQ_REMOVE(&tf->kfrwk_testq, wk, next);
			free_mem = 1;
		} else {
			/* Wake one of my colleages up to help too */
			taskqueue_enqueue(tf->kfrwk_tq, &tf->kfrwk_que);
		}
		if (wk->kt_e) {
			ktf = wk->kt_e->func;
		}
	}
	KTFRWK_UNLOCK();
	if (wk && free_mem) {
		free(wk, M_KTFRWK);
	}
	/* Execute the test */
	if (ktf) {
		(*ktf) (&kt_data);
	}
	/* We are done */
	atomic_add_int(&tf->kfrwk_waiting, 1);
}

static int
kerntest_frwk_init(void)
{
	u_int ncpus = mp_ncpus ? mp_ncpus : MAXCPU;

	KTFRWK_MUTEX_INIT();
	TAILQ_INIT(&kfrwk.kfrwk_testq);
	TAILQ_INIT(&kfrwk.kfrwk_testlist);
	/* Now lets start up a number of tasks to do the work */
	TASK_INIT(&kfrwk.kfrwk_que, 0, kfrwk_task, &kfrwk);
	kfrwk.kfrwk_tq = taskqueue_create_fast("sbtls_task", M_NOWAIT,
	    taskqueue_thread_enqueue, &kfrwk.kfrwk_tq);
	if (kfrwk.kfrwk_tq == NULL) {
		printf("Can't start taskqueue for Kernel Test Framework\n");
		panic("Taskqueue init fails for kfrwk");
	}
	taskqueue_start_threads(&kfrwk.kfrwk_tq, ncpus, PI_NET, "[kt_frwk task]");
	kfrwk.kfrwk_waiting = ncpus;
	ktest_frwk_inited = 1;
	return (0);
}

static int
kerntest_frwk_fini(void)
{
	KTFRWK_LOCK();
	if (!TAILQ_EMPTY(&kfrwk.kfrwk_testlist)) {
		/* Still modules registered */
		KTFRWK_UNLOCK();
		return (EBUSY);
	}
	ktest_frwk_inited = 0;
	KTFRWK_UNLOCK();
	taskqueue_free(kfrwk.kfrwk_tq);
	/* Ok lets destroy the mutex on the way outs */
	KTFRWK_DESTROY();
	return (0);
}


static int kerntest_execute(SYSCTL_HANDLER_ARGS);

SYSCTL_NODE(_kern, OID_AUTO, testfrwk, CTLFLAG_RW, 0, "Kernel Test Framework");
SYSCTL_PROC(_kern_testfrwk, OID_AUTO, runtest, (CTLTYPE_STRUCT | CTLFLAG_RW),
    0, 0, kerntest_execute, "IU", "Execute a kernel test");

int
kerntest_execute(SYSCTL_HANDLER_ARGS)
{
	struct kern_test kt;
	struct kern_test_list *li, *te = NULL;
	struct kern_test_entry *kte = NULL;
	int error = 0;

	if (ktest_frwk_inited == 0) {
		return (ENOENT);
	}
	/* Find the entry if possible */
	error = SYSCTL_IN(req, &kt, sizeof(struct kern_test));
	if (error) {
		return (error);
	}
	if (kt.num_threads <= 0) {
		return (EINVAL);
	}
	/* Grab some memory */
	kte = malloc(sizeof(struct kern_test_entry), M_KTFRWK, M_WAITOK);
	if (kte == NULL) {
		error = ENOMEM;
		goto out;
	}
	KTFRWK_LOCK();
	TAILQ_FOREACH(li, &kfrwk.kfrwk_testlist, next) {
		if (strcmp(li->name, kt.name) == 0) {
			te = li;
			break;
		}
	}
	if (te == NULL) {
		printf("Can't find the test %s\n", kt.name);
		error = ENOENT;
		free(kte, M_KTFRWK);
		goto out;
	}
	/* Ok we have a test item to run, can we? */
	if (!TAILQ_EMPTY(&kfrwk.kfrwk_testq)) {
		/* We don't know if there is enough threads */
		error = EAGAIN;
		free(kte, M_KTFRWK);
		goto out;
	}
	if (kfrwk.kfrwk_waiting < kt.num_threads) {
		error = E2BIG;
		free(kte, M_KTFRWK);
		goto out;
	}
	kt.tot_threads_running = kt.num_threads;
	/* Ok it looks like we can do it, lets get an entry */
	kte->kt_e = li;
	memcpy(&kte->kt_data, &kt, sizeof(kt));
	TAILQ_INSERT_TAIL(&kfrwk.kfrwk_testq, kte, next);
	taskqueue_enqueue(kfrwk.kfrwk_tq, &kfrwk.kfrwk_que);
out:
	KTFRWK_UNLOCK();
	return (error);
}

int
kern_testframework_register(const char *name, kerntfunc func)
{
	int error = 0;
	struct kern_test_list *li, *te = NULL;
	int len;

	len = strlen(name);
	if (len >= TEST_NAME_LEN) {
		return (E2BIG);
	}
	te = malloc(sizeof(struct kern_test_list), M_KTFRWK, M_WAITOK);
	if (te == NULL) {
		error = ENOMEM;
		goto out;
	}
	KTFRWK_LOCK();
	/* First does it already exist? */
	TAILQ_FOREACH(li, &kfrwk.kfrwk_testlist, next) {
		if (strcmp(li->name, name) == 0) {
			error = EALREADY;
			free(te, M_KTFRWK);
			goto out;
		}
	}
	/* Ok we can do it, lets add it to the list */
	te->func = func;
	strcpy(te->name, name);
	TAILQ_INSERT_TAIL(&kfrwk.kfrwk_testlist, te, next);
out:
	KTFRWK_UNLOCK();
	return (error);
}

int
kern_testframework_deregister(const char *name)
{
	struct kern_test_list *li, *te = NULL;
	u_int ncpus = mp_ncpus ? mp_ncpus : MAXCPU;
	int error = 0;

	KTFRWK_LOCK();
	/* First does it already exist? */
	TAILQ_FOREACH(li, &kfrwk.kfrwk_testlist, next) {
		if (strcmp(li->name, name) == 0) {
			te = li;
			break;
		}
	}
	if (te == NULL) {
		/* It is not registered so no problem */
		goto out;
	}
	if (ncpus != kfrwk.kfrwk_waiting) {
		/* We are busy executing something -- can't unload */
		error = EBUSY;
		goto out;
	}
	if (!TAILQ_EMPTY(&kfrwk.kfrwk_testq)) {
		/* Something still to execute */
		error = EBUSY;
		goto out;
	}
	/* Ok we can remove the dude safely */
	TAILQ_REMOVE(&kfrwk.kfrwk_testlist, te, next);
	memset(te, 0, sizeof(struct kern_test_list));
	free(te, M_KTFRWK);
out:
	KTFRWK_UNLOCK();
	return (error);
}

static int
kerntest_mod_init(module_t mod, int type, void *data)
{
	int err;

	switch (type) {
	case MOD_LOAD:
		err = kerntest_frwk_init();
		break;
	case MOD_QUIESCE:
		KTFRWK_LOCK();
		if (TAILQ_EMPTY(&kfrwk.kfrwk_testlist)) {
			err = 0;
		} else {
			err = EBUSY;
		}
		KTFRWK_UNLOCK();
		break;
	case MOD_UNLOAD:
		err = kerntest_frwk_fini();
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (err);
}

static moduledata_t kern_test_framework = {
	.name = "kernel_testfrwk",
	.evhand = kerntest_mod_init,
	.priv = 0
};

MODULE_VERSION(kern_testframework, 1);
DECLARE_MODULE(kern_testframework, kern_test_framework, SI_SUB_PSEUDO, SI_ORDER_ANY);