| /* |
| * Timer tests for the ARM virt machine. |
| * |
| * Copyright (C) 2017, Alexander Graf <agraf@suse.de> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2. |
| */ |
| #include <libcflat.h> |
| #include <devicetree.h> |
| #include <errata.h> |
| #include <asm/timer.h> |
| #include <asm/delay.h> |
| #include <asm/processor.h> |
| #include <asm/gic.h> |
| #include <asm/io.h> |
| |
| 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(); |
| } |