Hi all,

Here's an updated dyn-tick patch. Some minor fixes:

- Moved updating of time happen before other interrupts are
  called as noted by Russell King.

- Moved call to next_timer_interrupt to generic code from
  a custom idle module to allow using other idle modules,
  such as amd76x_pm.

- Added some experimental control options under
  /sys/devices/system/timer/timer0.

I've also put together a minimal website for dyn-tick:
http://www.muru.com/linux/dyntick/

Regards,

Tony

The sysfs debug options are following:

Check the state of dyn tick:
cat /sys/devices/system/timer/timer0/dyn_tick_state

Stop dyn-tick, and use normal HZ:
echo 0 > /sys/devices/system/timer/timer0/dyn_tick_state

Re-enabled dyn-tick:
echo 1 > /sys/devices/system/timer/timer0/dyn_tick_state

Lots of debug output to console, don't use this
from main console...

echo 1 > /sys/devices/system/timer/timer0/dyn_tick_dbg

Kill timer interrupt, and update time only from other
interrupts. Only use this for PM experiments. Time
will not stay correct as timers overflow.

echo 1 > /sys/devices/system/timer/timer0/dyn_tick_int

diff -Nru a/arch/i386/Kconfig b/arch/i386/Kconfig
--- a/arch/i386/Kconfig 2005-04-06 01:06:59 -07:00
+++ b/arch/i386/Kconfig 2005-04-06 01:06:59 -07:00
@@ -460,6 +460,16 @@
        bool "Provide RTC interrupt"
        depends on HPET_TIMER && RTC=y
 
+config NO_IDLE_HZ
+       bool "Dynamic Tick Timer - Skip timer ticks during idle"
+       help
+         This option enables support for skipping timer ticks when the
+         processor is idle. During system load, timer is continuous.
+         This option saves power, as it allows the system to stay in
+         idle mode longer. Currently supported timers are ACPI PM
+         timer, local APIC timer, and TSC timer. HPET timer is currently
+         not supported.
+
 config SMP
        bool "Symmetric multi-processing support"
        ---help---
diff -Nru a/arch/i386/kernel/apic.c b/arch/i386/kernel/apic.c
--- a/arch/i386/kernel/apic.c   2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/apic.c   2005-04-06 01:06:59 -07:00
@@ -26,6 +26,7 @@
 #include <linux/mc146818rtc.h>
 #include <linux/kernel_stat.h>
 #include <linux/sysdev.h>
+#include <linux/dyn-tick-timer.h>
 
 #include <asm/atomic.h>
 #include <asm/smp.h>
@@ -909,6 +910,8 @@
 
 #define APIC_DIVISOR 16
 
+static u32 apic_timer_val;
+
 static void __setup_APIC_LVTT(unsigned int clocks)
 {
        unsigned int lvtt_value, tmp_value, ver;
@@ -927,7 +930,15 @@
                                & ~(APIC_TDR_DIV_1 | APIC_TDR_DIV_TMBASE))
                                | APIC_TDR_DIV_16);
 
-       apic_write_around(APIC_TMICT, clocks/APIC_DIVISOR);
+       apic_timer_val = clocks/APIC_DIVISOR;
+
+#ifdef CONFIG_NO_IDLE_HZ
+       /* Local APIC timer is 24-bit */
+       if (apic_timer_val)
+               dyn_tick->max_skip = 0xffffff / apic_timer_val;
+#endif
+
+       apic_write_around(APIC_TMICT, apic_timer_val);
 }
 
 static void __init setup_APIC_timer(unsigned int clocks)
@@ -1040,6 +1051,13 @@
         */
        setup_APIC_timer(calibration_result);
 
+#ifdef CONFIG_NO_IDLE_HZ
+       if (calibration_result)
+               dyn_tick->state |= DYN_TICK_USE_APIC;
+       else
+               printk(KERN_INFO "dyn-tick: Cannot use local APIC\n");
+#endif
+
        local_irq_enable();
 }
 
@@ -1068,6 +1086,18 @@
        }
 }
 
+#if defined(CONFIG_NO_IDLE_HZ)
+void reprogram_apic_timer(unsigned int count)
+{
+       unsigned long flags;
+
+       count *= apic_timer_val;
+       local_irq_save(flags);
+       apic_write_around(APIC_TMICT, count);
+       local_irq_restore(flags);
+}
+#endif
+
 /*
  * the frequency of the profiling timer can be changed
  * by writing a multiplier value into /proc/profile.
@@ -1160,6 +1190,7 @@
 
 fastcall void smp_apic_timer_interrupt(struct pt_regs *regs)
 {
+       unsigned long seq;
        int cpu = smp_processor_id();
 
        /*
@@ -1178,6 +1209,23 @@
         * interrupt lock, which is the WrongThing (tm) to do.
         */
        irq_enter();
+
+#ifdef CONFIG_NO_IDLE_HZ
+       /*
+        * Check if we need to wake up PIT interrupt handler.
+        * Otherwise just wake up local APIC timer.
+        */
+       do {
+               seq = read_seqbegin(&xtime_lock);
+               if (dyn_tick->state & (DYN_TICK_ENABLED | DYN_TICK_SKIPPING)) {
+                       if (dyn_tick->skip_cpu == cpu && dyn_tick->skip > 
DYN_TICK_MIN_SKIP)
+                               dyn_tick->interrupt(99, NULL, regs);
+                       else
+                               reprogram_apic_timer(1);
+               }
+       } while (read_seqretry(&xtime_lock, seq));
+#endif
+
        smp_local_timer_interrupt(regs);
        irq_exit();
 }
diff -Nru a/arch/i386/kernel/dyn-tick.c b/arch/i386/kernel/dyn-tick.c
--- /dev/null   Wed Dec 31 16:00:00 196900
+++ b/arch/i386/kernel/dyn-tick.c       2005-04-06 01:06:59 -07:00
@@ -0,0 +1,68 @@
+/*
+ * linux/arch/i386/kernel/dyn-tick.c
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tony Lindgen <[EMAIL PROTECTED]> and
+ * Tuukka Tikkanen <[EMAIL PROTECTED]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+//#include <linux/module.h>
+//#include <linux/interrupt.h>
+//#include <linux/cpumask.h>
+//#include <linux/pm.h>
+#include <linux/dyn-tick-timer.h>
+//#include <asm/io.h>
+#include "dyn-tick.h"
+
+extern int dyn_tick_late_init(void);
+
+void arch_reprogram_timer(void)
+{
+       if (cpu_has_local_apic()) {
+               disable_pit_tick();
+               if (dyn_tick->state & DYN_TICK_TIMER_INT)
+                       reprogram_apic_timer(dyn_tick->skip);
+       } else {
+               if (dyn_tick->state & DYN_TICK_TIMER_INT)
+                       reprogram_pit_tick(dyn_tick->skip);
+               else
+                       disable_pit_tick();
+       }
+}
+
+static struct dyn_tick_timer arch_dyn_tick_timer = {
+       .arch_reprogram_timer   = &arch_reprogram_timer,
+};
+
+int __init dyn_tick_init(void)
+{
+       arch_dyn_tick_timer.arch_init = dyn_tick_late_init;
+       dyn_tick_register(&arch_dyn_tick_timer);
+
+       return 0;
+}
+arch_initcall(dyn_tick_init);
diff -Nru a/arch/i386/kernel/dyn-tick.h b/arch/i386/kernel/dyn-tick.h
--- /dev/null   Wed Dec 31 16:00:00 196900
+++ b/arch/i386/kernel/dyn-tick.h       2005-04-06 01:06:59 -07:00
@@ -0,0 +1,33 @@
+/*
+ * linux/arch/i386/kernel/dyn-tick.h
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tony Lindgen <[EMAIL PROTECTED]> and
+ * Tuukka Tikkanen <[EMAIL PROTECTED]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#if defined(CONFIG_SMP) || defined(CONFIG_X86_UP_APIC)
+#define cpu_has_local_apic()   (dyn_tick->state & DYN_TICK_USE_APIC)
+#else
+#define cpu_has_local_apic()   0
+#endif
diff -Nru a/arch/i386/kernel/irq.c b/arch/i386/kernel/irq.c
--- a/arch/i386/kernel/irq.c    2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/irq.c    2005-04-06 01:06:59 -07:00
@@ -15,6 +15,7 @@
 #include <linux/seq_file.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
+#include <linux/dyn-tick-timer.h>
 
 DEFINE_PER_CPU(irq_cpustat_t, irq_stat) ____cacheline_maxaligned_in_smp;
 EXPORT_PER_CPU_SYMBOL(irq_stat);
@@ -102,6 +103,12 @@
                );
        } else
 #endif
+
+#ifdef CONFIG_NO_IDLE_HZ
+       if (dyn_tick->state & (DYN_TICK_ENABLED | DYN_TICK_SKIPPING) && irq != 
0)
+               dyn_tick->interrupt(irq, NULL, regs);
+#endif
+
                __do_IRQ(irq, regs);
 
        irq_exit();
diff -Nru a/arch/i386/kernel/process.c b/arch/i386/kernel/process.c
--- a/arch/i386/kernel/process.c        2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/process.c        2005-04-06 01:06:59 -07:00
@@ -160,6 +160,10 @@
                        if (!idle)
                                idle = default_idle;
 
+#ifdef CONFIG_NO_IDLE_HZ
+                       dyn_tick_reprogram_timer();
+#endif
+
                        __get_cpu_var(irq_stat).idle_timestamp = jiffies;
                        idle();
                }
diff -Nru a/arch/i386/kernel/time.c b/arch/i386/kernel/time.c
--- a/arch/i386/kernel/time.c   2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/time.c   2005-04-06 01:06:59 -07:00
@@ -46,6 +46,7 @@
 #include <linux/bcd.h>
 #include <linux/efi.h>
 #include <linux/mca.h>
+#include <linux/dyn-tick-timer.h>
 
 #include <asm/io.h>
 #include <asm/smp.h>
@@ -67,6 +68,7 @@
 #include <asm/arch_hooks.h>
 
 #include "io_ports.h"
+#include "dyn-tick.h"
 
 extern spinlock_t i8259A_lock;
 int pit_latch_buggy;              /* extern */
@@ -308,6 +310,66 @@
        return IRQ_HANDLED;
 }
 
+#ifdef CONFIG_NO_IDLE_HZ
+static unsigned long long last_tick;
+void reprogram_pit_tick(int jiffies_to_skip);
+extern void replace_timer_interrupt(void * new_handler);
+
+#if defined(CONFIG_NO_IDLE_HZ) && defined(CONFIG_X86_LOCAL_APIC)
+extern void reprogram_apic_timer(unsigned int count);
+#else
+void reprogram_apic_timer(unsigned int count) {}
+#endif
+
+#ifdef DEBUG
+#define dbg_dyn_tick_irq() {if (skipped && skipped < dyn_tick->skip) \
+                               printk("%u/%li ", skipped, dyn_tick->skip);}
+#else
+#define dbg_dyn_tick_irq() {}
+#endif
+
+/*
+ * This interrupt handler updates the time based on number of jiffies skipped
+ * It would be somewhat more optimized to have a customa handler in each timer
+ * using hardware ticks instead of nanoseconds. Note that CONFIG_NO_IDLE_HZ
+ * currently disables timer fallback on skipped jiffies.
+ */
+irqreturn_t dyn_tick_timer_interrupt(int irq, void *dev_id, struct pt_regs 
*regs)
+{
+       unsigned long flags;
+       volatile unsigned long long now;
+       unsigned int skipped = 0;
+
+       if (dyn_tick->state & DYN_TICK_DEBUG) {
+               if (irq == 0)
+                       printk(".");
+               else
+                       printk("%i ", irq);
+       }
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       now = cur_timer->get_hw_time();
+       while (now - last_tick >= NS_TICK_LEN) {
+               last_tick += NS_TICK_LEN;
+               cur_timer->mark_offset();
+               do_timer_interrupt(irq, NULL, regs);
+               skipped++;
+       }
+       if (dyn_tick->state & (DYN_TICK_ENABLED | DYN_TICK_SKIPPING)) {
+               dbg_dyn_tick_irq();
+               dyn_tick->skip = 1;
+               if (cpu_has_local_apic())
+                       reprogram_apic_timer(dyn_tick->skip);
+               reprogram_pit_tick(dyn_tick->skip);
+               dyn_tick->state |= DYN_TICK_ENABLED;
+               dyn_tick->state &= ~DYN_TICK_SKIPPING;
+       }
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return IRQ_HANDLED;
+}
+#endif /* CONFIG_NO_IDLE_HZ */
+
 /* not static: needed by APM */
 unsigned long get_cmos_time(void)
 {
@@ -416,7 +478,7 @@
 
 
 /* XXX this driverfs stuff should probably go elsewhere later -john */
-static struct sys_device device_timer = {
+struct sys_device device_timer = {
        .id     = 0,
        .cls    = &timer_sysclass,
 };
@@ -452,6 +514,69 @@
 }
 #endif
 
+#ifdef CONFIG_NO_IDLE_HZ
+
+void disable_pit_tick(void)
+{
+       extern spinlock_t i8253_lock;
+       unsigned long flags;
+       spin_lock_irqsave(&i8253_lock, flags);
+       outb_p(0x31, PIT_MODE);         /* binary, mode 1, LSB/MSB, ch 0 */
+       spin_unlock_irqrestore(&i8253_lock, flags);
+}
+
+/*
+ * Reprograms the next timer interrupt
+ * PIT timer reprogramming code taken from APM code.
+ * Note that PIT timer is a 16-bit timer, which allows max
+ * skip of only few seconds.
+ */
+void reprogram_pit_tick(int jiffies_to_skip)
+{
+       int skip;
+       extern spinlock_t i8253_lock;
+       unsigned long flags;
+
+       skip = jiffies_to_skip * LATCH;
+       if (skip > 0xffff) {
+               skip = 0xffff;
+       }      
+
+       spin_lock_irqsave(&i8253_lock, flags);
+       outb_p(0x34, PIT_MODE);         /* binary, mode 2, LSB/MSB, ch 0 */
+       outb_p(skip & 0xff, PIT_CH0);   /* LSB */
+       outb(skip >> 8, PIT_CH0);       /* MSB */
+       spin_unlock_irqrestore(&i8253_lock, flags);
+}
+
+int __init dyn_tick_late_init(void)
+{
+       unsigned long flags;
+
+       if (!cur_timer->get_hw_time)
+               return -ENODEV;
+       write_seqlock_irqsave(&xtime_lock, flags);
+       last_tick = cur_timer->get_hw_time();
+       dyn_tick->skip = 1;
+       if (!cpu_has_local_apic())
+               dyn_tick->max_skip = 0xffff/LATCH;      /* PIT timer length */
+       printk(KERN_INFO "dyn-tick: Maximum ticks to skip limited to %i\n",
+              dyn_tick->max_skip);
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       if (cur_timer->late_init)
+               cur_timer->late_init();
+       dyn_tick->interrupt = dyn_tick_timer_interrupt;
+       replace_timer_interrupt(dyn_tick->interrupt);
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       dyn_tick->state |= DYN_TICK_ENABLED;
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return 0;
+}
+#endif /* CONFIG_NO_IDLE_HZ */
+
 void __init time_init(void)
 {
 #ifdef CONFIG_HPET_TIMER
@@ -471,6 +596,15 @@
 
        cur_timer = select_timer();
        printk(KERN_INFO "Using %s for high-res timesource\n",cur_timer->name);
+
+#ifdef CONFIG_NO_IDLE_HZ
+       if (strncmp(cur_timer->name, "tsc", 3) == 0 ||
+           strncmp(cur_timer->name, "pmtmr", 3) == 0)
+               dyn_tick->state |= DYN_TICK_SUITABLE;
+       else
+               printk(KERN_INFO "dyn-tick: Cannot use timer %s\n",
+                      cur_timer->name);
+#endif
 
        time_init_hook();
 }
diff -Nru a/arch/i386/kernel/timers/timer_pm.c 
b/arch/i386/kernel/timers/timer_pm.c
--- a/arch/i386/kernel/timers/timer_pm.c        2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/timers/timer_pm.c        2005-04-06 01:06:59 -07:00
@@ -15,6 +15,7 @@
 #include <linux/module.h>
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/dyn-tick-timer.h>
 #include <asm/types.h>
 #include <asm/timer.h>
 #include <asm/smp.h>
@@ -168,6 +169,7 @@
        monotonic_base += delta * NSEC_PER_USEC;
        write_sequnlock(&monotonic_lock);
 
+#ifndef CONFIG_NO_IDLE_HZ
        /* convert to ticks */
        delta += offset_delay;
        lost = delta / (USEC_PER_SEC / HZ);
@@ -184,6 +186,7 @@
                first_run = 0;
                offset_delay = 0;
        }
+#endif
 }
 
 
@@ -238,6 +241,25 @@
        return (unsigned long) offset_delay + cyc2us(delta);
 }
 
+static unsigned long long ns_time;
+
+static unsigned long long get_hw_time_pmtmr(void)
+{
+       u32 now, delta;
+       static unsigned int last_cycles;
+       now = read_pmtmr();
+       delta = (now - last_cycles) & ACPI_PM_MASK;
+       last_cycles = now;
+       ns_time += cyc2us(delta) * NSEC_PER_USEC;
+       return ns_time;
+}
+
+static void late_init_pmtmr(void)
+{
+       ns_time = monotonic_clock_pmtmr();
+}
+
+extern irqreturn_t pmtmr_interrupt(int irq, void *dev_id, struct pt_regs 
*regs);
 
 /* acpi timer_opts struct */
 static struct timer_opts timer_pmtmr = {
@@ -245,7 +267,9 @@
        .mark_offset            = mark_offset_pmtmr,
        .get_offset             = get_offset_pmtmr,
        .monotonic_clock        = monotonic_clock_pmtmr,
+       .get_hw_time            = get_hw_time_pmtmr,
        .delay                  = delay_pmtmr,
+       .late_init              = late_init_pmtmr,
 };
 
 struct init_timer_opts __initdata timer_pmtmr_init = {
diff -Nru a/arch/i386/kernel/timers/timer_tsc.c 
b/arch/i386/kernel/timers/timer_tsc.c
--- a/arch/i386/kernel/timers/timer_tsc.c       2005-04-06 01:06:59 -07:00
+++ b/arch/i386/kernel/timers/timer_tsc.c       2005-04-06 01:06:59 -07:00
@@ -112,6 +112,15 @@
        return delay_at_last_interrupt + edx;
 }
 
+static unsigned long get_hw_time_tsc(void)
+{
+       register unsigned long eax, edx;
+
+       unsigned long long hw_time;
+       rdtscll(hw_time);
+       return cycles_2_ns(hw_time);
+}
+
 static unsigned long long monotonic_clock_tsc(void)
 {
        unsigned long long last_offset, this_offset, base;
@@ -348,6 +357,7 @@
 
        rdtsc(last_tsc_low, last_tsc_high);
 
+#ifndef CONFIG_NO_IDLE_HZ
        spin_lock(&i8253_lock);
        outb_p(0x00, PIT_MODE);     /* latch the count ASAP */
 
@@ -415,11 +425,14 @@
                        cpufreq_delayed_get();
        } else
                lost_count = 0;
+#endif
+
        /* update the monotonic base value */
        this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
        monotonic_base += cycles_2_ns(this_offset - last_offset);
        write_sequnlock(&monotonic_lock);
 
+#ifndef CONFIG_NO_IDLE_HZ
        /* calculate delay_at_last_interrupt */
        count = ((LATCH-1) - count) * TICK_SIZE;
        delay_at_last_interrupt = (count + LATCH/2) / LATCH;
@@ -430,6 +443,7 @@
         */
        if (lost && abs(delay - delay_at_last_interrupt) > (900000/HZ))
                jiffies_64++;
+#endif
 }
 
 static int __init init_tsc(char* override)
@@ -551,6 +565,7 @@
        .mark_offset = mark_offset_tsc, 
        .get_offset = get_offset_tsc,
        .monotonic_clock = monotonic_clock_tsc,
+       .get_hw_time = get_hw_time_tsc,
        .delay = delay_tsc,
 };
 
diff -Nru a/arch/i386/mach-default/setup.c b/arch/i386/mach-default/setup.c
--- a/arch/i386/mach-default/setup.c    2005-04-06 01:06:59 -07:00
+++ b/arch/i386/mach-default/setup.c    2005-04-06 01:06:59 -07:00
@@ -85,6 +85,22 @@
        setup_irq(0, &irq0);
 }
 
+/**
+ * replace_timer_interrupt - allow replacing timer interrupt handler
+ *
+ * Description:
+ *     Can be used to replace timer interrupt handler with a more optimized
+ *     handler. Used for enabling and disabling of CONFIG_NO_IDLE_HZ.
+ */
+void replace_timer_interrupt(void * new_handler)
+{
+       unsigned long flags;
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       irq0.handler = new_handler;
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+}
+
 #ifdef CONFIG_MCA
 /**
  * mca_nmi_hook - hook into MCA specific NMI chain
diff -Nru a/include/asm-i386/timer.h b/include/asm-i386/timer.h
--- a/include/asm-i386/timer.h  2005-04-06 01:06:59 -07:00
+++ b/include/asm-i386/timer.h  2005-04-06 01:06:59 -07:00
@@ -1,6 +1,7 @@
 #ifndef _ASMi386_TIMER_H
 #define _ASMi386_TIMER_H
 #include <linux/init.h>
+#include <linux/interrupt.h>
 
 /**
  * struct timer_ops - used to define a timer source
@@ -21,7 +22,9 @@
        void (*mark_offset)(void);
        unsigned long (*get_offset)(void);
        unsigned long long (*monotonic_clock)(void);
+       unsigned long long (*get_hw_time)(void);
        void (*delay)(unsigned long);
+       void (*late_init)(void);
 };
 
 struct init_timer_opts {
diff -Nru a/include/linux/dyn-tick-timer.h b/include/linux/dyn-tick-timer.h
--- /dev/null   Wed Dec 31 16:00:00 196900
+++ b/include/linux/dyn-tick-timer.h    2005-04-06 01:06:59 -07:00
@@ -0,0 +1,74 @@
+/*
+ * linux/include/linux/dyn-tick-timer.h
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tony Lindgen <[EMAIL PROTECTED]> and
+ * Tuukka Tikkanen <[EMAIL PROTECTED]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _DYN_TICK_TIMER_H
+#define _DYN_TICK_TIMER_H
+
+#include <linux/interrupt.h>
+
+#define DYN_TICK_DEBUG         (1 << 31)
+#define DYN_TICK_TIMER_INT     (1 << 4)
+#define DYN_TICK_USE_APIC      (1 << 3)
+#define DYN_TICK_SKIPPING      (1 << 2)
+#define DYN_TICK_ENABLED       (1 << 1)
+#define DYN_TICK_SUITABLE      (1 << 0)
+
+struct dyn_tick_state {
+       unsigned int state;             /* Current state */
+       int skip_cpu;                   /* Skip handling processor */
+       unsigned long skip;             /* Ticks to skip */
+       unsigned int max_skip;          /* Max number of ticks to skip */
+       unsigned long irq_skip_mask;    /* Do not update time from these irqs */
+       irqreturn_t (*interrupt)(int, void *, struct pt_regs *);
+};
+
+struct dyn_tick_timer {
+       int (*arch_init) (void);
+       void (*arch_enable) (void);
+       void (*arch_disable) (void);
+       void (*arch_reprogram_timer) (void);
+};
+
+extern struct dyn_tick_state * dyn_tick;
+extern void dyn_tick_register(struct dyn_tick_timer * new_timer);
+
+#define NS_TICK_LEN            ((1 * 1000000000)/HZ)
+#define DYN_TICK_MIN_SKIP      2
+
+#ifdef CONFIG_NO_IDLE_HZ
+
+extern unsigned long dyn_tick_reprogram_timer(void);
+
+#else
+
+#define arch_has_safe_halt()           0
+#define dyn_tick_reprogram_timer()     {}
+
+
+#endif /* CONFIG_NO_IDLE_HZ */
+#endif /* _DYN_TICK_TIMER_H */
diff -Nru a/kernel/Makefile b/kernel/Makefile
--- a/kernel/Makefile   2005-04-06 01:06:59 -07:00
+++ b/kernel/Makefile   2005-04-06 01:06:59 -07:00
@@ -28,6 +28,7 @@
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
 obj-$(CONFIG_SECCOMP) += seccomp.o
+obj-$(CONFIG_NO_IDLE_HZ) += dyn-tick-timer.o
 
 ifneq ($(CONFIG_IA64),y)
 # According to Alan Modra <[EMAIL PROTECTED]>, the -fno-omit-frame-pointer is
diff -Nru a/kernel/dyn-tick-timer.c b/kernel/dyn-tick-timer.c
--- /dev/null   Wed Dec 31 16:00:00 196900
+++ b/kernel/dyn-tick-timer.c   2005-04-06 01:06:59 -07:00
@@ -0,0 +1,254 @@
+/*
+ * linux/arch/i386/kernel/dyn-tick.c
+ *
+ * Beginnings of generic dynamic tick timer support
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tony Lindgen <[EMAIL PROTECTED]> and
+ * Tuukka Tikkanen <[EMAIL PROTECTED]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/sysdev.h>
+#include <linux/interrupt.h>
+#include <linux/cpumask.h>
+#include <linux/pm.h>
+#include <linux/dyn-tick-timer.h>
+#include <asm/io.h>
+
+#include "io_ports.h"
+
+#define DYN_TICK_VERSION       "050301-1"
+
+struct dyn_tick_state dyn_tick_state;
+struct dyn_tick_state * dyn_tick = &dyn_tick_state;
+struct dyn_tick_timer * dyn_tick_cfg;
+
+static void (*orig_idle) (void) = 0;
+extern void disable_pit_tick(void);
+extern void reprogram_pit_tick(int jiffies_to_skip);
+extern void reprogram_apic_timer(unsigned int count);
+extern void reprogram_pit_tick(int jiffies_to_skip);
+static cpumask_t dyn_cpu_map;
+
+/*
+ * Arch independed code needed to reprogram next timer interrupt.
+ * Gets called from cpu_idle() before entering idle loop. Note that
+ * we want to have all processors idle before reprogramming the
+ * next timer interrupt.
+ */
+unsigned long dyn_tick_reprogram_timer(void)
+{
+       int cpu;
+       unsigned long flags;
+       cpumask_t idle_cpus;
+       unsigned long next;
+
+       if (dyn_tick->state & DYN_TICK_DEBUG)
+               printk("i");
+       
+       if (!(dyn_tick->state & DYN_TICK_ENABLED))
+               return 0;
+
+       /* Check if we are already skipping ticks and can idle other cpus */
+       if (dyn_tick->state & DYN_TICK_SKIPPING) {
+               reprogram_apic_timer(dyn_tick->skip);
+               return 0;
+       }
+
+       /* Check if we can start skipping ticks */
+       write_seqlock_irqsave(&xtime_lock, flags);
+       cpu = smp_processor_id();
+       cpu_set(cpu, dyn_cpu_map);
+       cpus_and(idle_cpus, dyn_cpu_map, cpu_online_map);
+       if (cpus_equal(idle_cpus, cpu_online_map)) {
+               next = next_timer_interrupt();
+               if (jiffies > next) {
+                       //printk("Too late? next: %lu jiffies: %lu\n",
+                       //       next, jjiffies);
+                       dyn_tick->skip = 1;
+               } else
+                       dyn_tick->skip = next_timer_interrupt() - jiffies;
+               if (dyn_tick->skip > DYN_TICK_MIN_SKIP) {
+                       if (dyn_tick->skip > dyn_tick->max_skip)
+                               dyn_tick->skip = dyn_tick->max_skip;
+
+                       dyn_tick_cfg->arch_reprogram_timer();
+
+                       dyn_tick->skip_cpu = cpu;
+                       dyn_tick->state |= DYN_TICK_SKIPPING;
+               }
+               cpus_clear(dyn_cpu_map);
+       }
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return dyn_tick->skip;
+}
+
+void __init dyn_tick_register(struct dyn_tick_timer * arch_timer)
+{
+       dyn_tick_cfg = arch_timer;
+       printk(KERN_INFO "dyn-tick: Registering dynamic tick timer v%s\n",
+              DYN_TICK_VERSION);
+}
+
+/*
+ * ---------------------------------------------------------------------------
+ * Sysfs interface
+ * ---------------------------------------------------------------------------
+ */
+
+extern struct sys_device device_timer;
+
+static ssize_t show_dyn_tick_state(struct sys_device *dev, char *buf)
+{
+       return sprintf(buf, "suitable:\t%i\n"
+                      "enabled:\t%i\n"
+                      "skipping:\t%i\n"
+                      "using APIC:\t%i\n"
+                      "int enabled:\t%i\n"
+                      "debug:\t\t%i\n",
+                      dyn_tick->state & DYN_TICK_SUITABLE,
+                      (dyn_tick->state & DYN_TICK_ENABLED) >> 1,
+                      (dyn_tick->state & DYN_TICK_SKIPPING) >> 2,
+                      (dyn_tick->state & DYN_TICK_USE_APIC) >> 3,
+                      (dyn_tick->state & DYN_TICK_TIMER_INT) >> 4,
+                      (dyn_tick->state & DYN_TICK_DEBUG) >> 31);
+}
+
+static ssize_t set_dyn_tick_state(struct sys_device *dev, const char * buf,
+                               ssize_t count)
+{
+       unsigned long flags;
+       unsigned int enable = simple_strtoul(buf, NULL, 2);
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       if (enable) {
+               if (dyn_tick_cfg->arch_enable)
+                       dyn_tick_cfg->arch_enable();
+               dyn_tick->state |= DYN_TICK_ENABLED;
+       } else {
+               if (dyn_tick_cfg->arch_disable)
+                       dyn_tick_cfg->arch_disable();
+               dyn_tick->state &= ~DYN_TICK_ENABLED;
+       }
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return count;
+}
+
+static SYSDEV_ATTR(dyn_tick_state, 0644, show_dyn_tick_state,
+                  set_dyn_tick_state);
+
+static ssize_t show_dyn_tick_int(struct sys_device *dev, char *buf)
+{
+       return sprintf(buf, "%i\n",
+                      (dyn_tick->state & DYN_TICK_TIMER_INT) >> 4);
+}
+
+static ssize_t set_dyn_tick_int(struct sys_device *dev, const char * buf,
+                               ssize_t count)
+{
+       unsigned long flags;
+       unsigned int enable = simple_strtoul(buf, NULL, 2);
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       if (enable)
+               dyn_tick->state |= DYN_TICK_TIMER_INT;
+       else
+               dyn_tick->state &= ~DYN_TICK_TIMER_INT;
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return count;
+}
+
+static SYSDEV_ATTR(dyn_tick_int, 0644, show_dyn_tick_int, set_dyn_tick_int);
+
+static ssize_t show_dyn_tick_dbg(struct sys_device *dev, char *buf)
+{
+       return sprintf(buf, "%i\n",
+                      (dyn_tick->state & DYN_TICK_DEBUG) >> 31);
+}
+
+static ssize_t set_dyn_tick_dbg(struct sys_device *dev, const char * buf,
+                               ssize_t count)
+{
+       unsigned long flags;
+       unsigned int enable = simple_strtoul(buf, NULL, 2);
+
+       write_seqlock_irqsave(&xtime_lock, flags);
+       if (enable)
+               dyn_tick->state |= DYN_TICK_DEBUG;
+       else
+               dyn_tick->state &= ~DYN_TICK_DEBUG;
+       write_sequnlock_irqrestore(&xtime_lock, flags);
+
+       return count;
+}
+
+static SYSDEV_ATTR(dyn_tick_dbg, 0644, show_dyn_tick_dbg, set_dyn_tick_dbg);
+
+/*
+ * ---------------------------------------------------------------------------
+ * Init functions
+ * ---------------------------------------------------------------------------
+ */
+
+static int __init dyn_tick_early_init(void)
+{
+       dyn_tick->state |= DYN_TICK_TIMER_INT;
+}
+
+subsys_initcall(dyn_tick_early_init);
+
+/*
+ * We need to initialize dynamic tick after calibrate delay
+ */
+static int __init dyn_tick_late_init(void)
+{
+       int ret = 0;
+
+       ret = sysdev_create_file(&device_timer, &attr_dyn_tick_state);
+       ret = sysdev_create_file(&device_timer, &attr_dyn_tick_int);
+       ret = sysdev_create_file(&device_timer, &attr_dyn_tick_dbg);
+
+       if (dyn_tick_cfg->arch_init == NULL ||
+           !(dyn_tick->state & DYN_TICK_SUITABLE))
+               return -ENODEV;
+
+       ret = dyn_tick_cfg->arch_init();
+       if (ret != 0) {
+               printk(KERN_WARNING "dyn-tick: Init failed\n");
+               return -ENODEV;
+       }
+
+       printk(KERN_INFO "dyn-tick: Timer using dynamic tick\n");
+
+       return ret;
+}
+
+late_initcall(dyn_tick_late_init);

Reply via email to