x86: PIT broadcast to fix local APIC timer stop issue for Deep C state
authorKeir Fraser <keir.fraser@citrix.com>
Mon, 14 Jul 2008 09:43:32 +0000 (10:43 +0100)
committerKeir Fraser <keir.fraser@citrix.com>
Mon, 14 Jul 2008 09:43:32 +0000 (10:43 +0100)
Local APIC timer may stop at deep C state (C3/C4...) entry. Initial
HPET broadcast working in legacy replacing mode, broke RTC intr, so
was bypassed. This patch add the logic that use platform timer (PIT)
to reenable local APIC timer at C state entry/exit.

Currently, only keep PIT enabled with 100Hz freq. The next step is
trying to dynamically enable/disable PIT while needed, and give it
lower freq.

Signed-off-by: Yu Ke <ke.yu@intel.com>
Signed-off-by: Tian Kevin <kevin.tian@intel.com>
Signed-off-by: Wei Gang <gang.wei@intel.com>
Signed-off-by: Keir Fraser <keir.fraser@citrix.com>
xen/arch/x86/acpi/cpu_idle.c
xen/arch/x86/setup.c
xen/arch/x86/time.c
xen/include/asm-x86/time.h

index 9517bd21adb61706f3286710557108552774c1d5..a8d182b3aaabcee327e5f91aa703c9e119d72b05 100644 (file)
@@ -56,6 +56,9 @@
 #define ACPI_PROCESSOR_MAX_C2_LATENCY   100
 #define ACPI_PROCESSOR_MAX_C3_LATENCY   1000
 
+static void (*lapic_timer_off)(void);
+static void (*lapic_timer_on)(void);
+
 extern u32 pmtmr_ioport;
 extern void (*pm_idle) (void);
 
@@ -437,7 +440,7 @@ static void acpi_processor_idle(void)
         /* preparing TSC stop */
         cstate_save_tsc();
         /* preparing APIC stop */
-        hpet_broadcast_enter();
+        lapic_timer_off();
 
         /* Get start time (ticks) */
         t1 = inl(pmtmr_ioport);
@@ -446,8 +449,6 @@ static void acpi_processor_idle(void)
         /* Get end time (ticks) */
         t2 = inl(pmtmr_ioport);
 
-        /* recovering APIC */
-        hpet_broadcast_exit();
         /* recovering TSC */
         cstate_restore_tsc();
 
@@ -460,6 +461,8 @@ static void acpi_processor_idle(void)
 
         /* Re-enable interrupts */
         local_irq_enable();
+        /* recovering APIC */
+        lapic_timer_on();
         /* Compute time (ticks) that we were actually asleep */
         sleep_ticks = ticks_elapsed(t1, t2);
         /* Do not account our idle-switching overhead: */
@@ -752,8 +755,20 @@ static int check_cx(struct acpi_processor_power *power, xen_processor_cx_t *cx)
     if ( cx->type == ACPI_STATE_C3 )
     {
         /* We must be able to use HPET in place of LAPIC timers. */
-        if ( !hpet_broadcast_is_available() )
+        if ( hpet_broadcast_is_available() )
+        {
+            lapic_timer_off = hpet_broadcast_enter;
+            lapic_timer_on = hpet_broadcast_exit;
+        }
+        else if ( pit_broadcast_is_available() )
+        {
+            lapic_timer_off = pit_broadcast_enter;
+            lapic_timer_on = pit_broadcast_exit;
+        }
+        else
+        {
             return -EINVAL;
+        }
 
         /* All the logic here assumes flags.bm_check is same across all CPUs */
         if ( !bm_check_flag )
index ac1aa93891928dd76e8850bccda1f556d421f674..ad86fa89193d9955166cc7e172dd8c291ee05ade 100644 (file)
@@ -96,7 +96,7 @@ boolean_param("noapic", skip_ioapic_setup);
 
 /* **** Linux config option: propagated to domain0. */
 /* xen_cpuidle: xen control cstate. */
-static int xen_cpuidle;
+/*static*/ int xen_cpuidle;
 boolean_param("cpuidle", xen_cpuidle);
 
 int early_boot = 1;
index 507b139bf8edc6e676cb5ffaec364aa12080f3f2..3525f4b544c0faed4c171c326ca6d6829316cbf7 100644 (file)
@@ -147,6 +147,32 @@ static inline u64 scale_delta(u64 delta, struct time_scale *scale)
     return product;
 }
 
+/*
+ * cpu_mask that denotes the CPUs that needs timer interrupt coming in as
+ * IPIs in place of local APIC timers
+ */
+extern int xen_cpuidle;
+static cpumask_t pit_broadcast_mask;
+
+static void smp_send_timer_broadcast_ipi(void)
+{
+    int cpu = smp_processor_id();
+    cpumask_t mask;
+
+    cpus_and(mask, cpu_online_map, pit_broadcast_mask);
+
+    if ( cpu_isset(cpu, mask) )
+    {
+        cpu_clear(cpu, mask);
+        raise_softirq(TIMER_SOFTIRQ);
+    }
+
+    if ( !cpus_empty(mask) )
+    {
+        cpumask_raise_softirq(mask, TIMER_SOFTIRQ);
+    }
+}
+
 static void timer_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs)
 {
     ASSERT(local_irq_is_enabled());
@@ -161,6 +187,9 @@ static void timer_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs)
     if ( !cpu_has_apic )
         raise_softirq(TIMER_SOFTIRQ);
 
+    if ( xen_cpuidle )
+        smp_send_timer_broadcast_ipi();
+
     /* Emulate a 32-bit PIT counter. */
     if ( using_pit )
     {
@@ -1006,9 +1035,10 @@ void __init early_time_init(void)
     setup_irq(0, &irq0);
 }
 
+/* keep pit enabled for pit_broadcast working while cpuidle enabled */
 static int disable_pit_irq(void)
 {
-    if ( !using_pit && cpu_has_apic )
+    if ( !using_pit && cpu_has_apic && !xen_cpuidle )
     {
         /* Disable PIT CH0 timer interrupt. */
         outb_p(0x30, PIT_MODE);
@@ -1026,6 +1056,21 @@ static int disable_pit_irq(void)
 }
 __initcall(disable_pit_irq);
 
+void pit_broadcast_enter(void)
+{
+    cpu_set(smp_processor_id(), pit_broadcast_mask);
+}
+
+void pit_broadcast_exit(void)
+{
+    cpu_clear(smp_processor_id(), pit_broadcast_mask);
+}
+
+int pit_broadcast_is_available(void)
+{
+    return xen_cpuidle;
+}
+
 void send_timer_event(struct vcpu *v)
 {
     send_guest_vcpu_virq(v, VIRQ_TIMER);
index 3ad0b0041efbeb763196392fc1c1d3f9e4cea2d9..0477f2b2b7d1b3fbefc51e159d52aba04cb0276e 100644 (file)
@@ -34,4 +34,8 @@ int cpu_frequency_change(u64 freq);
 struct tm;
 struct tm wallclock_time(void);
 
+void pit_broadcast_enter(void);
+void pit_broadcast_exit(void);
+int pit_broadcast_is_available(void);
+
 #endif /* __X86_TIME_H__ */