KVM: PIT: fix count read and mode 0 handling
authorMarcelo Tosatti <mtosatti@redhat.com>
Wed, 8 Apr 2009 16:14:19 +0000 (13:14 -0300)
committerAvi Kivity <avi@redhat.com>
Wed, 10 Jun 2009 08:48:39 +0000 (11:48 +0300)
Commit 46ee278652f4cbd51013471b64c7897ba9bcd1b1 causes Solaris 10
to hang on boot.

Assuming that PIT counter reads should return 0 for an expired timer
is wrong: when it is active, the counter never stops (see comment on
__kpit_elapsed).

Also arm a one shot timer for mode 0.

Signed-off-by: Marcelo Tosatti <mtosatti@redhat.com>
Signed-off-by: Avi Kivity <avi@redhat.com>
arch/x86/kvm/i8254.c

index cf09bb64f141130bf0449b1cbe3b16e1bb88496e..4d6f0d293ee29bb303dbe5e4b81b4cb3c9e2b6e2 100644 (file)
@@ -104,13 +104,18 @@ static s64 __kpit_elapsed(struct kvm *kvm)
        ktime_t remaining;
        struct kvm_kpit_state *ps = &kvm->arch.vpit->pit_state;
 
+       /*
+        * The Counter does not stop when it reaches zero. In
+        * Modes 0, 1, 4, and 5 the Counter ``wraps around'' to
+        * the highest count, either FFFF hex for binary counting
+        * or 9999 for BCD counting, and continues counting.
+        * Modes 2 and 3 are periodic; the Counter reloads
+        * itself with the initial count and continues counting
+        * from there.
+        */
        remaining = hrtimer_expires_remaining(&ps->pit_timer.timer);
-       if (ktime_to_ns(remaining) < 0)
-               remaining = ktime_set(0, 0);
-
-       elapsed = ps->pit_timer.period;
-       if (ktime_to_ns(remaining) <= ps->pit_timer.period)
-               elapsed = ps->pit_timer.period - ktime_to_ns(remaining);
+       elapsed = ps->pit_timer.period - ktime_to_ns(remaining);
+       elapsed = mod_64(elapsed, ps->pit_timer.period);
 
        return elapsed;
 }
@@ -280,7 +285,7 @@ static void create_pit_timer(struct kvm_kpit_state *ps, u32 val, int is_period)
 
        /* TODO The new value only affected after the retriggered */
        hrtimer_cancel(&pt->timer);
-       pt->period = (is_period == 0) ? 0 : interval;
+       pt->period = interval;
        ps->is_periodic = is_period;
 
        pt->timer.function = kvm_timer_fn;
@@ -304,10 +309,8 @@ static void pit_load_count(struct kvm *kvm, int channel, u32 val)
        pr_debug("pit: load_count val is %d, channel is %d\n", val, channel);
 
        /*
-        * Though spec said the state of 8254 is undefined after power-up,
-        * seems some tricky OS like Windows XP depends on IRQ0 interrupt
-        * when booting up.
-        * So here setting initialize rate for it, and not a specific number
+        * The largest possible initial count is 0; this is equivalent
+        * to 216 for binary counting and 104 for BCD counting.
         */
        if (val == 0)
                val = 0x10000;
@@ -322,6 +325,7 @@ static void pit_load_count(struct kvm *kvm, int channel, u32 val)
        /* Two types of timer
         * mode 1 is one shot, mode 2 is period, otherwise del timer */
        switch (ps->channels[0].mode) {
+       case 0:
        case 1:
         /* FIXME: enhance mode 4 precision */
        case 4: