xref: /openbmc/qemu/target/mips/sysemu/cp0_timer.c (revision efb91426)
1 /*
2  * QEMU MIPS timer support
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 #include "qemu/osdep.h"
24 #include "hw/irq.h"
25 #include "qemu/timer.h"
26 #include "sysemu/kvm.h"
27 #include "internal.h"
28 
29 /* MIPS R4K timer */
30 static uint32_t cpu_mips_get_count_val(CPUMIPSState *env)
31 {
32     int64_t now_ns;
33     now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
34     return env->CP0_Count +
35             (uint32_t)clock_ns_to_ticks(env->count_clock, now_ns);
36 }
37 
38 static void cpu_mips_timer_update(CPUMIPSState *env)
39 {
40     uint64_t now_ns, next_ns;
41     uint32_t wait;
42 
43     now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
44     wait = env->CP0_Compare - cpu_mips_get_count_val(env);
45     /* Clamp interval to overflow if virtual time had not progressed */
46     if (!wait) {
47         wait = UINT32_MAX;
48     }
49     next_ns = now_ns + clock_ticks_to_ns(env->count_clock, wait);
50     timer_mod(env->timer, next_ns);
51 }
52 
53 /* Expire the timer.  */
54 static void cpu_mips_timer_expire(CPUMIPSState *env)
55 {
56     cpu_mips_timer_update(env);
57     if (env->insn_flags & ISA_MIPS_R2) {
58         env->CP0_Cause |= 1 << CP0Ca_TI;
59     }
60     qemu_irq_raise(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
61 }
62 
63 uint32_t cpu_mips_get_count(CPUMIPSState *env)
64 {
65     if (env->CP0_Cause & (1 << CP0Ca_DC)) {
66         return env->CP0_Count;
67     } else {
68         uint64_t now_ns;
69 
70         now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
71         if (timer_pending(env->timer)
72             && timer_expired(env->timer, now_ns)) {
73             /* The timer has already expired.  */
74             cpu_mips_timer_expire(env);
75         }
76 
77         return cpu_mips_get_count_val(env);
78     }
79 }
80 
81 void cpu_mips_store_count(CPUMIPSState *env, uint32_t count)
82 {
83     /*
84      * This gets called from cpu_state_reset(), potentially before timer init.
85      * So env->timer may be NULL, which is also the case with KVM enabled so
86      * treat timer as disabled in that case.
87      */
88     if (env->CP0_Cause & (1 << CP0Ca_DC) || !env->timer) {
89         env->CP0_Count = count;
90     } else {
91         /* Store new count register */
92         env->CP0_Count = count - (uint32_t)clock_ns_to_ticks(env->count_clock,
93                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
94         /* Update timer timer */
95         cpu_mips_timer_update(env);
96     }
97 }
98 
99 void cpu_mips_store_compare(CPUMIPSState *env, uint32_t value)
100 {
101     env->CP0_Compare = value;
102     if (!(env->CP0_Cause & (1 << CP0Ca_DC))) {
103         cpu_mips_timer_update(env);
104     }
105     if (env->insn_flags & ISA_MIPS_R2) {
106         env->CP0_Cause &= ~(1 << CP0Ca_TI);
107     }
108     qemu_irq_lower(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
109 }
110 
111 void cpu_mips_start_count(CPUMIPSState *env)
112 {
113     cpu_mips_store_count(env, env->CP0_Count);
114 }
115 
116 void cpu_mips_stop_count(CPUMIPSState *env)
117 {
118     /* Store the current value */
119     env->CP0_Count += (uint32_t)clock_ns_to_ticks(env->count_clock,
120                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
121 }
122 
123 static void mips_timer_cb(void *opaque)
124 {
125     CPUMIPSState *env;
126 
127     env = opaque;
128 
129     if (env->CP0_Cause & (1 << CP0Ca_DC)) {
130         return;
131     }
132 
133     cpu_mips_timer_expire(env);
134 }
135 
136 void cpu_mips_clock_init(MIPSCPU *cpu)
137 {
138     CPUMIPSState *env = &cpu->env;
139 
140     /*
141      * If we're in KVM mode, don't create the periodic timer, that is handled in
142      * kernel.
143      */
144     if (!kvm_enabled()) {
145         env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &mips_timer_cb, env);
146     }
147 }
148