/* * Timer tests for the ARM virt machine. * * Copyright (C) 2017, Alexander Graf * * This work is licensed under the terms of the GNU GPL, version 2. */ #include #include #include #include #include #include #include #include static bool ptimer_unsupported; static void ptimer_unsupported_handler(struct pt_regs *regs, unsigned int esr) { ptimer_unsupported = true; regs->pc += 4; } static u64 read_vtimer_counter(void) { isb(); return read_sysreg(cntvct_el0); } static u64 read_vtimer_cval(void) { return read_sysreg(cntv_cval_el0); } static void write_vtimer_cval(u64 val) { write_sysreg(val, cntv_cval_el0); isb(); } static s32 read_vtimer_tval(void) { return read_sysreg(cntv_tval_el0); } static void write_vtimer_tval(s32 val) { write_sysreg(val, cntv_tval_el0); isb(); } static u64 read_vtimer_ctl(void) { return read_sysreg(cntv_ctl_el0); } static void write_vtimer_ctl(u64 val) { write_sysreg(val, cntv_ctl_el0); isb(); } static u64 read_ptimer_counter(void) { isb(); return read_sysreg(cntpct_el0); } static u64 read_ptimer_cval(void) { return read_sysreg(cntp_cval_el0); } static void write_ptimer_cval(u64 val) { write_sysreg(val, cntp_cval_el0); isb(); } static s32 read_ptimer_tval(void) { return read_sysreg(cntp_tval_el0); } static void write_ptimer_tval(s32 val) { write_sysreg(val, cntp_tval_el0); isb(); } static u64 read_ptimer_ctl(void) { return read_sysreg(cntp_ctl_el0); } static void write_ptimer_ctl(u64 val) { write_sysreg(val, cntp_ctl_el0); isb(); } struct timer_info { u32 irq; volatile bool irq_received; u64 (*read_counter)(void); u64 (*read_cval)(void); void (*write_cval)(u64); s32 (*read_tval)(void); void (*write_tval)(s32); u64 (*read_ctl)(void); void (*write_ctl)(u64); }; static struct timer_info vtimer_info = { .irq_received = false, .read_counter = read_vtimer_counter, .read_cval = read_vtimer_cval, .write_cval = write_vtimer_cval, .read_tval = read_vtimer_tval, .write_tval = write_vtimer_tval, .read_ctl = read_vtimer_ctl, .write_ctl = write_vtimer_ctl, }; static struct timer_info ptimer_info = { .irq_received = false, .read_counter = read_ptimer_counter, .read_cval = read_ptimer_cval, .write_cval = write_ptimer_cval, .read_tval = read_ptimer_tval, .write_tval = write_ptimer_tval, .read_ctl = read_ptimer_ctl, .write_ctl = write_ptimer_ctl, }; static void set_timer_irq_enabled(struct timer_info *info, bool enabled) { u32 irq = info->irq; if (enabled) gic_enable_irq(irq); else gic_disable_irq(irq); } static void irq_handler(struct pt_regs *regs) { struct timer_info *info; u32 irqstat = gic_read_iar(); u32 irqnr = gic_iar_irqnr(irqstat); if (irqnr == vtimer_info.irq) { info = &vtimer_info; } else if (irqnr == ptimer_info.irq) { info = &ptimer_info; } else { if (irqnr != GICC_INT_SPURIOUS) gic_write_eoir(irqstat); report_info("Unexpected interrupt: %d\n", irqnr); return; } info->write_ctl(ARCH_TIMER_CTL_IMASK | ARCH_TIMER_CTL_ENABLE); gic_write_eoir(irqstat); info->irq_received = true; } /* Check that the timer condition is met. */ static bool timer_pending(struct timer_info *info) { return (info->read_ctl() & ARCH_TIMER_CTL_ENABLE) && (info->read_ctl() & ARCH_TIMER_CTL_ISTATUS); } static bool gic_timer_check_state(struct timer_info *info, enum gic_irq_state expected_state) { int i; /* Wait for up to 1s for the GIC to sample the interrupt. */ for (i = 0; i < 10; i++) { mdelay(100); if (gic_irq_state(info->irq) == expected_state) { mdelay(100); if (gic_irq_state(info->irq) == expected_state) return true; } } return false; } static bool test_cval_10msec(struct timer_info *info) { u64 time_10ms = read_sysreg(cntfrq_el0) / 100; u64 time_1us = time_10ms / 10000; u64 before_timer, after_timer; s64 difference; /* Program timer to fire in 10 ms */ before_timer = info->read_counter(); info->write_cval(before_timer + time_10ms); info->write_ctl(ARCH_TIMER_CTL_ENABLE); /* Wait for the timer to fire */ while (!timer_pending(info)) ; /* It fired, check how long it took */ after_timer = info->read_counter(); difference = after_timer - (before_timer + time_10ms); report_info("After timer: 0x%016lx", after_timer); report_info("Expected : 0x%016lx", before_timer + time_10ms); report_info("Difference : %ld us", difference / time_1us); if (difference < 0) { printf("ISTATUS set too early\n"); return false; } return difference < time_10ms; } static void disable_timer(struct timer_info *info) { info->write_ctl(0); info->irq_received = false; } static void test_timer_pending(struct timer_info *info) { u64 now = info->read_counter(); u64 time_10s = read_sysreg(cntfrq_el0) * 10; u64 later = now + time_10s; /* * We don't want the irq handler to fire because that will change the * timer state and we want to test the timer output signal. We can * still read the pending state even if it's disabled. */ set_timer_irq_enabled(info, false); /* Enable the timer, but schedule it for much later */ info->write_cval(later); info->write_ctl(ARCH_TIMER_CTL_ENABLE); report(!timer_pending(info) && gic_timer_check_state(info, GIC_IRQ_STATE_INACTIVE), "not pending before"); info->write_cval(now - 1); report(timer_pending(info) && gic_timer_check_state(info, GIC_IRQ_STATE_PENDING), "interrupt signal pending"); disable_timer(info); set_timer_irq_enabled(info, true); report(!info->irq_received, "no interrupt when timer is disabled"); report(!timer_pending(info) && gic_timer_check_state(info, GIC_IRQ_STATE_INACTIVE), "interrupt signal no longer pending"); info->write_cval(now - 1); info->write_ctl(ARCH_TIMER_CTL_ENABLE | ARCH_TIMER_CTL_IMASK); report(timer_pending(info) && gic_timer_check_state(info, GIC_IRQ_STATE_INACTIVE), "interrupt signal not pending"); disable_timer(info); } static void test_timer_cval(struct timer_info *info) { report(test_cval_10msec(info), "latency within 10 ms"); report(info->irq_received, "interrupt received"); disable_timer(info); } static void timer_do_wfi(struct timer_info *info) { local_irq_disable(); if (info->irq_received) goto out; report_info("waiting for interrupt..."); wfi(); out: local_irq_enable(); } static void test_timer_tval(struct timer_info *info) { u64 time_10ms = read_sysreg(cntfrq_el0) / 100; s32 left; int i; info->write_tval(time_10ms); info->write_ctl(ARCH_TIMER_CTL_ENABLE); for (;;) { timer_do_wfi(info); left = info->read_tval(); if (info->irq_received || left <= 0) break; } /* Wait one second for the GIC to update the interrupt state. */ if (left <= 0 && !info->irq_received) { for (i = 0; i < 10; i++) { timer_do_wfi(info); if (info->irq_received) break; mdelay(100); } left = info->read_tval(); } report(info->irq_received, "interrupt received after TVAL/WFI"); report(left <= 0, "timer has expired"); report_info("TVAL is %d ticks", left); disable_timer(info); } static void test_timer(struct timer_info *info) { test_timer_cval(info); test_timer_pending(info); test_timer_tval(info); } static void test_vtimer(void) { report_prefix_push("vtimer-busy-loop"); test_timer(&vtimer_info); report_prefix_pop(); } static void test_ptimer(void) { if (ptimer_unsupported) return; report_prefix_push("ptimer-busy-loop"); test_timer(&ptimer_info); report_prefix_pop(); } static void test_init(void) { assert(TIMER_PTIMER_IRQ != -1 && TIMER_VTIMER_IRQ != -1); ptimer_info.irq = TIMER_PTIMER_IRQ; vtimer_info.irq = TIMER_VTIMER_IRQ; install_exception_handler(EL1H_SYNC, ESR_EL1_EC_UNKNOWN, ptimer_unsupported_handler); ptimer_info.read_ctl(); install_exception_handler(EL1H_SYNC, ESR_EL1_EC_UNKNOWN, NULL); if (ptimer_unsupported && !ERRATA(7b6b46311a85)) { report_skip("Skipping ptimer tests. Set ERRATA_7b6b46311a85=y to enable."); } else if (ptimer_unsupported) { report_fail("ptimer: read CNTP_CTL_EL0"); report_info("ptimer: skipping remaining tests"); } gic_enable_defaults(); install_irq_handler(EL1H_IRQ, irq_handler); set_timer_irq_enabled(&ptimer_info, true); set_timer_irq_enabled(&vtimer_info, true); local_irq_enable(); } static void print_timer_info(void) { printf("CNTFRQ_EL0 : 0x%016lx\n", read_sysreg(cntfrq_el0)); if (!ptimer_unsupported) { printf("CNTPCT_EL0 : 0x%016lx\n", ptimer_info.read_counter()); printf("CNTP_CTL_EL0 : 0x%016lx\n", ptimer_info.read_ctl()); printf("CNTP_CVAL_EL0: 0x%016lx\n", ptimer_info.read_cval()); } printf("CNTVCT_EL0 : 0x%016lx\n", vtimer_info.read_counter()); printf("CNTV_CTL_EL0 : 0x%016lx\n", vtimer_info.read_ctl()); printf("CNTV_CVAL_EL0: 0x%016lx\n", vtimer_info.read_cval()); } int main(int argc, char **argv) { int i; test_init(); print_timer_info(); if (argc == 1) { test_vtimer(); test_ptimer(); } for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "vtimer") == 0) test_vtimer(); if (strcmp(argv[i], "ptimer") == 0) test_ptimer(); } return report_summary(); }