From: Wanpeng Li <wanpeng.li@xxxxxxxxxxx> Add apic timer mode transition test. Signed-off-by: Wanpeng Li <wanpeng.li@xxxxxxxxxxx> --- lib/x86/apic-defs.h | 1 + x86/apic.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/lib/x86/apic-defs.h b/lib/x86/apic-defs.h index e0c3cca..a7bc6a0 100644 --- a/lib/x86/apic-defs.h +++ b/lib/x86/apic-defs.h @@ -91,6 +91,7 @@ #define APIC_TIMER_BASE_CLKIN 0x0 #define APIC_TIMER_BASE_TMBASE 0x1 #define APIC_TIMER_BASE_DIV 0x2 +#define APIC_LVT_TIMER_MASK (3 << 17) #define APIC_LVT_TIMER_ONESHOT (0 << 17) #define APIC_LVT_TIMER_PERIODIC (1 << 17) #define APIC_LVT_TIMER_TSCDEADLINE (2 << 17) diff --git a/x86/apic.c b/x86/apic.c index eb78579..6cfb52d 100644 --- a/x86/apic.c +++ b/x86/apic.c @@ -458,6 +458,83 @@ static void test_physical_broadcast(void) report("APIC physical broadcast shorthand", broadcast_received(ncpus)); } +void wait_until_tmcct_is_zero(uint32_t initial_count, bool stop_when_half) +{ + uint32_t tmcct = apic_read(APIC_TMCCT); + + if (tmcct) { + while (tmcct > (initial_count / 2)) + tmcct = apic_read(APIC_TMCCT); + + if ( stop_when_half ) + return; + + /* Wait until the counter reach 0 or wrap-around */ + while ( tmcct <= (initial_count / 2) && tmcct > 0 ) + tmcct = apic_read(APIC_TMCCT); + } +} + +static inline void apic_change_mode(unsigned long new_mode) +{ + uint32_t lvtt; + + lvtt = apic_read(APIC_LVTT); + apic_write(APIC_LVTT, (lvtt & ~APIC_LVT_TIMER_MASK) | new_mode); +} + +static void test_apic_change_mode() +{ + uint32_t tmict = 0x999999; + + printf("starting apic change mode\n"); + + apic_write(APIC_TMICT, tmict); + + apic_change_mode(APIC_LVT_TIMER_PERIODIC); + + report("TMICT value reset", apic_read(APIC_TMICT) == tmict); + + /* Testing one-shot */ + apic_change_mode(APIC_LVT_TIMER_ONESHOT); + apic_write(APIC_TMICT, tmict); + report("TMCCT should have a non-zero value", apic_read(APIC_TMCCT)); + + wait_until_tmcct_is_zero(tmict, false); + report("TMCCT should have reached 0", !apic_read(APIC_TMCCT)); + + /* + * Write TMICT before changing mode from one-shot to periodic TMCCT should + * be reset to TMICT periodicly + */ + apic_write(APIC_TMICT, tmict); + wait_until_tmcct_is_zero(tmict, true); + apic_change_mode(APIC_LVT_TIMER_PERIODIC); + report("TMCCT should have a non-zero value", apic_read(APIC_TMCCT)); + + /* + * After the change of mode, the counter should not be reset and continue + * counting down from where it was + */ + report("TMCCT should not be reset to TMICT value", apic_read(APIC_TMCCT) < (tmict / 2)); + wait_until_tmcct_is_zero(tmict, false); + report("TMCCT should be reset to the initial-count", apic_read(APIC_TMCCT) > (tmict / 2)); + + wait_until_tmcct_is_zero(tmict, true); + /* + * Keep the same TMICT and change timer mode to one-shot + * TMCCT should be > 0 and count-down to 0 + */ + apic_change_mode(APIC_LVT_TIMER_ONESHOT); + report("TMCCT should not be reset to init", apic_read(APIC_TMCCT) < (tmict / 2)); + wait_until_tmcct_is_zero(tmict, false); + report("TMCCT should have reach zero", !apic_read(APIC_TMCCT)); + + /* now tmcct == 0 and tmict != 0 */ + apic_change_mode(APIC_LVT_TIMER_PERIODIC); + report("TMCCT should stay at zero", !apic_read(APIC_TMCCT)); +} + int main() { setup_vm(); @@ -478,6 +555,7 @@ int main() test_multiple_nmi(); test_apic_timer_one_shot(); + test_apic_change_mode(); test_tsc_deadline_timer(); return report_summary(); -- 2.7.4