blob: c4330000e40e0cca45db28b566ea1da0da88c5a0 [file] [log] [blame]
/*
* 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,
};