xref: /openbmc/qemu/hw/xtensa/pic_cpu.c (revision 6e72a00f)
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/hw.h"
29 #include "qemu/log.h"
30 #include "qemu/timer.h"
31 
32 void xtensa_advance_ccount(CPUXtensaState *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(CPUXtensaState *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     CPUXtensaState *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(CPUXtensaState *env, uint32_t id, uint32_t active)
105 {
106     qemu_set_irq(env->irq_inputs[env->config->timerint[id]], active);
107 }
108 
109 void xtensa_rearm_ccompare_timer(CPUXtensaState *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 
126 static void xtensa_ccompare_cb(void *opaque)
127 {
128     XtensaCPU *cpu = opaque;
129     CPUXtensaState *env = &cpu->env;
130 
131     if (env->halted) {
132         env->halt_clock = qemu_get_clock_ns(vm_clock);
133         xtensa_advance_ccount(env, env->wake_ccount - env->sregs[CCOUNT]);
134         if (!cpu_has_work(CPU(cpu))) {
135             env->sregs[CCOUNT] = env->wake_ccount + 1;
136             xtensa_rearm_ccompare_timer(env);
137         }
138     }
139 }
140 
141 void xtensa_irq_init(CPUXtensaState *env)
142 {
143     XtensaCPU *cpu = xtensa_env_get_cpu(env);
144 
145     env->irq_inputs = (void **)qemu_allocate_irqs(
146             xtensa_set_irq, env, env->config->ninterrupt);
147     if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT) &&
148             env->config->nccompare > 0) {
149         env->ccompare_timer =
150             qemu_new_timer_ns(vm_clock, &xtensa_ccompare_cb, cpu);
151     }
152 }
153 
154 void *xtensa_get_extint(CPUXtensaState *env, unsigned extint)
155 {
156     if (extint < env->config->nextint) {
157         unsigned irq = env->config->extint[extint];
158         return env->irq_inputs[irq];
159     } else {
160         qemu_log("%s: trying to acquire invalid external interrupt %d\n",
161                 __func__, extint);
162         return NULL;
163     }
164 }
165