]>
Commit | Line | Data |
---|---|---|
2328826b MF |
1 | /* |
2 | * Copyright (c) 2011, Max Filippov, Open Source and Linux Lab. | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions are met: | |
7 | * * Redistributions of source code must retain the above copyright | |
8 | * notice, this list of conditions and the following disclaimer. | |
9 | * * Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * * Neither the name of the Open Source and Linux Lab nor the | |
13 | * names of its contributors may be used to endorse or promote products | |
14 | * derived from this software without specific prior written permission. | |
15 | * | |
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 | */ | |
27 | ||
28 | #include "hw.h" | |
b994e91b MF |
29 | #include "qemu-log.h" |
30 | #include "qemu-timer.h" | |
2328826b | 31 | |
b994e91b MF |
32 | void xtensa_advance_ccount(CPUState *env, uint32_t d) |
33 | { | |
34 | uint32_t old_ccount = env->sregs[CCOUNT]; | |
35 | ||
36 | env->sregs[CCOUNT] += d; | |
37 | ||
38 | if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT)) { | |
39 | int i; | |
40 | for (i = 0; i < env->config->nccompare; ++i) { | |
41 | if (env->sregs[CCOMPARE + i] - old_ccount <= d) { | |
42 | xtensa_timer_irq(env, i, 1); | |
43 | } | |
44 | } | |
45 | } | |
46 | } | |
47 | ||
48 | void check_interrupts(CPUState *env) | |
49 | { | |
50 | int minlevel = xtensa_get_cintlevel(env); | |
51 | uint32_t int_set_enabled = env->sregs[INTSET] & env->sregs[INTENABLE]; | |
52 | int level; | |
53 | ||
54 | /* If the CPU is halted advance CCOUNT according to the vm_clock time | |
55 | * elapsed since the moment when it was advanced last time. | |
56 | */ | |
57 | if (env->halted) { | |
58 | int64_t now = qemu_get_clock_ns(vm_clock); | |
59 | ||
60 | xtensa_advance_ccount(env, | |
61 | muldiv64(now - env->halt_clock, | |
62 | env->config->clock_freq_khz, 1000000)); | |
63 | env->halt_clock = now; | |
64 | } | |
65 | for (level = env->config->nlevel; level > minlevel; --level) { | |
66 | if (env->config->level_mask[level] & int_set_enabled) { | |
67 | env->pending_irq_level = level; | |
68 | cpu_interrupt(env, CPU_INTERRUPT_HARD); | |
69 | qemu_log_mask(CPU_LOG_INT, | |
70 | "%s level = %d, cintlevel = %d, " | |
71 | "pc = %08x, a0 = %08x, ps = %08x, " | |
72 | "intset = %08x, intenable = %08x, " | |
73 | "ccount = %08x\n", | |
74 | __func__, level, xtensa_get_cintlevel(env), | |
75 | env->pc, env->regs[0], env->sregs[PS], | |
76 | env->sregs[INTSET], env->sregs[INTENABLE], | |
77 | env->sregs[CCOUNT]); | |
78 | return; | |
79 | } | |
80 | } | |
81 | env->pending_irq_level = 0; | |
82 | cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); | |
83 | } | |
84 | ||
85 | static void xtensa_set_irq(void *opaque, int irq, int active) | |
86 | { | |
87 | CPUState *env = opaque; | |
88 | ||
89 | if (irq >= env->config->ninterrupt) { | |
90 | qemu_log("%s: bad IRQ %d\n", __func__, irq); | |
91 | } else { | |
92 | uint32_t irq_bit = 1 << irq; | |
93 | ||
94 | if (active) { | |
95 | env->sregs[INTSET] |= irq_bit; | |
96 | } else if (env->config->interrupt[irq].inttype == INTTYPE_LEVEL) { | |
97 | env->sregs[INTSET] &= ~irq_bit; | |
98 | } | |
99 | ||
100 | check_interrupts(env); | |
101 | } | |
102 | } | |
103 | ||
104 | void xtensa_timer_irq(CPUState *env, uint32_t id, uint32_t active) | |
105 | { | |
106 | qemu_set_irq(env->irq_inputs[env->config->timerint[id]], active); | |
107 | } | |
108 | ||
890c6333 MF |
109 | void xtensa_rearm_ccompare_timer(CPUState *env) |
110 | { | |
111 | int i; | |
112 | uint32_t wake_ccount = env->sregs[CCOUNT] - 1; | |
113 | ||
114 | for (i = 0; i < env->config->nccompare; ++i) { | |
115 | if (env->sregs[CCOMPARE + i] - env->sregs[CCOUNT] < | |
116 | wake_ccount - env->sregs[CCOUNT]) { | |
117 | wake_ccount = env->sregs[CCOMPARE + i]; | |
118 | } | |
119 | } | |
120 | env->wake_ccount = wake_ccount; | |
121 | qemu_mod_timer(env->ccompare_timer, env->halt_clock + | |
122 | muldiv64(wake_ccount - env->sregs[CCOUNT], | |
123 | 1000000, env->config->clock_freq_khz)); | |
124 | } | |
125 | ||
b994e91b MF |
126 | static void xtensa_ccompare_cb(void *opaque) |
127 | { | |
128 | CPUState *env = opaque; | |
890c6333 MF |
129 | |
130 | if (env->halted) { | |
131 | env->halt_clock = qemu_get_clock_ns(vm_clock); | |
132 | xtensa_advance_ccount(env, env->wake_ccount - env->sregs[CCOUNT]); | |
133 | if (!cpu_has_work(env)) { | |
134 | env->sregs[CCOUNT] = env->wake_ccount + 1; | |
135 | xtensa_rearm_ccompare_timer(env); | |
136 | } | |
137 | } | |
b994e91b MF |
138 | } |
139 | ||
140 | void xtensa_irq_init(CPUState *env) | |
141 | { | |
142 | env->irq_inputs = (void **)qemu_allocate_irqs( | |
143 | xtensa_set_irq, env, env->config->ninterrupt); | |
144 | if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT) && | |
145 | env->config->nccompare > 0) { | |
146 | env->ccompare_timer = | |
147 | qemu_new_timer_ns(vm_clock, &xtensa_ccompare_cb, env); | |
148 | } | |
149 | } | |
b8929a54 MF |
150 | |
151 | void *xtensa_get_extint(CPUState *env, unsigned extint) | |
152 | { | |
153 | if (extint < env->config->nextint) { | |
154 | unsigned irq = env->config->extint[extint]; | |
155 | return env->irq_inputs[irq]; | |
156 | } else { | |
157 | qemu_log("%s: trying to acquire invalid external interrupt %d\n", | |
158 | __func__, extint); | |
159 | return NULL; | |
160 | } | |
161 | } |