From: Jiri Bohac 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 Signed-off-by: Jiri Bohac --- 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: [,[,[,[,[,]]]]] --- 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 +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,