1/*-
2 * Copyright (c) 2018, Matthew Macy <mmacy@freebsd.org>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 *  1. Redistributions of source code must retain the above copyright notice,
8 *     this list of conditions and the following disclaimer.
9 *
10 *  2. Neither the name of Matthew Macy nor the names of its
11 *     contributors may be used to endorse or promote products derived from
12 *     this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/param.h>
28#include <sys/types.h>
29#include <sys/proc.h>
30#include <sys/counter.h>
31#include <sys/epoch.h>
32#include <sys/gtaskqueue.h>
33#include <sys/kernel.h>
34#include <sys/kthread.h>
35#include <sys/lock.h>
36#include <sys/malloc.h>
37#include <sys/module.h>
38#include <sys/mutex.h>
39#include <sys/proc.h>
40#include <sys/sched.h>
41#include <sys/smp.h>
42#include <sys/sysctl.h>
43#include <sys/systm.h>
44
45
46struct epoch_test_instance {
47	int threadid;
48};
49
50static int inited;
51static int iterations;
52#define ET_EXITING 0x1
53static volatile int state_flags;
54static struct mtx state_mtx __aligned(CACHE_LINE_SIZE*2);
55MTX_SYSINIT(state_mtx, &state_mtx, "epoch state mutex", MTX_DEF);
56static struct mtx mutexA __aligned(CACHE_LINE_SIZE*2);
57MTX_SYSINIT(mutexA, &mutexA, "epoch mutexA", MTX_DEF);
58static struct mtx mutexB __aligned(CACHE_LINE_SIZE*2);
59MTX_SYSINIT(mutexB, &mutexB, "epoch mutexB", MTX_DEF);
60epoch_t test_epoch;
61
62static void
63epoch_testcase1(struct epoch_test_instance *eti)
64{
65	int i, startticks;
66	struct mtx *mtxp;
67	struct epoch_tracker et;
68
69	startticks = ticks;
70	i = 0;
71	if (eti->threadid & 0x1)
72		mtxp = &mutexA;
73	else
74		mtxp = &mutexB;
75
76	while (i < iterations) {
77		epoch_enter_preempt(test_epoch, &et);
78		mtx_lock(mtxp);
79		i++;
80		mtx_unlock(mtxp);
81		epoch_exit_preempt(test_epoch, &et);
82		epoch_wait_preempt(test_epoch);
83	}
84	printf("test1: thread: %d took %d ticks to complete %d iterations\n",
85		   eti->threadid, ticks - startticks, iterations);
86}
87
88static void
89epoch_testcase2(struct epoch_test_instance *eti)
90{
91	int i, startticks;
92	struct mtx *mtxp;
93	struct epoch_tracker et;
94
95	startticks = ticks;
96	i = 0;
97	mtxp = &mutexA;
98
99	while (i < iterations) {
100		epoch_enter_preempt(test_epoch, &et);
101		mtx_lock(mtxp);
102		DELAY(1);
103		i++;
104		mtx_unlock(mtxp);
105		epoch_exit_preempt(test_epoch, &et);
106		epoch_wait_preempt(test_epoch);
107	}
108	printf("test2: thread: %d took %d ticks to complete %d iterations\n",
109		   eti->threadid, ticks - startticks, iterations);
110}
111
112static void
113testloop(void *arg) {
114
115	mtx_lock(&state_mtx);
116	while ((state_flags & ET_EXITING) == 0) {
117		msleep(&state_mtx, &state_mtx, 0, "epoch start wait", 0);
118		if (state_flags & ET_EXITING)
119			goto out;
120		mtx_unlock(&state_mtx);
121		epoch_testcase2(arg);
122		pause("W", 500);
123		epoch_testcase1(arg);
124		mtx_lock(&state_mtx);
125	}
126 out:
127	mtx_unlock(&state_mtx);
128	kthread_exit();
129}
130
131static struct thread *testthreads[MAXCPU];
132static struct epoch_test_instance etilist[MAXCPU];
133
134static int
135test_modinit(void)
136{
137	struct thread *td;
138	int i, error, pri_range, pri_off;
139
140	pri_range = PRI_MIN_TIMESHARE - PRI_MIN_REALTIME;
141	test_epoch = epoch_alloc("test_epoch", EPOCH_PREEMPT);
142	for (i = 0; i < mp_ncpus*2; i++) {
143		etilist[i].threadid = i;
144		error = kthread_add(testloop, &etilist[i], NULL, &testthreads[i],
145							0, 0, "epoch_test_%d", i);
146		if (error) {
147			printf("%s: kthread_add(epoch_test): error %d", __func__,
148				   error);
149		} else {
150			pri_off = (i*4)%pri_range;
151			td = testthreads[i];
152			thread_lock(td);
153			sched_prio(td, PRI_MIN_REALTIME + pri_off);
154			thread_unlock(td);
155		}
156	}
157	inited = 1;
158	return (0);
159}
160
161static int
162epochtest_execute(SYSCTL_HANDLER_ARGS)
163{
164	int error, v;
165
166	if (inited == 0)
167		return (ENOENT);
168
169	v = 0;
170	error = sysctl_handle_int(oidp, &v, 0, req);
171	if (error)
172		return (error);
173	if (req->newptr == NULL)
174		return (error);
175	if (v == 0)
176		return (0);
177	mtx_lock(&state_mtx);
178	iterations = v;
179	wakeup(&state_mtx);
180	mtx_unlock(&state_mtx);
181
182	return (0);
183}
184
185SYSCTL_NODE(_kern, OID_AUTO, epochtest, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
186    "Epoch Test Framework");
187SYSCTL_PROC(_kern_epochtest, OID_AUTO, runtest,
188    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE,
189    0, 0, epochtest_execute, "I",
190    "Execute an epoch test");
191
192static int
193epoch_test_module_event_handler(module_t mod, int what, void *arg __unused)
194{
195	int err;
196
197	switch (what) {
198	case MOD_LOAD:
199		if ((err = test_modinit()) != 0)
200			return (err);
201		break;
202	case MOD_UNLOAD:
203		mtx_lock(&state_mtx);
204		state_flags = ET_EXITING;
205		wakeup(&state_mtx);
206		mtx_unlock(&state_mtx);
207		/* yes --- gross */
208		pause("epoch unload", 3*hz);
209		epoch_free(test_epoch);
210		break;
211	default:
212		return (EOPNOTSUPP);
213	}
214
215	return (0);
216}
217
218static moduledata_t epoch_test_moduledata = {
219	"epoch_test",
220	epoch_test_module_event_handler,
221	NULL
222};
223
224MODULE_VERSION(epoch_test, 1);
225DECLARE_MODULE(epoch_test, epoch_test_moduledata, SI_SUB_PSEUDO, SI_ORDER_ANY);
226