@@ -24,6 +24,14 @@
#define APIC_DEST_SELF 0x40000
#define APIC_ICR2 0x310
+#define APIC_LVTT 0x320
+#define APIC_TIMER_MODE_MASK (0x3<<17)
+#define APIC_TIMER_MODE_ONESHOT (0x0<<17)
+#define APIC_TIMER_MODE_PERIODIC (0x1<<17)
+
+#define APIC_TMICT 0x380
+#define APIC_TMCCT 0x390
+
#define APIC_DEFAULT_BASE 0xfee00000ul
new file mode 100644
@@ -0,0 +1,9 @@
+include $(ROOT)/build/common.mk
+
+NAME := vlapic-timer
+CATEGORY := functional
+TEST-ENVS := hvm64
+
+obj-perenv += main.o
+
+include $(ROOT)/build/gen.mk
new file mode 100644
@@ -0,0 +1,151 @@
+/**
+ * @file tests/vlapic-timer/main.c
+ * @ref test-vlapic-timer - LAPIC Timer Emulation
+ *
+ * @page test-vlapic-timer LAPIC Timer Emulation
+ *
+ * Tests the behavior of the vlapic timer emulation by Xen.
+ *
+ * These tests are mostly base on observation made on baremetal, Intel SDM does
+ * not describe everything.
+ *
+ * It is testing switch between different mode, one-shot and periodic, as well
+ * a the TSC-Deadline mode.
+ *
+ * @see tests/vlapic-timer/main.c
+ */
+#include <xtf.h>
+#include <arch/apic.h>
+
+const char test_title[] = "Test vlapic-timer";
+
+static inline void change_mode(unsigned long new_mode)
+{
+ uint32_t lvtt;
+
+ lvtt = apic_read(APIC_LVTT);
+ apic_write(APIC_LVTT, (lvtt & ~APIC_TIMER_MODE_MASK) | new_mode);
+}
+
+void wait_tmcct_count_down(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 wait_until_tmcct_is_zero(uint32_t initial_count)
+{
+ wait_tmcct_count_down(initial_count, false);
+}
+static inline void wait_until_tmcct_is_half_down(uint32_t initial_count)
+{
+ wait_tmcct_count_down(initial_count, true);
+}
+
+void testing_oneshot_and_periodic_mode(void)
+{
+ uint32_t tmict = 0x9999999;
+
+ /* Start in one-shot mode */
+ change_mode(APIC_TIMER_MODE_ONESHOT);
+
+ apic_write(APIC_TMICT, tmict);
+
+ /* On mode change one-shot -> periodic, TMICT is not reset on baremetal */
+ change_mode(APIC_TIMER_MODE_PERIODIC);
+ if ( apic_read(APIC_TMICT) != tmict )
+ xtf_failure("Fail: TMICT value reset\n");
+
+ /*
+ * Testing one-shot
+ */
+ printk("Testing one-shot mode\n");
+ change_mode(APIC_TIMER_MODE_ONESHOT);
+
+ /* Testing TMCCT after setting TMICT */
+ apic_write(APIC_TMICT, tmict);
+ if ( !apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should have a non-zero value\n");
+
+ wait_until_tmcct_is_zero(tmict);
+ if ( apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should have reached 0\n");
+
+ /*
+ * Testing periodic timer mode
+ *
+ * Write TMICT before changing mode one-shot -> periodic,
+ * check that TMCCT keeps counting down after this mode change
+ */
+ apic_write(APIC_TMICT, tmict);
+ wait_until_tmcct_is_half_down(tmict);
+
+ printk("Testing periodic mode\n");
+ change_mode(APIC_TIMER_MODE_PERIODIC);
+
+ if ( !apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should have a non-zero value\n");
+
+ if ( apic_read(APIC_TMCCT) > (tmict / 2) )
+ xtf_failure("Fail: TMCCT should not be reset to TMICT value\n");
+
+ /* Check that the TMCCT is reset to TMICT */
+ wait_until_tmcct_is_zero(tmict);
+ if ( apic_read(APIC_TMCCT) < (tmict / 2) )
+ xtf_failure("Fail: TMCCT should be reset to TMICT periodically\n");
+
+ wait_until_tmcct_is_half_down(tmict);
+
+ /*
+ * Keep the same TMICT and change timer mode periodic -> one-shot
+ * Check that TMCCT keeps counting down and is not reset.
+ */
+ printk("Testing one-shot after periodic (with same tmict)\n");
+ change_mode(APIC_TIMER_MODE_ONESHOT);
+
+ if ( !apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should have a non-zero value\n");
+ if ( apic_read(APIC_TMCCT) > (tmict / 2) )
+ xtf_failure("Fail: TMCCT should not be reset to init\n");
+
+ wait_until_tmcct_is_zero(tmict);
+ if ( apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should have reach zero\n");
+
+ /* Now TMCCT == 0 and TMICT != 0 */
+ change_mode(APIC_TIMER_MODE_PERIODIC);
+ if ( apic_read(APIC_TMCCT) )
+ xtf_failure("Fail: TMCCT should stay at zero\n");
+}
+
+void test_main(void)
+{
+ if ( apic_init(APIC_MODE_XAPIC) )
+ return xtf_skip("No APIC support");
+
+ testing_oneshot_and_periodic_mode();
+
+ xtf_success(NULL);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
Start by testing one-shot and periodic timer modes. The behavior of TMICT and TMCCT while switching between periodic and one-shot timer modes check in this test is mostly base on observation of baremetal. Intel SDM gives little details about it. Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- arch/x86/include/arch/apic.h | 8 +++ tests/vlapic-timer/Makefile | 9 +++ tests/vlapic-timer/main.c | 151 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 tests/vlapic-timer/Makefile create mode 100644 tests/vlapic-timer/main.c