qubes-linux-kernel/patches.arch/x86_64-hpet-64bit-timer.patch
2010-07-07 13:12:45 +02:00

224 lines
6.8 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
Signed-off-by: Jiri Bohac <jbohac@suse.cz>
---
Documentation/kernel-parameters.txt | 2
arch/x86/kernel/hpet.c | 88 ++++++++++++++++++++++++++++++++----
2 files changed, 81 insertions(+), 9 deletions(-)
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -497,6 +497,8 @@ and is between 256 and 4096 characters.
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>]]]]]
--- a/arch/x86/kernel/hpet.c
+++ b/arch/x86/kernel/hpet.c
@@ -37,6 +37,7 @@ unsigned long hpet_address;
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;
@@ -59,6 +60,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)
@@ -103,6 +131,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;
@@ -212,6 +251,7 @@ static void hpet_reserve_platform_timers
* Common hpet info
*/
static unsigned long hpet_period;
+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);
@@ -278,10 +318,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();
/*
* The mult factor is defined as (include/linux/clockchips.h)
@@ -328,9 +396,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
@@ -339,7 +408,7 @@ static void hpet_set_mode(enum clock_eve
* (See AMD-8111 HyperTransport I/O Hub Data Sheet,
* Publication # 24674)
*/
- hpet_writel((unsigned long) delta, HPET_Tn_CMP(timer));
+ hpet_write_value((unsigned long) delta, HPET_Tn_CMP(timer));
hpet_start_counter();
hpet_print_config();
break;
@@ -347,7 +416,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;
@@ -376,11 +446,11 @@ 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;
- 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));
hpet_readl(HPET_Tn_CMP(timer)); /* pre-read for bnc#433746 */
/*
@@ -388,9 +458,9 @@ static int hpet_next_event(unsigned long
* what we wrote hit the chip before we compare it to the
* counter.
*/
- WARN_ON_ONCE((u32)hpet_readl(HPET_Tn_CMP(timer)) != cnt);
+ WARN_ON_ONCE((u32)hpet_readl(HPET_Tn_CMP(timer)) != (u32)cnt);
- return (s32)((u32)hpet_readl(HPET_COUNTER) - cnt) >= 0 ? -ETIME : 0;
+ return (s32)((u32)hpet_readl(HPET_COUNTER) - (u32)cnt) >= 0 ? -ETIME : 0;
}
static void hpet_legacy_set_mode(enum clock_event_mode mode,