224 lines
6.8 KiB
Diff
224 lines
6.8 KiB
Diff
|
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,
|