[PATCH] x86_64: Add pmtimer support
authorAndi Kleen <ak@suse.de>
Tue, 17 May 2005 04:53:28 +0000 (21:53 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Tue, 17 May 2005 14:59:15 +0000 (07:59 -0700)
There are unfortunately more and more multi processor Opteron systems which
don't have HPET timer support in the southbridge.  This covers in particular
Nvidia and VIA chipsets.  They also don't guarantee that the TSCs are
synchronized between CPUs; and especially with MP powernow the systems are
nearly unusable because the time gets very inconsistent between CPUs.

The timer code for x86-64 was originally written under the assumption that we
could fall back to the HPET timer on such systems.  But this doesn't work
there.

Another alternative is to use the ACPI PM timer as primary time source.  This
patch does that.  The kernel only uses PM timer when there is no other choice
because it has some disadvantages.

Ported over from i386.  It should be faster than the i386 version because I
dropped the "read three times" workaround, but is still considerable slower
than HPET and also does not work together with vsyscalls which have to be
disabled.

Cc: <mark.langsdorf@amd.com>
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
arch/x86_64/Kconfig
arch/x86_64/kernel/Makefile
arch/x86_64/kernel/pmtimer.c [new file with mode: 0644]
arch/x86_64/kernel/time.c
arch/x86_64/kernel/vsyscall.c
include/asm-x86_64/proto.h
include/asm-x86_64/vsyscall.h

index 44ee7f6..82cb2a3 100644 (file)
@@ -303,6 +303,20 @@ config HPET_TIMER
          as it is off-chip.  You can find the HPET spec at
          <http://www.intel.com/labs/platcomp/hpet/hpetspec.htm>.
 
+config X86_PM_TIMER
+       bool "PM timer"
+       default y
+       help
+         Support the ACPI PM timer for time keeping. This is slow,
+         but is useful on some chipsets without HPET on systems with more
+         than one CPU. On a single processor or single socket multi core
+         system it is normally not required.
+         When the PM timer is active 64bit vsyscalls are disabled
+         and should not be enabled (/proc/sys/kernel/vsyscall64 should
+         not be changed).
+         The kernel selects the PM timer only as a last resort, so it is
+         useful to enable just in case.
+
 config HPET_EMULATE_RTC
        bool "Provide RTC interrupt"
        depends on HPET_TIMER && RTC=y
index 0a3318e..5ca4a45 100644 (file)
@@ -28,6 +28,7 @@ obj-$(CONFIG_GART_IOMMU)      += pci-gart.o aperture.o
 obj-$(CONFIG_DUMMY_IOMMU)      += pci-nommu.o pci-dma.o
 obj-$(CONFIG_SWIOTLB)          += swiotlb.o
 obj-$(CONFIG_KPROBES)          += kprobes.o
+obj-$(CONFIG_X86_PM_TIMER)     += pmtimer.o
 
 obj-$(CONFIG_MODULES)          += module.o
 
diff --git a/arch/x86_64/kernel/pmtimer.c b/arch/x86_64/kernel/pmtimer.c
new file mode 100644 (file)
index 0000000..feb5f10
--- /dev/null
@@ -0,0 +1,101 @@
+/* Ported over from i386 by AK, original copyright was:
+ *
+ * (C) Dominik Brodowski <linux@brodo.de> 2003
+ *
+ * Driver to use the Power Management Timer (PMTMR) available in some
+ * southbridges as primary timing source for the Linux kernel.
+ *
+ * Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c,
+ * timer_hpet.c, and on Arjan van de Ven's implementation for 2.4.
+ *
+ * This file is licensed under the GPL v2.
+ *
+ * Dropped all the hardware bug workarounds for now. Hopefully they
+ * are not needed on 64bit chipsets.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/cpumask.h>
+#include <asm/io.h>
+#include <asm/proto.h>
+#include <asm/msr.h>
+#include <asm/vsyscall.h>
+
+/* The I/O port the PMTMR resides at.
+ * The location is detected during setup_arch(),
+ * in arch/i386/kernel/acpi/boot.c */
+u32 pmtmr_ioport;
+
+/* value of the Power timer at last timer interrupt */
+static u32 offset_delay;
+static u32 last_pmtmr_tick;
+
+#define ACPI_PM_MASK 0xFFFFFF /* limit it to 24 bits */
+
+static inline u32 cyc2us(u32 cycles)
+{
+       /* The Power Management Timer ticks at 3.579545 ticks per microsecond.
+        * 1 / PM_TIMER_FREQUENCY == 0.27936511 =~ 286/1024 [error: 0.024%]
+        *
+        * Even with HZ = 100, delta is at maximum 35796 ticks, so it can
+        * easily be multiplied with 286 (=0x11E) without having to fear
+        * u32 overflows.
+        */
+       cycles *= 286;
+       return (cycles >> 10);
+}
+
+int pmtimer_mark_offset(void)
+{
+       static int first_run = 1;
+       unsigned long tsc;
+       u32 lost;
+
+       u32 tick = inl(pmtmr_ioport);
+       u32 delta;
+
+       delta = cyc2us((tick - last_pmtmr_tick) & ACPI_PM_MASK);
+
+       last_pmtmr_tick = tick;
+       monotonic_base += delta * NSEC_PER_USEC;
+
+       delta += offset_delay;
+
+       lost = delta / (USEC_PER_SEC / HZ);
+       offset_delay = delta % (USEC_PER_SEC / HZ);
+
+       rdtscll(tsc);
+       vxtime.last_tsc = tsc - offset_delay * cpu_khz;
+
+       /* don't calculate delay for first run,
+          or if we've got less then a tick */
+       if (first_run || (lost < 1)) {
+               first_run = 0;
+               offset_delay = 0;
+       }
+
+       return lost - 1;
+}
+
+unsigned int do_gettimeoffset_pm(void)
+{
+       u32 now, offset, delta = 0;
+
+       offset = last_pmtmr_tick;
+       now = inl(pmtmr_ioport);
+       delta = (now - offset) & ACPI_PM_MASK;
+
+       return offset_delay + cyc2us(delta);
+}
+
+
+static int __init nopmtimer_setup(char *s)
+{
+       pmtmr_ioport = 0;
+       return 0;
+}
+
+__setup("nopmtimer", nopmtimer_setup);
index 0e3b60e..735b676 100644 (file)
@@ -26,6 +26,8 @@
 #include <linux/sysdev.h>
 #include <linux/bcd.h>
 #include <linux/kallsyms.h>
+#include <linux/acpi.h>
+#include <acpi/achware.h>      /* for PM timer frequency */
 #include <asm/8253pit.h>
 #include <asm/pgtable.h>
 #include <asm/vsyscall.h>
@@ -396,6 +398,10 @@ static irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
                        (offset - vxtime.last)*(NSEC_PER_SEC/HZ) / hpet_tick;
 
                vxtime.last = offset;
+#ifdef CONFIG_X86_PM_TIMER
+       } else if (vxtime.mode == VXTIME_PMTMR) {
+               lost = pmtimer_mark_offset();
+#endif
        } else {
                offset = (((tsc - vxtime.last_tsc) *
                           vxtime.tsc_quot) >> 32) - (USEC_PER_SEC / HZ);
@@ -898,6 +904,13 @@ void __init time_init(void)
                        hpet_period;
                cpu_khz = hpet_calibrate_tsc();
                timename = "HPET";
+#ifdef CONFIG_X86_PM_TIMER
+       } else if (pmtmr_ioport) {
+               vxtime_hz = PM_TIMER_FREQUENCY;
+               timename = "PM";
+               pit_init();
+               cpu_khz = pit_calibrate_tsc();
+#endif
        } else {
                pit_init();
                cpu_khz = pit_calibrate_tsc();
@@ -922,6 +935,27 @@ void __init time_init(void)
 #endif
 }
 
+/*
+ * Make an educated guess if the TSC is trustworthy and synchronized
+ * over all CPUs.
+ */
+static __init int unsynchronized_tsc(void)
+{
+#ifdef CONFIG_SMP
+       if (oem_force_hpet_timer())
+               return 1;
+       /* Intel systems are normally all synchronized. Exceptions
+          are handled in the OEM check above. */
+       if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL)
+               return 0;
+       /* All in a single socket - should be synchronized */
+       if (cpus_weight(cpu_core_map[0]) == num_online_cpus())
+               return 0;
+#endif
+       /* Assume multi socket systems are not synchronized */
+       return num_online_cpus() > 1;
+}
+
 /*
  * Decide after all CPUs are booted what mode gettimeofday should use.
  */
@@ -929,29 +963,23 @@ void __init time_init_gtod(void)
 {
        char *timetype;
 
-       /*
-        * AMD systems with more than one CPU don't have fully synchronized
-        * TSCs. Always use HPET gettimeofday for these, although it is slower.
-        * Intel SMP systems usually have synchronized TSCs, so use always
-        * the TSC.
-        *
-        * Exceptions:
-        * IBM Summit2 checked by oem_force_hpet_timer().
-        * AMD dual core may also not need HPET. Check me.
-        *
-        * Can be turned off with "notsc".
-        */
-       if (num_online_cpus() > 1 &&
-           boot_cpu_data.x86_vendor == X86_VENDOR_AMD)
-               notsc = 1;
-       /* Some systems will want to disable TSC and use HPET. */
-       if (oem_force_hpet_timer())
+       if (unsynchronized_tsc())
                notsc = 1;
        if (vxtime.hpet_address && notsc) {
                timetype = "HPET";
                vxtime.last = hpet_readl(HPET_T0_CMP) - hpet_tick;
                vxtime.mode = VXTIME_HPET;
                do_gettimeoffset = do_gettimeoffset_hpet;
+#ifdef CONFIG_X86_PM_TIMER
+       /* Using PM for gettimeofday is quite slow, but we have no other
+          choice because the TSC is too unreliable on some systems. */
+       } else if (pmtmr_ioport && !vxtime.hpet_address && notsc) {
+               timetype = "PM";
+               do_gettimeoffset = do_gettimeoffset_pm;
+               vxtime.mode = VXTIME_PMTMR;
+               sysctl_vsyscall = 0;
+               printk(KERN_INFO "Disabling vsyscall due to use of PM timer\n");
+#endif
        } else {
                timetype = vxtime.hpet_address ? "HPET/TSC" : "PIT/TSC";
                vxtime.mode = VXTIME_TSC;
index b4b8dc5..1a75414 100644 (file)
@@ -65,7 +65,7 @@ static force_inline void do_vgettimeofday(struct timeval * tv)
                usec = (__xtime.tv_nsec / 1000) +
                        (__jiffies - __wall_jiffies) * (1000000 / HZ);
 
-               if (__vxtime.mode == VXTIME_TSC) {
+               if (__vxtime.mode != VXTIME_HPET) {
                        sync_core();
                        rdtscll(t);
                        if (t < __vxtime.last_tsc)
@@ -217,7 +217,6 @@ static int __init vsyscall_init(void)
        BUG_ON((unsigned long) &vtime != VSYSCALL_ADDR(__NR_vtime));
        BUG_ON((VSYSCALL_ADDR(0) != __fix_to_virt(VSYSCALL_FIRST_PAGE)));
        map_vsyscall();
-       sysctl_vsyscall = 1;
        register_sysctl_table(kernel_root_table2, 0);
        return 0;
 }
index d0f8f8b..f2f0736 100644 (file)
@@ -30,6 +30,11 @@ extern void ia32_syscall(void);
 extern void iommu_hole_init(void);
 
 extern void time_init_gtod(void);
+extern int pmtimer_mark_offset(void);
+extern unsigned int do_gettimeoffset_pm(void);
+extern u32 pmtmr_ioport;
+extern unsigned long long monotonic_base;
+extern int sysctl_vsyscall;
 
 extern void do_softirq_thunk(void);
 
index b0c8d43..2872da2 100644 (file)
@@ -25,6 +25,7 @@ enum vsyscall_num {
 
 #define VXTIME_TSC     1
 #define VXTIME_HPET    2
+#define VXTIME_PMTMR   3
 
 struct vxtime_data {
        long hpet_address;      /* HPET base address */
@@ -54,6 +55,8 @@ extern struct timezone sys_tz;
 extern int sysctl_vsyscall;
 extern seqlock_t xtime_lock;
 
+extern int sysctl_vsyscall;
+
 #define ARCH_HAVE_XTIME_LOCK 1
 
 #endif /* __KERNEL__ */