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 Inc. 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 AUTHOR 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 AUTHOR 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/conf.h>
#include <sys/cpuctl.h>
#include <sys/fcntl.h>
#include <sys/ioccom.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/pcpu.h>
#include <sys/pmckern.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <tests/kern_testfrwk.h>
#include <tests/callout_test.h>
#include <machine/cpu.h>

MALLOC_DEFINE(M_CALLTMP, "Temp callout Memory", "CalloutTest");

struct callout_run {
	struct mtx lock;
	struct callout *co_array;
	int co_test;
	int co_number_callouts;
	int co_return_npa;
	int co_completed;
	int callout_waiting;
	int drain_calls;
	int cnt_zero;
	int cnt_one;
	int index;
};

static struct callout_run *comaster[MAXCPU];

uint64_t callout_total = 0;

static void execute_the_co_test(struct callout_run *rn);

static void
co_saydone(void *arg)
{
	struct callout_run *rn;

	rn = (struct callout_run *)arg;
	printf("The callout test is now complete for thread %d\n",
	    rn->index);
	printf("number_callouts:%d\n",
	    rn->co_number_callouts);
	printf("Callouts that bailed (Not PENDING or ACTIVE cleared):%d\n",
	    rn->co_return_npa);
	printf("Callouts that completed:%d\n", rn->co_completed);
	printf("Drain calls:%d\n", rn->drain_calls);
	printf("Zero returns:%d non-zero:%d\n",
	    rn->cnt_zero,
	    rn->cnt_one);

}

static void
drainit(void *arg)
{
	struct callout_run *rn;

	rn = (struct callout_run *)arg;
	mtx_lock(&rn->lock);
	rn->drain_calls++;
	mtx_unlock(&rn->lock);
}

static void
test_callout(void *arg)
{
	struct callout_run *rn;
	int cpu;

	critical_enter();
	cpu = curcpu;
	critical_exit();
	rn = (struct callout_run *)arg;
	atomic_add_int(&rn->callout_waiting, 1);
	mtx_lock(&rn->lock);
	if (callout_pending(&rn->co_array[cpu]) ||
	    !callout_active(&rn->co_array[cpu])) {
		rn->co_return_npa++;
		atomic_subtract_int(&rn->callout_waiting, 1);
		mtx_unlock(&rn->lock);
		return;
	}
	callout_deactivate(&rn->co_array[cpu]);
	rn->co_completed++;
	mtx_unlock(&rn->lock);
	atomic_subtract_int(&rn->callout_waiting, 1);
}

void
execute_the_co_test(struct callout_run *rn)
{
	int i, ret, cpu;
	uint32_t tk_s, tk_e, tk_d;

	mtx_lock(&rn->lock);
	rn->callout_waiting = 0;
	for (i = 0; i < rn->co_number_callouts; i++) {
		if (rn->co_test == 1) {
			/* start all on spread out cpu's */
			cpu = i % mp_ncpus;
			callout_reset_sbt_on(&rn->co_array[i], 3, 0, test_callout, rn,
			    cpu, 0);
		} else {
			/* Start all on the same CPU */
			callout_reset_sbt_on(&rn->co_array[i], 3, 0, test_callout, rn,
			    rn->index, 0);
		}
	}
	tk_s = ticks;
	while (rn->callout_waiting != rn->co_number_callouts) {
		cpu_spinwait();
		tk_e = ticks;
		tk_d = tk_e - tk_s;
		if (tk_d > 100) {
			break;
		}
	}
	/* OK everyone is waiting and we have the lock */
	for (i = 0; i < rn->co_number_callouts; i++) {
		ret = callout_async_drain(&rn->co_array[i], drainit);
		if (ret) {
			rn->cnt_one++;
		} else {
			rn->cnt_zero++;
		}
	}
	rn->callout_waiting -= rn->cnt_one;
	mtx_unlock(&rn->lock);
	/* Now wait until all are done */
	tk_s = ticks;
	while (rn->callout_waiting > 0) {
		cpu_spinwait();
		tk_e = ticks;
		tk_d = tk_e - tk_s;
		if (tk_d > 100) {
			break;
		}
	}
	co_saydone((void *)rn);
}


static void
run_callout_test(struct kern_test *test)
{
	struct callout_test *u;
	size_t sz;
	int i;
	struct callout_run *rn;
	int index = test->tot_threads_running;

	u = (struct callout_test *)test->test_options;
	if (comaster[index] == NULL) {
		rn = comaster[index] = malloc(sizeof(struct callout_run), M_CALLTMP, M_WAITOK);
		memset(comaster[index], 0, sizeof(struct callout_run));
		mtx_init(&rn->lock, "callouttest", NULL, MTX_DUPOK);
		rn->index = index;
	} else {
		rn = comaster[index];
		rn->co_number_callouts = rn->co_return_npa = 0;
		rn->co_completed = rn->callout_waiting = 0;
		rn->drain_calls = rn->cnt_zero = rn->cnt_one = 0;
		if (rn->co_array) {
			free(rn->co_array, M_CALLTMP);
			rn->co_array = NULL;
		}
	}
	rn->co_number_callouts = u->number_of_callouts;
	rn->co_test = u->test_number;
	sz = sizeof(struct callout) * rn->co_number_callouts;
	rn->co_array = malloc(sz, M_CALLTMP, M_WAITOK);
	for (i = 0; i < rn->co_number_callouts; i++) {
		callout_init(&rn->co_array[i], CALLOUT_MPSAFE);
	}
	execute_the_co_test(rn);
}

int callout_test_is_loaded = 0;

static void
cocleanup(void)
{
	int i;

	for (i = 0; i < MAXCPU; i++) {
		if (comaster[i]) {
			if (comaster[i]->co_array) {
				free(comaster[i]->co_array, M_CALLTMP);
				comaster[i]->co_array = NULL;
			}
			free(comaster[i], M_CALLTMP);
			comaster[i] = NULL;
		}
	}
}

static int
callout_test_modevent(module_t mod, int type, void *data)
{
	int err = 0;

	switch (type) {
	case MOD_LOAD:
		err = kern_testframework_register("callout_test",
		    run_callout_test);
		if (err) {
			printf("Can't load callout_test err:%d returned\n",
			    err);
		} else {
			memset(comaster, 0, sizeof(comaster));
			callout_test_is_loaded = 1;
		}
		break;
	case MOD_QUIESCE:
		err = kern_testframework_deregister("callout_test");
		if (err == 0) {
			callout_test_is_loaded = 0;
			cocleanup();
		}
		break;
	case MOD_UNLOAD:
		if (callout_test_is_loaded) {
			err = kern_testframework_deregister("callout_test");
			if (err == 0) {
				cocleanup();
				callout_test_is_loaded = 0;
			}
		}
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (err);
}

static moduledata_t callout_test_mod = {
	.name = "callout_test",
	.evhand = callout_test_modevent,
	.priv = 0
};

MODULE_DEPEND(callout_test, kern_testframework, 1, 1, 1);
DECLARE_MODULE(callout_test, callout_test_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);