| /* |
| * Measure the cost of micro level operations. |
| * |
| * This test provides support for quantifying the cost of micro level |
| * operations. To improve precision in the measurements, one should |
| * consider pinning each VCPU to a specific physical CPU (PCPU) and to |
| * ensure no other task could run on that PCPU to skew the results. |
| * This can be achieved by enabling QMP server in the QEMU command in |
| * unittest.cfg for micro-bench, allowing a client program to get the |
| * thread_id for each VCPU thread from the QMP server. Based on that |
| * information, the client program can then pin the corresponding VCPUs to |
| * dedicated PCPUs and isolate interrupts and tasks from those PCPUs. |
| * |
| * Copyright Columbia University |
| * Author: Shih-Wei Li <shihwei@cs.columbia.edu> |
| * Author: Christoffer Dall <cdall@cs.columbia.edu> |
| * Author: Andrew Jones <drjones@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include <libcflat.h> |
| #include <asm/gic.h> |
| |
| #define NTIMES (1U << 16) |
| |
| static u32 cntfrq; |
| |
| static volatile bool ipi_ready, ipi_received; |
| static void *vgic_dist_base; |
| static void (*write_eoir)(u32 irqstat); |
| |
| static void ipi_irq_handler(struct pt_regs *regs) |
| { |
| ipi_ready = false; |
| ipi_received = true; |
| gic_write_eoir(gic_read_iar()); |
| ipi_ready = true; |
| } |
| |
| static void ipi_secondary_entry(void *data) |
| { |
| install_irq_handler(EL1H_IRQ, ipi_irq_handler); |
| gic_enable_defaults(); |
| local_irq_enable(); |
| ipi_ready = true; |
| while (true) |
| cpu_relax(); |
| } |
| |
| static bool test_init(void) |
| { |
| int v = gic_init(); |
| |
| if (!v) { |
| printf("No supported gic present, skipping tests...\n"); |
| return false; |
| } |
| |
| if (nr_cpus < 2) { |
| printf("At least two cpus required, skipping tests...\n"); |
| return false; |
| } |
| |
| switch (v) { |
| case 2: |
| vgic_dist_base = gicv2_dist_base(); |
| write_eoir = gicv2_write_eoir; |
| break; |
| case 3: |
| vgic_dist_base = gicv3_dist_base(); |
| write_eoir = gicv3_write_eoir; |
| break; |
| } |
| |
| ipi_ready = false; |
| gic_enable_defaults(); |
| on_cpu_async(1, ipi_secondary_entry, NULL); |
| |
| cntfrq = get_cntfrq(); |
| printf("Timer Frequency %d Hz (Output in microseconds)\n", cntfrq); |
| |
| return true; |
| } |
| |
| static void ipi_prep(void) |
| { |
| unsigned tries = 1 << 28; |
| |
| while (!ipi_ready && tries--) |
| cpu_relax(); |
| assert(ipi_ready); |
| } |
| |
| static void ipi_exec(void) |
| { |
| unsigned tries = 1 << 28; |
| static int received = 0; |
| |
| ipi_received = false; |
| |
| gic_ipi_send_single(1, 1); |
| |
| while (!ipi_received && tries--) |
| cpu_relax(); |
| |
| ++received; |
| assert_msg(ipi_received, "failed to receive IPI in time, but received %d successfully\n", received); |
| } |
| |
| static void hvc_exec(void) |
| { |
| asm volatile("mov w0, #0x4b000000; hvc #0" ::: "w0"); |
| } |
| |
| static void mmio_read_user_exec(void) |
| { |
| /* |
| * FIXME: Read device-id in virtio mmio here in order to |
| * force an exit to userspace. This address needs to be |
| * updated in the future if any relevant changes in QEMU |
| * test-dev are made. |
| */ |
| void *userspace_emulated_addr = (void*)0x0a000008; |
| |
| readl(userspace_emulated_addr); |
| } |
| |
| static void mmio_read_vgic_exec(void) |
| { |
| readl(vgic_dist_base + GICD_IIDR); |
| } |
| |
| static void eoi_exec(void) |
| { |
| int spurious_id = 1023; /* writes to EOI are ignored */ |
| |
| /* Avoid measuring assert(..) in gic_write_eoir */ |
| write_eoir(spurious_id); |
| } |
| |
| struct exit_test { |
| const char *name; |
| void (*prep)(void); |
| void (*exec)(void); |
| bool run; |
| }; |
| |
| static struct exit_test tests[] = { |
| {"hvc", NULL, hvc_exec, true}, |
| {"mmio_read_user", NULL, mmio_read_user_exec, true}, |
| {"mmio_read_vgic", NULL, mmio_read_vgic_exec, true}, |
| {"eoi", NULL, eoi_exec, true}, |
| {"ipi", ipi_prep, ipi_exec, true}, |
| }; |
| |
| struct ns_time { |
| uint64_t ns; |
| uint64_t ns_frac; |
| }; |
| |
| #define PS_PER_SEC (1000 * 1000 * 1000 * 1000UL) |
| static void ticks_to_ns_time(uint64_t ticks, struct ns_time *ns_time) |
| { |
| uint64_t ps_per_tick = PS_PER_SEC / cntfrq + !!(PS_PER_SEC % cntfrq); |
| uint64_t ps; |
| |
| ps = ticks * ps_per_tick; |
| ns_time->ns = ps / 1000; |
| ns_time->ns_frac = (ps % 1000) / 100; |
| } |
| |
| static void loop_test(struct exit_test *test) |
| { |
| uint64_t start, end, total_ticks, ntimes = NTIMES; |
| struct ns_time total_ns, avg_ns; |
| |
| if (test->prep) |
| test->prep(); |
| |
| isb(); |
| start = read_sysreg(cntpct_el0); |
| while (ntimes--) |
| test->exec(); |
| isb(); |
| end = read_sysreg(cntpct_el0); |
| |
| total_ticks = end - start; |
| ticks_to_ns_time(total_ticks, &total_ns); |
| avg_ns.ns = total_ns.ns / NTIMES; |
| avg_ns.ns_frac = total_ns.ns_frac / NTIMES; |
| |
| printf("%-30s%15" PRId64 ".%-15" PRId64 "%15" PRId64 ".%-15" PRId64 "\n", |
| test->name, total_ns.ns, total_ns.ns_frac, avg_ns.ns, avg_ns.ns_frac); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int i; |
| |
| if (!test_init()) |
| return 1; |
| |
| printf("\n%-30s%18s%13s%18s%13s\n", "name", "total ns", "", "avg ns", ""); |
| for (i = 0 ; i < 92; ++i) |
| printf("%c", '-'); |
| printf("\n"); |
| for (i = 0; i < ARRAY_SIZE(tests); i++) { |
| if (!tests[i].run) |
| continue; |
| assert(tests[i].name && tests[i].exec); |
| loop_test(&tests[i]); |
| } |
| |
| return 0; |
| } |