Skip to content

Commit 428c7e1

Browse files
dwmw2smb49
authored andcommitted
clockevents/drivers/i8253: Fix stop sequence for timer 0
BugLink: https://bugs.launchpad.net/bugs/2109301 commit 531b2ca upstream. According to the data sheet, writing the MODE register should stop the counter (and thus the interrupts). This appears to work on real hardware, at least modern Intel and AMD systems. It should also work on Hyper-V. However, on some buggy virtual machines the mode change doesn't have any effect until the counter is subsequently loaded (or perhaps when the IRQ next fires). So, set MODE 0 and then load the counter, to ensure that those buggy VMs do the right thing and the interrupts stop. And then write MODE 0 *again* to stop the counter on compliant implementations too. Apparently, Hyper-V keeps firing the IRQ *repeatedly* even in mode zero when it should only happen once, but the second MODE write stops that too. Userspace test program (mostly written by tglx): ===== #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stdint.h> #include <sys/io.h> static __always_inline void __out##bwl(type value, uint16_t port) \ { \ asm volatile("out" #bwl " %" #bw "0, %w1" \ : : "a"(value), "Nd"(port)); \ } \ \ static __always_inline type __in##bwl(uint16_t port) \ { \ type value; \ asm volatile("in" #bwl " %w1, %" #bw "0" \ : "=a"(value) : "Nd"(port)); \ return value; \ } BUILDIO(b, b, uint8_t) #define inb __inb #define outb __outb #define PIT_MODE 0x43 #define PIT_CH0 0x40 #define PIT_CH2 0x42 static int is8254; static void dump_pit(void) { if (is8254) { // Latch and output counter and status outb(0xC2, PIT_MODE); printf("%02x %02x %02x\n", inb(PIT_CH0), inb(PIT_CH0), inb(PIT_CH0)); } else { // Latch and output counter outb(0x0, PIT_MODE); printf("%02x %02x\n", inb(PIT_CH0), inb(PIT_CH0)); } } int main(int argc, char* argv[]) { int nr_counts = 2; if (argc > 1) nr_counts = atoi(argv[1]); if (argc > 2) is8254 = 1; if (ioperm(0x40, 4, 1) != 0) return 1; dump_pit(); printf("Set oneshot\n"); outb(0x38, PIT_MODE); outb(0x00, PIT_CH0); outb(0x0F, PIT_CH0); dump_pit(); usleep(1000); dump_pit(); printf("Set periodic\n"); outb(0x34, PIT_MODE); outb(0x00, PIT_CH0); outb(0x0F, PIT_CH0); dump_pit(); usleep(1000); dump_pit(); dump_pit(); usleep(100000); dump_pit(); usleep(100000); dump_pit(); printf("Set stop (%d counter writes)\n", nr_counts); outb(0x30, PIT_MODE); while (nr_counts--) outb(0xFF, PIT_CH0); dump_pit(); usleep(100000); dump_pit(); usleep(100000); dump_pit(); printf("Set MODE 0\n"); outb(0x30, PIT_MODE); dump_pit(); usleep(100000); dump_pit(); usleep(100000); dump_pit(); return 0; } ===== Suggested-by: Sean Christopherson <[email protected]> Co-developed-by: Li RongQing <[email protected]> Signed-off-by: Li RongQing <[email protected]> Signed-off-by: David Woodhouse <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Tested-by: Michael Kelley <[email protected]> Link: https://lore.kernel.org/all/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]> Signed-off-by: Noah Wager <[email protected]> Signed-off-by: Stefan Bader <[email protected]>
1 parent 980914e commit 428c7e1

File tree

3 files changed

+25
-23
lines changed

3 files changed

+25
-23
lines changed

arch/x86/kernel/cpu/mshyperv.c

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include <linux/interrupt.h>
1717
#include <linux/irq.h>
1818
#include <linux/kexec.h>
19-
#include <linux/i8253.h>
2019
#include <linux/random.h>
2120
#include <asm/processor.h>
2221
#include <asm/hypervisor.h>
@@ -594,16 +593,6 @@ static void __init ms_hyperv_init_platform(void)
594593
if (efi_enabled(EFI_BOOT))
595594
x86_platform.get_nmi_reason = hv_get_nmi_reason;
596595

597-
/*
598-
* Hyper-V VMs have a PIT emulation quirk such that zeroing the
599-
* counter register during PIT shutdown restarts the PIT. So it
600-
* continues to interrupt @18.2 HZ. Setting i8253_clear_counter
601-
* to false tells pit_shutdown() not to zero the counter so that
602-
* the PIT really is shutdown. Generation 2 VMs don't have a PIT,
603-
* and setting this value has no effect.
604-
*/
605-
i8253_clear_counter_on_shutdown = false;
606-
607596
#if IS_ENABLED(CONFIG_HYPERV)
608597
if ((hv_get_isolation_type() == HV_ISOLATION_TYPE_VBS) ||
609598
ms_hyperv.paravisor_present)

drivers/clocksource/i8253.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@
2020
DEFINE_RAW_SPINLOCK(i8253_lock);
2121
EXPORT_SYMBOL(i8253_lock);
2222

23-
/*
24-
* Handle PIT quirk in pit_shutdown() where zeroing the counter register
25-
* restarts the PIT, negating the shutdown. On platforms with the quirk,
26-
* platform specific code can set this to false.
27-
*/
28-
bool i8253_clear_counter_on_shutdown __ro_after_init = true;
29-
3023
#ifdef CONFIG_CLKSRC_I8253
3124
/*
3225
* Since the PIT overflows every tick, its not very useful
@@ -112,12 +105,33 @@ void clockevent_i8253_disable(void)
112105
{
113106
raw_spin_lock(&i8253_lock);
114107

108+
/*
109+
* Writing the MODE register should stop the counter, according to
110+
* the datasheet. This appears to work on real hardware (well, on
111+
* modern Intel and AMD boxes; I didn't dig the Pegasos out of the
112+
* shed).
113+
*
114+
* However, some virtual implementations differ, and the MODE change
115+
* doesn't have any effect until either the counter is written (KVM
116+
* in-kernel PIT) or the next interrupt (QEMU). And in those cases,
117+
* it may not stop the *count*, only the interrupts. Although in
118+
* the virt case, that probably doesn't matter, as the value of the
119+
* counter will only be calculated on demand if the guest reads it;
120+
* it's the interrupts which cause steal time.
121+
*
122+
* Hyper-V apparently has a bug where even in mode 0, the IRQ keeps
123+
* firing repeatedly if the counter is running. But it *does* do the
124+
* right thing when the MODE register is written.
125+
*
126+
* So: write the MODE and then load the counter, which ensures that
127+
* the IRQ is stopped on those buggy virt implementations. And then
128+
* write the MODE again, which is the right way to stop it.
129+
*/
115130
outb_p(0x30, PIT_MODE);
131+
outb_p(0, PIT_CH0);
132+
outb_p(0, PIT_CH0);
116133

117-
if (i8253_clear_counter_on_shutdown) {
118-
outb_p(0, PIT_CH0);
119-
outb_p(0, PIT_CH0);
120-
}
134+
outb_p(0x30, PIT_MODE);
121135

122136
raw_spin_unlock(&i8253_lock);
123137
}

include/linux/i8253.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#define PIT_LATCH ((PIT_TICK_RATE + HZ/2) / HZ)
2222

2323
extern raw_spinlock_t i8253_lock;
24-
extern bool i8253_clear_counter_on_shutdown;
2524
extern struct clock_event_device i8253_clockevent;
2625
extern void clockevent_i8253_init(bool oneshot);
2726
extern void clockevent_i8253_disable(void);

0 commit comments

Comments
 (0)