]> xenbits.xensource.com Git - xen.git/commitdiff
x86/vhpet: add support for level triggered interrupts
authorRoger Pau Monné <roger.pau@citrix.com>
Tue, 24 Jul 2018 13:54:18 +0000 (15:54 +0200)
committerJan Beulich <jbeulich@suse.com>
Tue, 24 Jul 2018 13:54:18 +0000 (15:54 +0200)
Level triggered interrupts are not an optional feature of HPET, and
must be implemented in order to comply with the HPET specification.

Implement them by adding a callback to the timer which sets the
interrupt bit in the general interrupt status register. Further
interrupts (in case of periodic mode) will not be injected until the
bit is cleared.

In order to reset the interrupts when the status bit is clear Xen must
also detect accesses to such register.

While there convert tn and i in hpet_write to unsigned.

Reported-by: Stefan Bader <stefan.bader@canonical.com>
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
xen/arch/x86/hvm/hpet.c
xen/arch/x86/hvm/irq.c
xen/include/asm-x86/hvm/irq.h

index b7dcfa8af960c3f36be0d4bc0d124874243db820..a594254a4124eb611709badac9126881ff9d0ca4 100644 (file)
@@ -213,6 +213,17 @@ static void hpet_stop_timer(HPETState *h, unsigned int tn,
     hpet_get_comparator(h, tn, guest_time);
 }
 
+static void hpet_timer_fired(struct vcpu *v, void *data)
+{
+    unsigned int tn = (unsigned long)data;
+    HPETState *h = vcpu_vhpet(v);
+
+    write_lock(&h->lock);
+    if ( __test_and_set_bit(tn, &h->hpet.isr) )
+        ASSERT_UNREACHABLE();
+    write_unlock(&h->lock);
+}
+
 /* the number of HPET tick that stands for
  * 1/(2^10) second, namely, 0.9765625 milliseconds */
 #define  HPET_TINY_TIME_SPAN  ((h->stime_freq >> 10) / STIME_PER_HPET_TICK)
@@ -234,7 +245,8 @@ static void hpet_set_timer(HPETState *h, unsigned int tn,
         pit_stop_channel0_irq(&vhpet_domain(h)->arch.vpit);
     }
 
-    if ( !timer_enabled(h, tn) )
+    if ( !timer_enabled(h, tn) ||
+         (timer_level(h, tn) && test_bit(tn, &h->hpet.isr)) )
         return;
 
     if ( !timer_int_route_valid(h, tn) )
@@ -283,8 +295,12 @@ static void hpet_set_timer(HPETState *h, unsigned int tn,
      * timer we also need the period which may be different because time may
      * have elapsed between the time the comparator was written and the timer
      * being enabled (now).
+     *
+     * NB: set periodic timers as oneshot if interrupt type is set to level
+     * because the user must ack the interrupt (by writing 1 to the interrupt
+     * status register) before another interrupt can be delivered.
      */
-    oneshot = !timer_is_periodic(h, tn);
+    oneshot = !timer_is_periodic(h, tn) || timer_level(h, tn);
     TRACE_2_LONG_4D(TRC_HVM_EMUL_HPET_START_TIMER, tn, irq,
                     TRC_PAR_LONG(hpet_tick_to_ns(h, diff)),
                     TRC_PAR_LONG(oneshot ? 0LL :
@@ -292,7 +308,8 @@ static void hpet_set_timer(HPETState *h, unsigned int tn,
     create_periodic_time(vhpet_vcpu(h), &h->pt[tn],
                          hpet_tick_to_ns(h, diff),
                          oneshot ? 0 : hpet_tick_to_ns(h, h->hpet.period[tn]),
-                         irq, NULL, NULL, false);
+                         irq, timer_level(h, tn) ? hpet_timer_fired : NULL,
+                         (void *)(unsigned long)tn, timer_level(h, tn));
 }
 
 static inline uint64_t hpet_fixup_reg(
@@ -328,7 +345,7 @@ static int hpet_write(
     HPETState *h = vcpu_vhpet(v);
     uint64_t old_val, new_val;
     uint64_t guest_time;
-    int tn, i;
+    unsigned int tn, i;
 
     /* Acculumate a bit mask of timers whos state is changed by this write. */
     unsigned long start_timers = 0;
@@ -385,6 +402,27 @@ static int hpet_write(
         }
         break;
 
+    case HPET_STATUS:
+        /* write 1 to clear. */
+        while ( new_val )
+        {
+            bool active;
+
+            i = find_first_set_bit(new_val);
+            if ( i >= HPET_TIMER_NUM )
+                break;
+            __clear_bit(i, &new_val);
+            active = __test_and_clear_bit(i, &h->hpet.isr);
+            if ( active )
+            {
+                hvm_ioapic_deassert(v->domain, timer_int_route(h, i));
+                if ( hpet_enabled(h) && timer_enabled(h, i) &&
+                     timer_level(h, i) && timer_is_periodic(h, i) )
+                    set_start_timer(i);
+            }
+        }
+        break;
+
     case HPET_COUNTER:
         h->hpet.mc64 = new_val;
         if ( hpet_enabled(h) )
@@ -410,14 +448,6 @@ static int hpet_write(
 
         timer_sanitize_int_route(h, tn);
 
-        if ( timer_level(h, tn) )
-        {
-            gdprintk(XENLOG_ERR,
-                     "HPET: level triggered interrupt not supported now\n");
-            domain_crash(current->domain);
-            break;
-        }
-
         if ( new_val & HPET_TN_32BIT )
         {
             h->hpet.timers[tn].cmp = (uint32_t)h->hpet.timers[tn].cmp;
index c85d00440261221b3db9b7ca4255635070cef10b..8095c829b6ee991901ffda7ecb541bcf9eda688b 100644 (file)
@@ -61,6 +61,21 @@ int hvm_ioapic_assert(struct domain *d, unsigned int gsi, bool level)
     return vector;
 }
 
+void hvm_ioapic_deassert(struct domain *d, unsigned int gsi)
+{
+    struct hvm_irq *hvm_irq = hvm_domain_irq(d);
+
+    if ( gsi >= hvm_irq->nr_gsis )
+    {
+        ASSERT_UNREACHABLE();
+        return;
+    }
+
+    spin_lock(&d->arch.hvm_domain.irq_lock);
+    hvm_irq->gsi_assert_count[gsi]--;
+    spin_unlock(&d->arch.hvm_domain.irq_lock);
+}
+
 static void assert_irq(struct domain *d, unsigned ioapic_gsi, unsigned pic_irq)
 {
     assert_gsi(d, ioapic_gsi);
index 1a52ec6045003b762d4c864369cd279270fece73..8a43cb97af4d31fa274e27112f24b3842d3be7b2 100644 (file)
@@ -207,8 +207,9 @@ int hvm_set_pci_link_route(struct domain *d, u8 link, u8 isa_irq);
 
 int hvm_inject_msi(struct domain *d, uint64_t addr, uint32_t data);
 
-/* Assert an IO APIC pin. */
+/* Assert/deassert an IO APIC pin. */
 int hvm_ioapic_assert(struct domain *d, unsigned int gsi, bool level);
+void hvm_ioapic_deassert(struct domain *d, unsigned int gsi);
 
 void hvm_maybe_deassert_evtchn_irq(void);
 void hvm_assert_evtchn_irq(struct vcpu *v);