| /* | |
| * 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) | |
| { | |
| if (current_level() == CurrentEL_EL1) { | |
| assert(TIMER_PTIMER_IRQ != -1 && TIMER_VTIMER_IRQ != -1); | |
| ptimer_info.irq = TIMER_PTIMER_IRQ; | |
| vtimer_info.irq = TIMER_VTIMER_IRQ; | |
| } else { | |
| assert(TIMER_HPTIMER_IRQ != -1 && TIMER_HVTIMER_IRQ != -1); | |
| ptimer_info.irq = TIMER_HPTIMER_IRQ; | |
| vtimer_info.irq = TIMER_HVTIMER_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(); | |
| } |