| /* |
| * 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, |
| }; |