| /* | 
 |  * This file is part of the UCB release of Plan 9. It is subject to the license | 
 |  * terms in the LICENSE file found in the top-level directory of this | 
 |  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No | 
 |  * part of the UCB release of Plan 9, including this file, may be copied, | 
 |  * modified, propagated, or distributed except according to the terms contained | 
 |  * in the LICENSE file. | 
 |  */ | 
 |  | 
 | // regression device. | 
 | // Currently, has only one file, monitor, which is used to send | 
 | // commands to the monitor. | 
 | // TODO: read them back :-) | 
 |  | 
 | #include <slab.h> | 
 | #include <kmalloc.h> | 
 | #include <kref.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <error.h> | 
 | #include <cpio.h> | 
 | #include <pmap.h> | 
 | #include <smp.h> | 
 | #include <net/ip.h> | 
 | #include <monitor.h> | 
 | #include <ktest.h> | 
 |  | 
 | struct dev regressdevtab; | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return regressdevtab.name; | 
 | } | 
 |  | 
 | struct regress | 
 | { | 
 | 	spinlock_t lock; | 
 | 	struct queue *monitor; | 
 | }; | 
 | struct regress regress; | 
 |  | 
 | enum{ | 
 | 	Monitordirqid = 0, | 
 | 	Monitordataqid, | 
 | 	Monitorctlqid, | 
 | }; | 
 |  | 
 | struct dirtab regresstab[]={ | 
 | 	{".",		{Monitordirqid, 0, QTDIR},	0,	DMDIR|0550}, | 
 | 	{"mondata",	{Monitordataqid},		0,	0600}, | 
 | 	{"monctl",	{Monitorctlqid},		0,	0600}, | 
 | }; | 
 |  | 
 | static char *ctlcommands = "ktest"; | 
 |  | 
 | static struct chan *regressattach(char *spec) | 
 | { | 
 | 	uint32_t n; | 
 |  | 
 | 	regress.monitor = qopen(2 << 20, 0, 0, 0); | 
 | 	if (! regress.monitor) { | 
 | 		printk("monitor allocate failed. No monitor output\n"); | 
 | 	} | 
 | 	return devattach(devname(), spec); | 
 | } | 
 |  | 
 | static void regressinit(void) | 
 | { | 
 | } | 
 |  | 
 | static struct walkqid *regresswalk(struct chan *c, struct chan *nc, char **name, | 
 |                                    unsigned int nname) | 
 | { | 
 | 	return devwalk(c, nc, name, nname, regresstab, ARRAY_SIZE(regresstab), | 
 | 	               devgen); | 
 | } | 
 |  | 
 | static size_t regressstat(struct chan *c, uint8_t *db, size_t n) | 
 | { | 
 | 	if (regress.monitor) | 
 | 		regresstab[Monitordataqid].length = qlen(regress.monitor); | 
 | 	else | 
 | 		regresstab[Monitordataqid].length = 0; | 
 |  | 
 | 	return devstat(c, db, n, regresstab, ARRAY_SIZE(regresstab), devgen); | 
 | } | 
 |  | 
 | static struct chan *regressopen(struct chan *c, int omode) | 
 | { | 
 | 	if (c->qid.type & QTDIR) { | 
 | 		if (openmode(omode) != O_READ) | 
 | 			error(EPERM, ERROR_FIXME); | 
 | 	} | 
 | 	c->mode = openmode(omode); | 
 | 	c->flag |= COPEN; | 
 | 	c->offset = 0; | 
 | 	return c; | 
 | } | 
 |  | 
 | static void regressclose(struct chan *unused) | 
 | { | 
 | } | 
 |  | 
 | static size_t regressread(struct chan *c, void *va, size_t n, off64_t off) | 
 | { | 
 | 	uint64_t w, *bp; | 
 | 	char *a, *ea; | 
 | 	uintptr_t offset = off; | 
 | 	uint64_t pc; | 
 | 	int snp_ret, ret = 0; | 
 |  | 
 | 	switch((int)c->qid.path){ | 
 | 	case Monitordirqid: | 
 | 		n = devdirread(c, va, n, regresstab, ARRAY_SIZE(regresstab), | 
 | 			       devgen); | 
 | 		break; | 
 |  | 
 | 	case Monitorctlqid: | 
 | 		n = readstr(off, va, n, ctlcommands); | 
 | 		break; | 
 |  | 
 | 	case Monitordataqid: | 
 | 		if (regress.monitor) { | 
 | 			printd("monitordataqid: regress.monitor %p len %p\n", | 
 | 			       regress.monitor, qlen(kprof.monitor)); | 
 | 			if (qlen(regress.monitor) > 0) | 
 | 				n = qread(regress.monitor, va, n); | 
 | 			else | 
 | 				n = 0; | 
 | 		} else | 
 | 			error(EFAIL, "no monitor queue"); | 
 | 		break; | 
 | 	default: | 
 | 		n = 0; | 
 | 		break; | 
 | 	} | 
 | 	return n; | 
 | } | 
 |  | 
 | int __tlb_bench_x; | 
 |  | 
 | static void __tlb_s(void) | 
 | { | 
 | 	tlbflush(); | 
 | 	cmb();	/* tlbflush is asm volatile, but it can still be reordered. */ | 
 | 	WRITE_ONCE(__tlb_bench_x, 1); | 
 | } | 
 |  | 
 | static void __tlb_s_ipi(struct hw_trapframe *hw_tf, void *data) | 
 | { | 
 | 	__tlb_s(); | 
 | } | 
 |  | 
 | static void __tlb_s_kmsg(uint32_t srcid, long a0, long a1, long a2) | 
 | { | 
 | 	__tlb_s(); | 
 | } | 
 |  | 
 | /* This runs the test from the calling core, which is typically core 0 if you | 
 |  * are running from the shell.  If you run from another core, note that | 
 |  * deregister_irq() will synchronize_rcu, which moves this thread to core 0 at | 
 |  * the end of the function. */ | 
 | static void __tlb_shootdown_bench(int target_core, int mode) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	uint64_t s, *d; | 
 | 	const char *str = NULL; | 
 | 	struct irq_handler *irqh; | 
 | 	int tbdf = MKBUS(BusIPI, 0, 0, 0); | 
 | 	#define ITERS 10 | 
 |  | 
 | 	if (target_core == core_id()) | 
 | 		error(EINVAL, "TLB bench: Aborting, we are core %d", | 
 | 		      target_core); | 
 | 	if (target_core < 0 || target_core >= num_cores) | 
 | 		error(EINVAL, | 
 | 		      "TLB bench: Aborting, target_core %d out of range", | 
 | 		      target_core); | 
 | 	irqh = register_irq(I_TESTING, __tlb_s_ipi, NULL, tbdf); | 
 | 	if (!irqh) | 
 | 		error(EFAIL, | 
 | 		      "TLB bench: Oh crap, we couldn't register the IRQ!"); | 
 | 	d = kmalloc(sizeof(uint64_t) * ITERS, MEM_WAIT); | 
 | 	if (waserror()) { | 
 | 		deregister_irq(irqh->apic_vector, tbdf); | 
 | 		kfree(d); | 
 | 		nexterror(); | 
 | 	} | 
 | 	for (int i = 0; i < ITERS; i++) { | 
 | 		__tlb_bench_x = 0; | 
 | 		s = start_timing(); | 
 | 		switch (mode) { | 
 | 		case 1: | 
 | 			str = "NOOP"; | 
 | 			__tlb_bench_x = 1; | 
 | 			break; | 
 | 		case 2: | 
 | 			tlbflush(); | 
 | 			str = "LOCAL"; | 
 | 			__tlb_bench_x = 1; | 
 | 			break; | 
 | 		case 3: | 
 | 			/* To run this test, you need to hacked this into | 
 | 			 * POKE_HANDLER.  If not, you'll wedge the machine. | 
 | 				mov %cr3,%rax;\ | 
 | 				mov %rax,%cr3;\ | 
 | 				incl __tlb_bench_x;\ | 
 | 			* And comment out the error(). */ | 
 | 			error(EFAIL, "TLB bench: hack the POKE_HANDLER"); | 
 |  | 
 | 			send_ipi(target_core, I_POKE_CORE); | 
 | 			str = "POKE"; | 
 | 			while (!READ_ONCE(__tlb_bench_x)) | 
 | 				cpu_relax(); | 
 | 			break; | 
 | 		case 4: | 
 | 			send_ipi(target_core, I_TESTING); | 
 | 			str = "IPI"; | 
 | 			while (!READ_ONCE(__tlb_bench_x)) | 
 | 				cpu_relax(); | 
 | 			break; | 
 | 		case 5: | 
 | 			send_kernel_message(target_core, __tlb_s_kmsg, 0, 0, 0, | 
 | 					    KMSG_IMMEDIATE); | 
 | 			str = "KMSG"; | 
 | 			while (!READ_ONCE(__tlb_bench_x)) | 
 | 				cpu_relax(); | 
 | 			break; | 
 | 		case 6: | 
 | 			send_kernel_message(target_core, __tlb_s_kmsg, 0, 0, 0, | 
 | 					    KMSG_IMMEDIATE); | 
 | 			str = "NOACK-KMSG"; | 
 | 			break; | 
 | 		case 7: | 
 | 			send_ipi(target_core, I_TESTING); | 
 | 			str = "NOACK-IPI"; | 
 | 			break; | 
 | 		default: | 
 | 			error(EINVAL, "TLB bench: bad mode %d", mode); | 
 | 		} | 
 | 		d[i] = stop_timing(s); | 
 | 		/* The NOACKs still need to wait, so we don't race with the | 
 | 		 * remote core and our *next* loop. */ | 
 | 		while (!READ_ONCE(__tlb_bench_x)) | 
 | 			cpu_relax(); | 
 | 		/* The remote core has signalled it did the TLB flush, but it | 
 | 		 * takes a little while for it to halt or otherwise get back to | 
 | 		 * idle.  Wait a little to get a more stable measurement. | 
 | 		 * Without this delay (or something similar), I've seen extra | 
 | 		 * delays of close to 400ns.  Note that in real usage, the | 
 | 		 * remote core won't always be ready to handle the IRQ, so this | 
 | 		 * test is best case. */ | 
 | 		udelay(1000); | 
 | 	} | 
 | 	for (int i = 0; i < ITERS; i++) | 
 | 		printk("%02d: TLB %s shootdown: %llu ns\n", i, str, | 
 | 		       tsc2nsec(d[i])); | 
 | 	deregister_irq(irqh->apic_vector, tbdf); | 
 | 	kfree(d); | 
 | 	poperror(); | 
 | } | 
 |  | 
 | static size_t regresswrite(struct chan *c, void *a, size_t n, off64_t unused) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	uintptr_t pc; | 
 | 	struct cmdbuf *cb; | 
 | 	cb = parsecmd(a, n); | 
 |  | 
 | 	if (waserror()) { | 
 | 		kfree(cb); | 
 | 		nexterror(); | 
 | 	} | 
 |  | 
 | 	switch ((int)(c->qid.path)) { | 
 | 	case Monitorctlqid: | 
 | 		if (cb->nf < 1) | 
 | 			error(EFAIL, "%s no command, need %s", __func__, | 
 | 			      ctlcommands); | 
 | 		if (!strcmp(cb->f[0], "ktest")) { | 
 | 			run_registered_ktest_suites(); | 
 | 		} else if (!strcmp(cb->f[0], "tlb")) { | 
 | 			if (cb->nf < 3) | 
 | 				error(EFAIL, | 
 | 				      "TLB bench: need core and mode (ints)"); | 
 | 			__tlb_shootdown_bench(strtol(cb->f[1], NULL, 10), | 
 | 					      strtol(cb->f[2], NULL, 10)); | 
 | 		} else { | 
 | 			error(EFAIL, "regresswrite: only commands are %s", | 
 | 			      ctlcommands); | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case Monitordataqid: | 
 | 		if (onecmd(cb->nf, cb->f, NULL) < 0) | 
 | 			n = -1; | 
 | 		break; | 
 | 	default: | 
 | 		error(EBADFD, ERROR_FIXME); | 
 | 	} | 
 | 	kfree(cb); | 
 | 	poperror(); | 
 | 	return n; | 
 | } | 
 |  | 
 | struct dev regressdevtab __devtab = { | 
 | 	.name = "regress", | 
 |  | 
 | 	.reset = devreset, | 
 | 	.init = regressinit, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = regressattach, | 
 | 	.walk = regresswalk, | 
 | 	.stat = regressstat, | 
 | 	.open = regressopen, | 
 | 	.create = devcreate, | 
 | 	.close = regressclose, | 
 | 	.read = regressread, | 
 | 	.bread = devbread, | 
 | 	.write = regresswrite, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = devremove, | 
 | 	.wstat = devwstat, | 
 | }; |