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