Blob Blame History Raw
From: Jiri Bohac <jbohac@suse.cz>
Subject: allow 64-bit mode for HPET Timer0
References: bnc#456700

The kernel uses the HPET timers in 32-bit mode for clock-events.
While 32 bits, with a wrap-around time of >4 minutes, is probably
good enough for the clock-event purposes, on some chipsets this
has a negative side-effect on the HPET main counter.

Unlike the original HPET specification 1.0 from 2004, which does not
mention any side-effects of setting TN_32MODE_CNF on the
individual timers, the ICH9 documentation, for example, says:

    NOTE: When this bit is set to ‘1’, the hardware counter will
    do a 32-bit operation on comparator match and rollovers, thus
    the upper 32-bit of the Timer 0 Comparator Value register is
    ignored. The upper 32-bit of the main counter is not involved
    in any rollover from lower 32-bit of the main counter and
    becomes all zeros.

(see http://www.intel.com/assets/pdf/datasheet/316972.pdf, page
819, section 21.1.5, Bit 8). I've seen this behaviour also on
ICH8. I have no idea what other chipsets are affected. But I have
seen AMD chipsets that Do The Right Thing.

This means, that when the kernel configures the Timer 0 to 32-bit
mode, on these chipsets it also cripples the 64-bit main counter
to 32 bits.

The HPET may be mmapped in userspace and the main counter
accessed directly by applications, expecting a 64-bit main
counter.

This patch allows the Timer0 to be configured in 64-bit mode
on x86_64 when a hpet64 command-line option is specified.

Updated-by: Jeff Mahoney <jeffm@suse.com>
Updated-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Jiri Bohac <jbohac@suse.cz>

---
 Documentation/kernel-parameters.txt |    2 
 arch/x86/kernel/hpet.c              |   86 ++++++++++++++++++++++++++++++++----
 2 files changed, 80 insertions(+), 8 deletions(-)

Index: linux-3.0-tmp-jikos/Documentation/kernel-parameters.txt
===================================================================
--- linux-3.0-tmp-jikos.orig/Documentation/kernel-parameters.txt
+++ linux-3.0-tmp-jikos/Documentation/kernel-parameters.txt
@@ -494,6 +494,8 @@ bytes respectively. Such letter suffixes
 			Range: 0 - 8192
 			Default: 64
 
+	hpet64		[X86-64,HPET] enable 64-bit mode of the HPET timer (bnc#456700)
+
 	com20020=	[HW,NET] ARCnet - COM20020 chipset
 			Format:
 			<io>[,<irq>[,<nodeID>[,<backplane>[,<ckp>[,<timeout>]]]]]
Index: linux-3.0-tmp-jikos/arch/x86/kernel/hpet.c
===================================================================
--- linux-3.0-tmp-jikos.orig/arch/x86/kernel/hpet.c
+++ linux-3.0-tmp-jikos/arch/x86/kernel/hpet.c
@@ -43,6 +43,7 @@ u8					hpet_msi_disable;
 static unsigned long			hpet_num_timers;
 #endif
 static void __iomem			*hpet_virt_address;
+static int hpet_legacy_use_64_bits;
 
 struct hpet_dev {
 	struct clock_event_device	evt;
@@ -65,6 +66,33 @@ static inline void hpet_writel(unsigned
 
 #ifdef CONFIG_X86_64
 #include <asm/pgtable.h>
+static inline unsigned long hpet_read_value(unsigned long a)
+{
+	if (hpet_legacy_use_64_bits)
+		return readq(hpet_virt_address + a);
+	else
+		return readl(hpet_virt_address + a);
+}
+
+static void hpet_write_value(unsigned long d, unsigned long a)
+{
+	if (hpet_legacy_use_64_bits)
+		writeq(d, hpet_virt_address + a);
+	else
+		writel(d, hpet_virt_address + a);
+}
+
+#else
+
+static inline unsigned long hpet_read_value(unsigned long a)
+{
+	return readl(hpet_virt_address + a);
+}
+
+static void hpet_write_value(unsigned long d, unsigned long a)
+{
+	writel(d, hpet_virt_address + a);
+}
 #endif
 
 static inline void hpet_set_mapping(void)
@@ -109,6 +137,17 @@ static int __init disable_hpet(char *str
 }
 __setup("nohpet", disable_hpet);
 
+#ifdef CONFIG_X86_64
+static int hpet64 = 0;
+static int __init hpet64_setup(char *str)
+{
+	hpet64 = 1;
+	return 1;
+}
+__setup("hpet64", hpet64_setup);
+#endif
+
+
 static inline int is_hpet_capable(void)
 {
 	return !boot_hpet_disable && hpet_address;
@@ -218,6 +257,7 @@ static void hpet_reserve_platform_timers
  * Common hpet info
  */
 static unsigned long hpet_freq;
+static int hpet_legacy_use_64_bits; /* configure T0 in 64-bit mode? */
 
 static void hpet_legacy_set_mode(enum clock_event_mode mode,
 			  struct clock_event_device *evt);
@@ -283,10 +323,38 @@ static void hpet_enable_legacy_int(void)
 	hpet_legacy_int_enabled = 1;
 }
 
+static int timer0_use_64_bits(void)
+{
+#ifndef CONFIG_X86_64
+	/* using the HPET in 64-bit mode without atomic 64-bit
+	 * accesses is too inefficient
+	 */
+	return 0;
+#else
+
+	if (unlikely(hpet64)) {
+		u32 id, t0_cfg;
+		id = hpet_readl(HPET_ID);
+		t0_cfg = hpet_readl(HPET_Tn_CFG(0));
+
+		if ((id & HPET_ID_64BIT) && (t0_cfg & HPET_TN_64BIT_CAP)) {
+			printk(KERN_DEBUG "hpet timer0 configured in 64-bit mode\n");
+			return 1;
+		}
+		else {
+			printk(KERN_DEBUG "hpet timer0 does not support 64-bit mode\n");
+			return 0;
+		}
+	}
+	else return 0;
+#endif
+}
+
 static void hpet_legacy_clockevent_register(void)
 {
 	/* Start HPET legacy interrupts */
 	hpet_enable_legacy_int();
+	hpet_legacy_use_64_bits = timer0_use_64_bits();
 
 	/*
 	 * Start hpet with the boot cpu mask and make it
@@ -318,9 +386,10 @@ static void hpet_set_mode(enum clock_eve
 		/* Make sure we use edge triggered interrupts */
 		cfg &= ~HPET_TN_LEVEL;
 		cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |
-		       HPET_TN_SETVAL | HPET_TN_32BIT;
+		       HPET_TN_SETVAL |
+		       (hpet_legacy_use_64_bits ? 0 : HPET_TN_32BIT);
 		hpet_writel(cfg, HPET_Tn_CFG(timer));
-		hpet_writel(cmp, HPET_Tn_CMP(timer));
+		hpet_write_value(cmp, HPET_Tn_CMP(timer));
 		udelay(1);
 		/*
 		 * HPET on AMD 81xx needs a second write (with HPET_TN_SETVAL
@@ -329,7 +398,7 @@ static void hpet_set_mode(enum clock_eve
 		 * (See AMD-8111 HyperTransport I/O Hub Data Sheet,
 		 * Publication # 24674)
 		 */
-		hpet_writel((unsigned int) delta, HPET_Tn_CMP(timer));
+		hpet_write_value((unsigned long) delta, HPET_Tn_CMP(timer));
 		hpet_start_counter();
 		hpet_print_config();
 		break;
@@ -337,7 +406,8 @@ static void hpet_set_mode(enum clock_eve
 	case CLOCK_EVT_MODE_ONESHOT:
 		cfg = hpet_readl(HPET_Tn_CFG(timer));
 		cfg &= ~HPET_TN_PERIODIC;
-		cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
+		cfg |= HPET_TN_ENABLE |
+		       (hpet_legacy_use_64_bits ? 0 : HPET_TN_32BIT);
 		hpet_writel(cfg, HPET_Tn_CFG(timer));
 		break;
 
@@ -366,12 +436,12 @@ static void hpet_set_mode(enum clock_eve
 static int hpet_next_event(unsigned long delta,
 			   struct clock_event_device *evt, int timer)
 {
-	u32 cnt;
+	unsigned long cnt;
 	s32 res;
 
-	cnt = hpet_readl(HPET_COUNTER);
+	cnt = hpet_read_value(HPET_COUNTER);
 	cnt += (u32) delta;
-	hpet_writel(cnt, HPET_Tn_CMP(timer));
+	hpet_write_value(cnt, HPET_Tn_CMP(timer));
 
 	/*
 	 * HPETs are a complete disaster. The compare register is
@@ -395,7 +465,7 @@ static int hpet_next_event(unsigned long
 	 * the event. The minimum programming delta for the generic
 	 * clockevents code is set to 1.5 * HPET_MIN_CYCLES.
 	 */
-	res = (s32)(cnt - hpet_readl(HPET_COUNTER));
+	res = (s32)((u32)cnt - (u32)hpet_readl(HPET_COUNTER));
 
 	return res < HPET_MIN_CYCLES ? -ETIME : 0;
 }