]> Git Repo - qemu.git/blobdiff - hw/ppc/ppc_booke.c
ppc: replace the 'Object *intc' by a 'ICPState *icp' pointer under the CPU
[qemu.git] / hw / ppc / ppc_booke.c
index 30375c0c41b4d2518fed96c99577063d9f654d49..23bcf1b1387e1d80654cfd627c94195457ea7628 100644 (file)
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "cpu.h"
 #include "hw/hw.h"
-#include "hw/ppc.h"
+#include "hw/ppc/ppc.h"
 #include "qemu/timer.h"
 #include "sysemu/sysemu.h"
-#include "hw/nvram.h"
+#include "hw/timer/m48t59.h"
 #include "qemu/log.h"
 #include "hw/loader.h"
+#include "kvm_ppc.h"
 
 
 /* Timer Control Register */
 
 #define TCR_WP_SHIFT  30        /* Watchdog Timer Period */
-#define TCR_WP_MASK   (0x3 << TCR_WP_SHIFT)
+#define TCR_WP_MASK   (0x3U << TCR_WP_SHIFT)
 #define TCR_WRC_SHIFT 28        /* Watchdog Timer Reset Control */
-#define TCR_WRC_MASK  (0x3 << TCR_WRC_SHIFT)
-#define TCR_WIE       (1 << 27) /* Watchdog Timer Interrupt Enable */
-#define TCR_DIE       (1 << 26) /* Decrementer Interrupt Enable */
+#define TCR_WRC_MASK  (0x3U << TCR_WRC_SHIFT)
+#define TCR_WIE       (1U << 27) /* Watchdog Timer Interrupt Enable */
+#define TCR_DIE       (1U << 26) /* Decrementer Interrupt Enable */
 #define TCR_FP_SHIFT  24        /* Fixed-Interval Timer Period */
-#define TCR_FP_MASK   (0x3 << TCR_FP_SHIFT)
-#define TCR_FIE       (1 << 23) /* Fixed-Interval Timer Interrupt Enable */
-#define TCR_ARE       (1 << 22) /* Auto-Reload Enable */
+#define TCR_FP_MASK   (0x3U << TCR_FP_SHIFT)
+#define TCR_FIE       (1U << 23) /* Fixed-Interval Timer Interrupt Enable */
+#define TCR_ARE       (1U << 22) /* Auto-Reload Enable */
 
 /* Timer Control Register (e500 specific fields) */
 
 
 /* Timer Status Register  */
 
-#define TSR_FIS       (1 << 26) /* Fixed-Interval Timer Interrupt Status */
-#define TSR_DIS       (1 << 27) /* Decrementer Interrupt Status */
+#define TSR_FIS       (1U << 26) /* Fixed-Interval Timer Interrupt Status */
+#define TSR_DIS       (1U << 27) /* Decrementer Interrupt Status */
 #define TSR_WRS_SHIFT 28        /* Watchdog Timer Reset Status */
-#define TSR_WRS_MASK  (0x3 << TSR_WRS_SHIFT)
-#define TSR_WIS       (1 << 30) /* Watchdog Timer Interrupt Status */
-#define TSR_ENW       (1 << 31) /* Enable Next Watchdog Timer */
+#define TSR_WRS_MASK  (0x3U << TSR_WRS_SHIFT)
+#define TSR_WIS       (1U << 30) /* Watchdog Timer Interrupt Status */
+#define TSR_ENW       (1U << 31) /* Enable Next Watchdog Timer */
 
 typedef struct booke_timer_t booke_timer_t;
 struct booke_timer_t {
 
     uint64_t fit_next;
-    struct QEMUTimer *fit_timer;
+    QEMUTimer *fit_timer;
 
     uint64_t wdt_next;
-    struct QEMUTimer *wdt_timer;
+    QEMUTimer *wdt_timer;
 
     uint32_t flags;
 };
@@ -127,20 +131,45 @@ static uint8_t booke_get_wdt_target(CPUPPCState *env, ppc_tb_t *tb_env)
 static void booke_update_fixed_timer(CPUPPCState         *env,
                                      uint8_t           target_bit,
                                      uint64_t          *next,
-                                     struct QEMUTimer *timer)
+                                     QEMUTimer         *timer,
+                                     int               tsr_bit)
 {
     ppc_tb_t *tb_env = env->tb_env;
-    uint64_t lapse;
+    uint64_t delta_tick, ticks = 0;
     uint64_t tb;
-    uint64_t period = 1 << (target_bit + 1);
+    uint64_t period;
     uint64_t now;
 
-    now = qemu_get_clock_ns(vm_clock);
+    if (!(env->spr[SPR_BOOKE_TSR] & tsr_bit)) {
+        /*
+         * Don't arm the timer again when the guest has the current
+         * interrupt still pending. Wait for it to ack it.
+         */
+        return;
+    }
+
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
     tb  = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset);
+    period = 1ULL << target_bit;
+    delta_tick = period - (tb & (period - 1));
+
+    /* the timer triggers only when the selected bit toggles from 0 to 1 */
+    if (tb & period) {
+        ticks = period;
+    }
 
-    lapse = period - ((tb - (1 << target_bit)) & (period - 1));
+    if (ticks + delta_tick < ticks) {
+        /* Overflow, so assume the biggest number we can express. */
+        ticks = UINT64_MAX;
+    } else {
+        ticks += delta_tick;
+    }
 
-    *next = now + muldiv64(lapse, get_ticks_per_sec(), tb_env->tb_freq);
+    *next = now + muldiv64(ticks, NANOSECONDS_PER_SECOND, tb_env->tb_freq);
+    if ((*next < now) || (*next > INT64_MAX)) {
+        /* Overflow, so assume the biggest number the qemu timer supports. */
+        *next = INT64_MAX;
+    }
 
     /* XXX: If expire time is now. We can't run the callback because we don't
      * have access to it. So we just set the timer one nanosecond later.
@@ -148,9 +177,16 @@ static void booke_update_fixed_timer(CPUPPCState         *env,
 
     if (*next == now) {
         (*next)++;
+    } else {
+        /*
+         * There's no point to fake any granularity that's more fine grained
+         * than milliseconds. Anything beyond that just overloads the system.
+         */
+        *next = MAX(*next, now + SCALE_MS);
     }
 
-    qemu_mod_timer(timer, *next);
+    /* Fire the next timer */
+    timer_mod(timer, *next);
 }
 
 static void booke_decr_cb(void *opaque)
@@ -162,8 +198,12 @@ static void booke_decr_cb(void *opaque)
     booke_update_irq(cpu);
 
     if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) {
-        /* Auto Reload */
-        cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
+        /* Do not reload 0, it is already there. It would just trigger
+         * the timer again and lead to infinite loop */
+        if (env->spr[SPR_BOOKE_DECAR] != 0) {
+            /* Auto Reload */
+            cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
+        }
     }
 }
 
@@ -183,7 +223,8 @@ static void booke_fit_cb(void *opaque)
     booke_update_fixed_timer(env,
                              booke_get_fit_target(env, tb_env),
                              &booke_timer->fit_next,
-                             booke_timer->fit_timer);
+                             booke_timer->fit_timer,
+                             TSR_FIS);
 }
 
 static void booke_wdt_cb(void *opaque)
@@ -203,14 +244,35 @@ static void booke_wdt_cb(void *opaque)
     booke_update_fixed_timer(env,
                              booke_get_wdt_target(env, tb_env),
                              &booke_timer->wdt_next,
-                             booke_timer->wdt_timer);
+                             booke_timer->wdt_timer,
+                             TSR_WIS);
 }
 
 void store_booke_tsr(CPUPPCState *env, target_ulong val)
 {
     PowerPCCPU *cpu = ppc_env_get_cpu(env);
+    ppc_tb_t *tb_env = env->tb_env;
+    booke_timer_t *booke_timer = tb_env->opaque;
 
     env->spr[SPR_BOOKE_TSR] &= ~val;
+    kvmppc_clear_tsr_bits(cpu, val);
+
+    if (val & TSR_FIS) {
+        booke_update_fixed_timer(env,
+                                 booke_get_fit_target(env, tb_env),
+                                 &booke_timer->fit_next,
+                                 booke_timer->fit_timer,
+                                 TSR_FIS);
+    }
+
+    if (val & TSR_WIS) {
+        booke_update_fixed_timer(env,
+                                 booke_get_wdt_target(env, tb_env),
+                                 &booke_timer->wdt_next,
+                                 booke_timer->wdt_timer,
+                                 TSR_WIS);
+    }
+
     booke_update_irq(cpu);
 }
 
@@ -220,21 +282,22 @@ void store_booke_tcr(CPUPPCState *env, target_ulong val)
     ppc_tb_t *tb_env = env->tb_env;
     booke_timer_t *booke_timer = tb_env->opaque;
 
-    tb_env = env->tb_env;
     env->spr[SPR_BOOKE_TCR] = val;
+    kvmppc_set_tcr(cpu);
 
     booke_update_irq(cpu);
 
     booke_update_fixed_timer(env,
                              booke_get_fit_target(env, tb_env),
                              &booke_timer->fit_next,
-                             booke_timer->fit_timer);
+                             booke_timer->fit_timer,
+                             TSR_FIS);
 
     booke_update_fixed_timer(env,
                              booke_get_wdt_target(env, tb_env),
                              &booke_timer->wdt_next,
-                             booke_timer->wdt_timer);
-
+                             booke_timer->wdt_timer,
+                             TSR_WIS);
 }
 
 static void ppc_booke_timer_reset_handle(void *opaque)
@@ -242,16 +305,39 @@ static void ppc_booke_timer_reset_handle(void *opaque)
     PowerPCCPU *cpu = opaque;
     CPUPPCState *env = &cpu->env;
 
-    env->spr[SPR_BOOKE_TSR] = 0;
-    env->spr[SPR_BOOKE_TCR] = 0;
+    store_booke_tcr(env, 0);
+    store_booke_tsr(env, -1);
+}
 
-    booke_update_irq(cpu);
+/*
+ * This function will be called whenever the CPU state changes.
+ * CPU states are defined "typedef enum RunState".
+ * Regarding timer, When CPU state changes to running after debug halt
+ * or similar cases which takes time then in between final watchdog
+ * expiry happenes. This will cause exit to QEMU and configured watchdog
+ * action will be taken. To avoid this we always clear the watchdog state when
+ * state changes to running.
+ */
+static void cpu_state_change_handler(void *opaque, int running, RunState state)
+{
+    PowerPCCPU *cpu = opaque;
+    CPUPPCState *env = &cpu->env;
+
+    if (!running) {
+        return;
+    }
+
+    /*
+     * Clear watchdog interrupt condition by clearing TSR.
+     */
+    store_booke_tsr(env, TSR_ENW | TSR_WIS | TSR_WRS_MASK);
 }
 
 void ppc_booke_timers_init(PowerPCCPU *cpu, uint32_t freq, uint32_t flags)
 {
     ppc_tb_t *tb_env;
     booke_timer_t *booke_timer;
+    int ret = 0;
 
     tb_env      = g_malloc0(sizeof(ppc_tb_t));
     booke_timer = g_malloc0(sizeof(booke_timer_t));
@@ -262,12 +348,24 @@ void ppc_booke_timers_init(PowerPCCPU *cpu, uint32_t freq, uint32_t flags)
     tb_env->tb_freq    = freq;
     tb_env->decr_freq  = freq;
     tb_env->opaque     = booke_timer;
-    tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &booke_decr_cb, cpu);
+    tb_env->decr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_decr_cb, cpu);
 
     booke_timer->fit_timer =
-        qemu_new_timer_ns(vm_clock, &booke_fit_cb, cpu);
+        timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_fit_cb, cpu);
     booke_timer->wdt_timer =
-        qemu_new_timer_ns(vm_clock, &booke_wdt_cb, cpu);
+        timer_new_ns(QEMU_CLOCK_VIRTUAL, &booke_wdt_cb, cpu);
+
+    ret = kvmppc_booke_watchdog_enable(cpu);
+
+    if (ret) {
+        /* TODO: Start the QEMU emulated watchdog if not running on KVM.
+         * Also start the QEMU emulated watchdog if KVM does not support
+         * emulated watchdog or somehow it is not enabled (supported but
+         * not enabled is though some bug and requires debugging :)).
+         */
+    }
+
+    qemu_add_vm_change_state_handler(cpu_state_change_handler, cpu);
 
     qemu_register_reset(ppc_booke_timer_reset_handle, cpu);
 }
This page took 0.033236 seconds and 4 git commands to generate.