From patchwork Thu Oct 8 18:02:47 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marcelo Tosatti X-Patchwork-Id: 52547 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n98IAAMf012391 for ; Thu, 8 Oct 2009 18:10:10 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758950AbZJHSEg (ORCPT ); Thu, 8 Oct 2009 14:04:36 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1758550AbZJHSEg (ORCPT ); Thu, 8 Oct 2009 14:04:36 -0400 Received: from mx1.redhat.com ([209.132.183.28]:61669 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758206AbZJHSEe (ORCPT ); Thu, 8 Oct 2009 14:04:34 -0400 Received: from int-mx05.intmail.prod.int.phx2.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.18]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id n98I48xF028198 for ; Thu, 8 Oct 2009 14:04:08 -0400 Received: from ns3.rdu.redhat.com (ns3.rdu.redhat.com [10.11.255.199]) by int-mx05.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id n98I46jE013634; Thu, 8 Oct 2009 14:04:07 -0400 Received: from amt.cnet (vpn-10-92.str.redhat.com [10.32.10.92]) by ns3.rdu.redhat.com (8.13.8/8.13.8) with ESMTP id n98I43BT002488; Thu, 8 Oct 2009 14:04:04 -0400 Received: from amt.cnet (amt.cnet [127.0.0.1]) by amt.cnet (Postfix) with ESMTP id 800E16814E2; Thu, 8 Oct 2009 15:02:49 -0300 (BRT) Received: (from marcelo@localhost) by amt.cnet (8.14.3/8.14.3/Submit) id n98I2l5g007799; Thu, 8 Oct 2009 15:02:47 -0300 Date: Thu, 8 Oct 2009 15:02:47 -0300 From: Marcelo Tosatti To: Avi Kivity Cc: kvm@vger.kernel.org, Zachary Amsden Subject: kvm: test: timer testcase (v2) Message-ID: <20091008180247.GA7436@amt.cnet> References: <20091007190201.GA6418@amt.cnet> <4ACDDD09.6080302@redhat.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <4ACDDD09.6080302@redhat.com> User-Agent: Mutt/1.5.19 (2009-01-05) X-Scanned-By: MIMEDefang 2.67 on 10.5.11.18 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org Index: qemu-kvm/kvm/user/config-x86-common.mak =================================================================== --- qemu-kvm.orig/kvm/user/config-x86-common.mak +++ qemu-kvm/kvm/user/config-x86-common.mak @@ -58,6 +58,9 @@ $(TEST_DIR)/tsc.flat: $(cstart.o) $(TEST $(TEST_DIR)/apic.flat: $(cstart.o) $(TEST_DIR)/apic.o $(TEST_DIR)/vm.o \ $(TEST_DIR)/print.o +$(TEST_DIR)/time.flat: $(cstart.o) $(TEST_DIR)/time.o $(TEST_DIR)/vm.o \ + $(TEST_DIR)/print.o + $(TEST_DIR)/realmode.flat: $(TEST_DIR)/realmode.o $(CC) -m32 -nostdlib -o $@ -Wl,-T,$(TEST_DIR)/realmode.lds $^ Index: qemu-kvm/kvm/user/config-x86_64.mak =================================================================== --- qemu-kvm.orig/kvm/user/config-x86_64.mak +++ qemu-kvm/kvm/user/config-x86_64.mak @@ -7,6 +7,7 @@ CFLAGS += -D__x86_64__ tests = $(TEST_DIR)/access.flat $(TEST_DIR)/sieve.flat \ $(TEST_DIR)/simple.flat $(TEST_DIR)/stringio.flat \ $(TEST_DIR)/memtest1.flat $(TEST_DIR)/emulator.flat \ - $(TEST_DIR)/hypercall.flat $(TEST_DIR)/apic.flat + $(TEST_DIR)/hypercall.flat $(TEST_DIR)/apic.flat \ + $(TEST_DIR)/time.flat include config-x86-common.mak Index: qemu-kvm/kvm/user/test/x86/io.h =================================================================== --- /dev/null +++ qemu-kvm/kvm/user/test/x86/io.h @@ -0,0 +1,40 @@ +#ifndef __PIO_H +#define __PIO_H + +static inline void outb(unsigned char val, unsigned short port) +{ + asm volatile("outb %0, %w1": : "a"(val), "Nd" (port)); +} + +static inline void outw(unsigned short val, unsigned short port) +{ + asm volatile("outw %0, %w1": : "a"(val), "Nd" (port)); +} + +static inline void outl(unsigned long val, unsigned short port) +{ + asm volatile("outl %0, %w1": : "a"(val), "Nd" (port)); +} + +static inline unsigned char inb(unsigned short port) +{ + unsigned char val; + asm volatile("inb %w1, %0": "=a"(val) : "Nd" (port)); + return val; +} + +static inline short inw(unsigned short port) +{ + short val; + asm volatile("inw %w1, %0": "=a"(val) : "Nd" (port)); + return val; +} + +static inline unsigned int inl(unsigned short port) +{ + unsigned int val; + asm volatile("inl %w1, %0": "=a"(val) : "Nd" (port)); + return val; +} + +#endif Index: qemu-kvm/kvm/user/test/x86/time.c =================================================================== --- /dev/null +++ qemu-kvm/kvm/user/test/x86/time.c @@ -0,0 +1,938 @@ +#include "libcflat.h" +#include "apic.h" +#include "vm.h" +#include "io.h" + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned u32; +typedef unsigned long ulong; +typedef unsigned long long u64; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +typedef struct { + unsigned short offset0; + unsigned short selector; + unsigned short ist : 3; + unsigned short : 5; + unsigned short type : 4; + unsigned short : 1; + unsigned short dpl : 2; + unsigned short p : 1; + unsigned short offset1; +#ifdef __x86_64__ + unsigned offset2; + unsigned reserved; +#endif +} idt_entry_t; + +typedef struct { + ulong rflags; + ulong cs; + ulong rip; + ulong func; + ulong regs[sizeof(ulong)*2]; +} isr_regs_t; + +#ifdef __x86_64__ +# define R "r" +#else +# define R "e" +#endif + +extern char isr_entry_point[]; + +asm ( + "isr_entry_point: \n" +#ifdef __x86_64__ + "push %r15 \n\t" + "push %r14 \n\t" + "push %r13 \n\t" + "push %r12 \n\t" + "push %r11 \n\t" + "push %r10 \n\t" + "push %r9 \n\t" + "push %r8 \n\t" +#endif + "push %"R "di \n\t" + "push %"R "si \n\t" + "push %"R "bp \n\t" + "push %"R "sp \n\t" + "push %"R "bx \n\t" + "push %"R "dx \n\t" + "push %"R "cx \n\t" + "push %"R "ax \n\t" +#ifdef __x86_64__ + "mov %rsp, %rdi \n\t" + "callq *8*16(%rsp) \n\t" +#else + "push %esp \n\t" + "calll *4+4*8(%esp) \n\t" + "add $4, %esp \n\t" +#endif + "pop %"R "ax \n\t" + "pop %"R "cx \n\t" + "pop %"R "dx \n\t" + "pop %"R "bx \n\t" + "pop %"R "bp \n\t" + "pop %"R "bp \n\t" + "pop %"R "si \n\t" + "pop %"R "di \n\t" +#ifdef __x86_64__ + "pop %r8 \n\t" + "pop %r9 \n\t" + "pop %r10 \n\t" + "pop %r11 \n\t" + "pop %r12 \n\t" + "pop %r13 \n\t" + "pop %r14 \n\t" + "pop %r15 \n\t" +#endif +#ifdef __x86_64__ + "add $8, %rsp \n\t" + "iretq \n\t" +#else + "add $4, %esp \n\t" + "iretl \n\t" +#endif + ); + +static idt_entry_t idt[256]; + + +static u16 read_cs(void) +{ + u16 v; + + asm("mov %%cs, %0" : "=rm"(v)); + return v; +} + +static void init_idt(void) +{ + struct { + u16 limit; + ulong idt; + } __attribute__((packed)) idt_ptr = { + sizeof(idt_entry_t) * 256 - 1, + (ulong)&idt, + }; + + asm volatile("lidt %0" : : "m"(idt_ptr)); +} + +static void set_idt_entry(unsigned vec, void (*func)(isr_regs_t *regs)) +{ + u8 *thunk = vmalloc(50); + ulong ptr = (ulong)thunk; + idt_entry_t ent = { + .offset0 = ptr, + .selector = read_cs(), + .ist = 0, + .type = 14, + .dpl = 0, + .p = 1, + .offset1 = ptr >> 16, +#ifdef __x86_64__ + .offset2 = ptr >> 32, +#endif + }; +#ifdef __x86_64__ + /* sub $8, %rsp */ + *thunk++ = 0x48; *thunk++ = 0x83; *thunk++ = 0xec; *thunk++ = 0x08; + /* mov $func_low, %(rsp) */ + *thunk++ = 0xc7; *thunk++ = 0x04; *thunk++ = 0x24; + *(u32 *)thunk = (ulong)func; thunk += 4; + /* mov $func_high, %(rsp+4) */ + *thunk++ = 0xc7; *thunk++ = 0x44; *thunk++ = 0x24; *thunk++ = 0x04; + *(u32 *)thunk = (ulong)func >> 32; thunk += 4; + /* jmp isr_entry_point */ + *thunk ++ = 0xe9; + *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4); +#else + /* push $func */ + *thunk++ = 0x68; + *(u32 *)thunk = (ulong)func; + /* jmp isr_entry_point */ + *thunk ++ = 0xe9; + *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4); +#endif + idt[vec] = ent; +} + +static void irq_disable(void) +{ + asm volatile("cli"); +} + +static void irq_enable(void) +{ + asm volatile("sti"); +} + +static void eoi(void) +{ + apic_write(APIC_EOI, 0); +} + +static void set_ioapic_redir(unsigned line, unsigned vec, unsigned trig_mode) +{ + ioapic_redir_entry_t e = { + .vector = vec, + .delivery_mode = 0, + .trig_mode = trig_mode, + }; + + ioapic_write_redir(line, e); +} + +/* interrupt handlers */ + +#define TIMER_VEC_BASE 0x90 + +struct int_table { + void (*func)(isr_regs_t *regs); + void (*irq_handler)(void *irq_priv); + void *irq_priv; +}; + +static struct int_table int_handlers[]; + +#define decl_irq_handler(N) \ +static void timer_int_##N(isr_regs_t *regs) { \ + struct int_table *t = &int_handlers[N]; \ + t->irq_handler(t->irq_priv); \ + eoi(); \ +} + +void set_irq_handler(int vec, void (*func)(void *irq_priv), void *irq_priv); + +void hlt(void) { asm volatile("hlt"); } + +#define NS_FREQ 1000000000ULL +#define US_FREQ 1000000ULL + +#define ns2cyc(ns) (ns*cpu_hz)/NS_FREQ +#define cyc2ns(cyc) (cyc*NS_FREQ)/cpu_hz + +#define us_to_ns(n) (1000*n) +#define ms_to_ns(n) (1000000*n) +#define s_to_ns(n) (1000000000*n) + +#define sdelay(n) nsdelay(s_to_ns(n)) + +u64 cpu_hz; + +#define mod_64(x, y) ((x) % (y)) + +static inline u64 div64_u64(u64 dividend, u64 divisor) +{ + return dividend / divisor; +} + +static u64 muldiv64(u64 a, u32 b, u32 c) +{ + union { + u64 ll; + struct { + u32 low, high; + } l; + } u, res; + u64 rl, rh; + + u.ll = a; + rl = (u64)u.l.low * (u64)b; + rh = (u64)u.l.high * (u64)b; + rh += (rl >> 32); + res.l.high = div64_u64(rh, c); + res.l.low = div64_u64(((mod_64(rh, c) << 32) + (rl & 0xffffffff)), c); + return res.ll; +} + +u64 rdtsc(void) +{ + unsigned a, d; + + asm volatile("rdtsc" : "=a"(a), "=d"(d)); + return a | (u64)d << 32; +} + +void wrtsc(u64 tsc) +{ + unsigned a = tsc, d = tsc >> 32; + + asm volatile("wrmsr" : : "a"(a), "d"(d), "c"(0x10)); +} + +void nsdelay(u64 ns) +{ + u64 entry = cyc2ns(rdtsc()); + + do { + __asm__ volatile ("nop"); + } while (cyc2ns(rdtsc()) - entry < ns); +} + +struct clocksource { + char *name; + int (*init) (void); + u64 (*read) (void); + u64 freq; +}; + +/* return count in nanoseconds */ +u64 clocksource_read(struct clocksource *clock) +{ + u64 val = clock->read(); + + val = muldiv64(val, NS_FREQ, clock->freq); + + return val; +} + +struct freq_divisor { + unsigned int divisor; + unsigned int program_value; +}; + +enum clockevt_type { CLOCKEVT_PERIODIC, CLOCKEVT_ONESHOT, }; + +struct clockevent { + char *name; + u64 (*init) (int vec); + int (*arm) (u64 count, u64 divisor, enum clockevt_type); + void (*cancel)(void); + u64 (*remain) (void); + + u64 max_count; + u64 freq; + struct freq_divisor *divisors; + + unsigned vec; +}; + +void clock_arm(struct clockevent *clockevt, enum clockevt_type type, + u64 period, u64 divisor) +{ + u64 count = (period * clockevt->freq) / NS_FREQ; + + if (count > clockevt->max_count) + printf("ERROR clock_arm: %s invalid count %ld (max_count=%ld)\n", + clockevt->name, period, clockevt->max_count); + + clockevt->arm(count, divisor, type); +} + +void clock_arm_raw(struct clockevent *clockevt, enum clockevt_type type, + u64 count, u64 divisor) +{ + if (count > clockevt->max_count) + printf("ERROR clock_arm: %s invalid count %ld (max_count=%ld)\n", + clockevt->name, count, clockevt->max_count); + + clockevt->arm(count, divisor, type); +} + + +/* -------- TSC clocksource ------------- */ + +int tsc_init(void) { printf("%s\n", __func__); return 0; } +u64 tsc_read(void) { return rdtsc(); } + +struct clocksource tsc = { + .name = "tsc", + .init = tsc_init, + .read = tsc_read, +}; + +/* --------- ACPI clocksource ----------- */ + +#define ACPI_PORT 0xb008 +#define ACPI_FREQ 3579545 + +u64 acpi_time_base; + +static void acpi_ovf(void *irq_priv) +{ + static int flip = 1; + + outw(1, 0xb000); + if (flip) + acpi_time_base += 0xffffff; + flip ^= 1; +} + +int acpi_init(void) +{ + outb(0xf1, 0xb2); + outw(1, 0xb002); + + /* wait to synchronize flip above with bit 23 going 1->0 */ + while (inl(ACPI_PORT) > 0x100); + + set_irq_handler(TIMER_VEC_BASE+3, acpi_ovf, 0); + set_ioapic_redir(9, TIMER_VEC_BASE+3, 1); + + return 0; +} + +u64 acpi_read(void) +{ + u64 val; + + irq_disable(); + val = acpi_time_base + inl(ACPI_PORT); + irq_enable(); + + return val; +} + +struct clocksource acpi = { + .name = "acpi", + .init = acpi_init, + .read = acpi_read, + .freq = ACPI_FREQ, +}; + +struct clocksource *clocksources[] = { &tsc, &acpi, }; + +/* --------- HPET clockevent ---------- */ + +#define HPET_CFG 0x010 +#define HPET_COUNTER 0x0f0 + +#define HPET_Tn_CFG(n) (0x100 + 0x20 * n) +#define HPET_Tn_CMP(n) (0x108 + 0x20 * n) + +static void *hpet_addr = (void *)0xfed00000; + +static unsigned long hpet_readl(unsigned reg) +{ + return *(volatile unsigned long *)(hpet_addr + reg); +} + +static void hpet_writel(unsigned reg, unsigned long val) +{ + *(volatile unsigned long *)(hpet_addr + reg) = val; +} + +struct freq_divisor hpet_divisors[] = { + {1, 1}, + {-1, -1}, +}; + +int hpet_vec; + +u64 hpet_timer_init(int vec) +{ + u64 val; + + hpet_writel(HPET_CFG, hpet_readl(HPET_CFG)|0x3); + + val = hpet_readl(HPET_COUNTER); + sdelay(1); + val = hpet_readl(HPET_COUNTER) - val; + printf("%s: detected %lld Hz timer\n", __func__, val); + hpet_vec = vec; + + return val; +} + +int hpet_timer_arm(u64 period, u64 divisor, enum clockevt_type type) +{ + u64 cfg = 0x4; + + set_ioapic_redir(8, hpet_vec, 0); + if (type == CLOCKEVT_PERIODIC) + cfg |= 0x8; + + hpet_writel(HPET_Tn_CFG(1), cfg); + hpet_writel(HPET_COUNTER, 0); + hpet_writel(HPET_CFG, hpet_readl(HPET_CFG)|0x3); /* enable HPET */ + hpet_writel(HPET_Tn_CMP(1), period); + + return 0; +} + +void hpet_timer_cancel(void) +{ + hpet_writel(HPET_Tn_CFG(1), 0); + hpet_writel(HPET_CFG, hpet_readl(HPET_CFG) & ~(0x3)); +} + +u64 hpet_timer_remain(void) +{ + return hpet_readl(HPET_Tn_CMP(1)) - hpet_readl(HPET_COUNTER); +} + +struct clockevent hpet_timer = { + .name = "hpet", + .init = hpet_timer_init, + .arm = hpet_timer_arm, + .cancel = hpet_timer_cancel, + .remain = hpet_timer_remain, + .max_count = 0xffffffff, + .divisors = hpet_divisors, +}; + +/* --------- LAPIC clockevent ---------- */ + +static void dummy(void *irq_priv) +{ +} + +struct freq_divisor lapic_divisors[] = { + {1, 0xB}, + {2, 0x0}, + {4, 0x1}, + {8, 0x2}, + {16, 0x3}, + {32, 0x8}, + {64, 0x9}, + {128, 0xA}, + {-1, -1}, +}; + +u64 lapic_timer_init(int vec) +{ + u64 hz; + + set_irq_handler(vec, dummy, 0); + apic_write(APIC_LVTT, vec); + apic_write(APIC_TDCR, 0xB); /* divide by 1 */ + apic_write(APIC_TMICT, 0xffffffff); + sdelay(1); + hz = 0xffffffff - apic_read(APIC_TMCCT); + printf("%s: detected %d Hz timer\n", __func__, hz); + return hz; +} + +int lapic_timer_arm(u64 period, u64 divisor, enum clockevt_type type) +{ + if (type == CLOCKEVT_PERIODIC) + apic_write(APIC_LVTT, apic_read(APIC_LVTT) | 1 << 17); + /* divide count */ + apic_write(APIC_TDCR, divisor); + /* initial count */ + apic_write(APIC_TMICT, period); + return 0; +} + +void lapic_timer_cancel(void) +{ + apic_write(APIC_LVTT, apic_read(APIC_LVTT) & ~(1 << 17)); /* one-shot */ + apic_write(APIC_TMICT, 0); +} + +u64 lapic_timer_remain(void) +{ + return apic_read(APIC_TMCCT); +} + +struct clockevent lapic_timer = { + .name = "lapic", + .init = lapic_timer_init, + .arm = lapic_timer_arm, + .cancel = lapic_timer_cancel, + .remain = lapic_timer_remain, + .max_count = 0xffffffff, + .divisors = lapic_divisors, +}; + +/* ---------- PIT clockevent --------- */ + +#define PIT_FREQ 1193181 +#define PIT_CNT_0 0x40 +#define PIT_CNT_1 0x41 +#define PIT_CNT_2 0x42 +#define PIT_TCW 0x43 + +int pit_vec; + +u64 pit_timer_remain(void) +{ + outb(0xf0, PIT_TCW); + return inb(PIT_CNT_0) | inb(PIT_CNT_0) << 8; +} + +u64 pit_timer_init(int vec) +{ + set_ioapic_redir(2, vec, 0); + /* mask LINT0, int is coming through IO-APIC */ + apic_write(APIC_LVT0, 1 << 16); + pit_vec = vec; + return PIT_FREQ; +} + +int pit_timer_arm(u64 period, u64 divisor, enum clockevt_type type) +{ + unsigned char ctrl_word = 0x30; + + hpet_writel(HPET_CFG, hpet_readl(HPET_CFG) & ~(0x3)); /* disable HPET */ + if (type == CLOCKEVT_PERIODIC) + ctrl_word |= 0x4; + outb(ctrl_word, PIT_TCW); + outb(period & 0xff, PIT_CNT_0); + outb((period & 0xff00) >> 8, PIT_CNT_0); + return 0; +} + +void pit_timer_cancel(void) +{ + unsigned char ctrl_word = 0x30; + + set_irq_handler(pit_vec, dummy, 0); + outb(ctrl_word, PIT_TCW); + outb(0, PIT_CNT_0); + outb(0, PIT_CNT_0); + nsdelay(ms_to_ns(100)); +} + +struct freq_divisor pit_divisors[] = { + {1, 1}, + {-1, -1}, +}; + +struct clockevent pit_timer = { + .name = "pit", + .init = pit_timer_init, + .arm = pit_timer_arm, + .cancel = pit_timer_cancel, + .remain = pit_timer_remain, + .divisors = pit_divisors, + .max_count = 0xffff, +}; + + +#define NR_CLOCKEVENTS 3 + +/* clockevent initialization */ +struct clockevent *clockevents[NR_CLOCKEVENTS] = { + &pit_timer, &lapic_timer, &hpet_timer, +}; + +decl_irq_handler(0) /* PIT */ +decl_irq_handler(1) /* LAPIC */ +decl_irq_handler(2) /* HPET */ +decl_irq_handler(3) /* ACPI OVF */ + +static struct int_table int_handlers[NR_CLOCKEVENTS+1] = { + { .func = timer_int_0 }, + { .func = timer_int_1 }, + { .func = timer_int_2 }, + { .func = timer_int_3 }, +}; + +void set_irq_handler(int vec, void (*func)(void *irq_priv), void *irq_priv) +{ + int int_table_idx = vec - TIMER_VEC_BASE; + + if (int_table_idx >= NR_CLOCKEVENTS+1) + printf("%s invalid vec\n", __func__); + + int_handlers[int_table_idx].irq_handler = func; + int_handlers[int_table_idx].irq_priv = irq_priv; +} + +void init_interrupts(void) +{ + int i; + + for (i = 0; i < NR_CLOCKEVENTS+1; i++) { + int vec = TIMER_VEC_BASE+i; + + set_idt_entry(vec, int_handlers[i].func); + } +} + +int init_clockevents(void) +{ + int i; + int vec = TIMER_VEC_BASE; + + for (i=0; i < ARRAY_SIZE(clockevents); i++) { + u64 freq = clockevents[i]->init(vec); + clockevents[i]->freq = freq; + clockevents[i]->vec = vec; + vec++; + } + return 0; +} + +void init_clocksources(void) +{ + int i; + + for(i=0; i < ARRAY_SIZE(clocksources); i++) + clocksources[i]->init(); +} + +/* actual tests */ + +#define TIME_TABLE_SZ 70 +struct time_table { + int idx; + struct clocksource *source; + struct clockevent *event; + u64 period; + u64 count; + u32 freq_divider; + unsigned long int val[TIME_TABLE_SZ]; + unsigned long int remain[TIME_TABLE_SZ]; +}; + +void time_table_record(struct time_table *t) +{ + t->remain[t->idx] = t->event->remain(); + t->val[t->idx] = clocksource_read(t->source); + t->idx++; + if (t->idx >= TIME_TABLE_SZ/2) + t->idx = 0; +} + +void dump_time_table(struct time_table *t) +{ + int i; + + for (i = 1; i < t->idx; i++) + printf("i=%d %lld - %lld = %lld (expect %lld) %% %d off\n", i, + t->val[i], t->val[i-1], t->val[i] - t->val[i-1], t->period, + 100 - ((t->val[i] - t->val[i-1])*100 / t->period)); +} + +/* Tunables + * + * pct_delta_threshold: percentage of correctness before declaring failure + * pct_remain_threshold: "" for remain (read from clockevent on interrupt) + * nr_periods_per_clock: test periodic clocks in the + * <0...max_count>/nr_periods_per_clock range. + * + */ + +int pct_delta_threshold = 10; +int pct_remain_threshold = 20; +int nr_periods_per_clock = 4; + +void inspect_table(struct time_table *t) +{ + int i; + u64 percent_avg = 0; + int dump_table = 0; + + if (t->idx <= 1 || t->period == 0) { + printf("failure\n"); + return; + } + + /* first entry can be little of if programming is slow (eg hpet) */ + for (i = 2; i < t->idx; i++) { + u64 fire_period = t->val[i] - t->val[i-1]; + u64 percent_off = (fire_period*100) / t->period; + + percent_avg += percent_off; + if (percent_off < 100-pct_delta_threshold || + percent_off > 100+pct_delta_threshold) + dump_table = 1; + } + + percent_avg /= t->idx-1; + + if (dump_table) { + printf("%s vs %s (period=%lld divider=%d) ", t->source->name, + t->event->name, t->period, t->freq_divider); + printf("correctness = %d %% off\n", percent_avg-100); + dump_time_table(t); + } +} + +void inspect_table_remain(struct time_table *t) +{ + int i; + int dump_table = 0; + + for (i = 2; i < t->idx; i++) { + u64 percent_off = (t->remain[i]*100) / t->count; + if (percent_off < 100-pct_remain_threshold || + percent_off > 100+pct_remain_threshold) + dump_table = 1; + } + + if (dump_table) { + printf("%s vs %s (period=%lld divider=%d)\n", t->source->name, + t->event->name, t->period, t->freq_divider); + for (i = 0; i < t->idx; i++) + printf("remain i=%d %lld (expect %lld) %d %% off\n", i, + t->remain[i], t->count, + ((t->remain[i]*100) / t->count)-100); + } +} + +static void timer_int_record(void *irq_priv) +{ + time_table_record(irq_priv); +} + +#define NR_TABLE_RECORD 30 + +void __test_periodic_one_clock(struct clockevent *clockevt, + struct clocksource *source, u64 count, + u64 freq_divider, u64 freq_divider_value) +{ + u64 period; + struct time_table *t = vmalloc(sizeof(struct time_table)); + + period = muldiv64(count, NS_FREQ, clockevt->freq); + + t->idx = 0; + t->period = period; + t->count = count / freq_divider; + t->source = source; + t->event = clockevt; + t->freq_divider = freq_divider; + + clockevt->cancel(); + set_irq_handler(clockevt->vec, timer_int_record, t); + + clock_arm_raw(clockevt, CLOCKEVT_PERIODIC, count/freq_divider, freq_divider_value); + + while (t->idx <= NR_TABLE_RECORD) + hlt(); + + clockevt->cancel(); + inspect_table(t); + inspect_table_remain(t); + vfree(t); +} + +void test_periodic_one_clock(struct clockevent *clockevt, + struct clocksource *source, u64 count) +{ + struct freq_divisor *entry = clockevt->divisors; + + while (entry && entry->divisor != -1) { + if (count < entry->divisor) + break; + __test_periodic_one_clock(clockevt, source, count, entry->divisor, + entry->program_value); + entry++; + } +} + +void test_periodic_one_clock2(struct clockevent *clockevt, + struct clocksource *source, u64 count) +{ + struct freq_divisor *entry = clockevt->divisors; + + while (entry && entry->divisor != -1) { + __test_periodic_one_clock(clockevt, source, count*entry->divisor, + entry->divisor, entry->program_value); + entry++; + } +} + +void test_periodic_events(void) +{ + int i, x; + + for (x = 0; x < ARRAY_SIZE(clocksources); x++) { + struct clocksource *clocksource = clocksources[x]; + + for (i = 0; i < ARRAY_SIZE(clockevents); i++) { + struct clockevent *clockevt = clockevents[i]; + u64 count, inc; + + printf("clockevent = %s clocksource = %s\n", clockevt->name, + clocksource->name); + + inc = clockevt->max_count / nr_periods_per_clock; + for (count = inc; count < clockevt->max_count; + count += inc) + test_periodic_one_clock(clockevt, clocksource, count); + + test_periodic_one_clock(clockevt, clocksource, 10); + test_periodic_one_clock2(clockevt, clocksource, + clockevt->max_count); + } + } +} + +u64 static_periods[] = { 1, 2, 5, 10, 20}; + +void test_periodic_events_short(void) +{ + int i, x, n; + + for (x = 0; x < ARRAY_SIZE(clocksources); x++) { + struct clocksource *clocksource = clocksources[x]; + + for (i = 0; i < ARRAY_SIZE(clockevents); i++) { + struct clockevent *clockevt = clockevents[i]; + + + for (n = 0; n < ARRAY_SIZE(static_periods); n++) { + printf("clockevent = %s clocksource = %s period = %dms\n", + clockevt->name, clocksource->name, static_periods[n]); + test_periodic_one_clock(clockevt, clocksource, + muldiv64(ms_to_ns(static_periods[n]), + clockevt->freq, NS_FREQ)); + } + } + } +} + +/* early calibration with PIT to detect TSC frequency, which is necessary + * to find lapic frequency. + */ +volatile int timer_isr; +static void timer_int_handler(void *irq_priv) +{ + timer_isr++; +} + +void early_calibrate_cpu_hz(void) +{ + u64 t1, t2; + int ints_per_sec = (PIT_FREQ/0xffff)+1; + + timer_isr = 0; + + pit_timer.arm(0xffff, 1, CLOCKEVT_PERIODIC); + t1 = rdtsc(); + do { + hlt(); + } while (timer_isr < ints_per_sec); + t2 = rdtsc(); + cpu_hz = t2 - t1; + printf("detected %lld MHz cpu\n", cpu_hz/1000/1000); + tsc.freq = cpu_hz; +} + +void early_calibrate(void) +{ + pit_timer.init(TIMER_VEC_BASE); + set_irq_handler(TIMER_VEC_BASE, timer_int_handler, 0); + early_calibrate_cpu_hz(); +} + +int main() +{ + setup_vm(); + init_interrupts(); + + init_idt(); + irq_enable(); + + early_calibrate(); + init_clocksources(); + init_clockevents(); + + test_periodic_events_short(); + // full test is slow since it uses maximum count and dividers + // test_periodic_events(); + + return 0; +} +