]>
Commit | Line | Data |
---|---|---|
10df8ff1 MF |
1 | /* |
2 | * Copyright (c) 2013 - 2019, 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 "qemu/osdep.h" | |
29 | #include "hw/hw.h" | |
30 | #include "hw/xtensa/mx_pic.h" | |
31 | #include "qemu/log.h" | |
32 | ||
33 | #define MX_MAX_CPU 32 | |
34 | #define MX_MAX_IRQ 32 | |
35 | ||
36 | #define MIROUT 0x0 | |
37 | #define MIPICAUSE 0x100 | |
38 | #define MIPISET 0x140 | |
39 | #define MIENG 0x180 | |
40 | #define MIENGSET 0x184 | |
41 | #define MIASG 0x188 | |
42 | #define MIASGSET 0x18c | |
43 | #define MIPIPART 0x190 | |
44 | #define SYSCFGID 0x1a0 | |
45 | #define MPSCORE 0x200 | |
46 | #define CCON 0x220 | |
47 | ||
48 | struct XtensaMxPic { | |
49 | unsigned n_cpu; | |
50 | unsigned n_irq; | |
51 | ||
52 | uint32_t ext_irq_state; | |
53 | uint32_t mieng; | |
54 | uint32_t miasg; | |
55 | uint32_t mirout[MX_MAX_IRQ]; | |
56 | uint32_t mipipart; | |
57 | uint32_t runstall; | |
58 | ||
59 | qemu_irq *irq_inputs; | |
60 | struct XtensaMxPicCpu { | |
61 | XtensaMxPic *mx; | |
62 | qemu_irq *irq; | |
63 | qemu_irq runstall; | |
64 | uint32_t mipicause; | |
65 | uint32_t mirout_cache; | |
66 | uint32_t irq_state_cache; | |
67 | uint32_t ccon; | |
68 | MemoryRegion reg; | |
69 | } cpu[MX_MAX_CPU]; | |
70 | }; | |
71 | ||
72 | static uint64_t xtensa_mx_pic_ext_reg_read(void *opaque, hwaddr offset, | |
73 | unsigned size) | |
74 | { | |
75 | struct XtensaMxPicCpu *mx_cpu = opaque; | |
76 | struct XtensaMxPic *mx = mx_cpu->mx; | |
77 | ||
78 | if (offset < MIROUT + MX_MAX_IRQ) { | |
79 | return mx->mirout[offset - MIROUT]; | |
80 | } else if (offset >= MIPICAUSE && offset < MIPICAUSE + MX_MAX_CPU) { | |
81 | return mx->cpu[offset - MIPICAUSE].mipicause; | |
82 | } else { | |
83 | switch (offset) { | |
84 | case MIENG: | |
85 | return mx->mieng; | |
86 | ||
87 | case MIASG: | |
88 | return mx->miasg; | |
89 | ||
90 | case MIPIPART: | |
91 | return mx->mipipart; | |
92 | ||
93 | case SYSCFGID: | |
94 | return ((mx->n_cpu - 1) << 18) | (mx_cpu - mx->cpu); | |
95 | ||
96 | case MPSCORE: | |
97 | return mx->runstall; | |
98 | ||
99 | case CCON: | |
100 | return mx_cpu->ccon; | |
101 | ||
102 | default: | |
103 | qemu_log_mask(LOG_GUEST_ERROR, | |
104 | "unknown RER in MX PIC range: 0x%08x\n", | |
105 | (uint32_t)offset); | |
106 | return 0; | |
107 | } | |
108 | } | |
109 | } | |
110 | ||
111 | static uint32_t xtensa_mx_pic_get_ipi_for_cpu(const XtensaMxPic *mx, | |
112 | unsigned cpu) | |
113 | { | |
114 | uint32_t mipicause = mx->cpu[cpu].mipicause; | |
115 | uint32_t mipipart = mx->mipipart; | |
116 | ||
117 | return (((mipicause & 1) << (mipipart & 3)) | | |
118 | ((mipicause & 0x000e) != 0) << ((mipipart >> 2) & 3) | | |
119 | ((mipicause & 0x00f0) != 0) << ((mipipart >> 4) & 3) | | |
120 | ((mipicause & 0xff00) != 0) << ((mipipart >> 6) & 3)) & 0x7; | |
121 | } | |
122 | ||
123 | static uint32_t xtensa_mx_pic_get_ext_irq_for_cpu(const XtensaMxPic *mx, | |
124 | unsigned cpu) | |
125 | { | |
126 | return ((((mx->ext_irq_state & mx->mieng) | mx->miasg) & | |
127 | mx->cpu[cpu].mirout_cache) << 2) | | |
128 | xtensa_mx_pic_get_ipi_for_cpu(mx, cpu); | |
129 | } | |
130 | ||
131 | static void xtensa_mx_pic_update_cpu(XtensaMxPic *mx, unsigned cpu) | |
132 | { | |
133 | uint32_t irq = xtensa_mx_pic_get_ext_irq_for_cpu(mx, cpu); | |
134 | uint32_t changed_irq = mx->cpu[cpu].irq_state_cache ^ irq; | |
135 | unsigned i; | |
136 | ||
137 | qemu_log_mask(CPU_LOG_INT, "%s: CPU %d, irq: %08x, changed_irq: %08x\n", | |
138 | __func__, cpu, irq, changed_irq); | |
139 | mx->cpu[cpu].irq_state_cache = irq; | |
140 | for (i = 0; changed_irq; ++i) { | |
141 | uint32_t mask = 1u << i; | |
142 | ||
143 | if (changed_irq & mask) { | |
144 | changed_irq ^= mask; | |
145 | qemu_set_irq(mx->cpu[cpu].irq[i], irq & mask); | |
146 | } | |
147 | } | |
148 | } | |
149 | ||
150 | static void xtensa_mx_pic_update_all(XtensaMxPic *mx) | |
151 | { | |
152 | unsigned cpu; | |
153 | ||
154 | for (cpu = 0; cpu < mx->n_cpu; ++cpu) { | |
155 | xtensa_mx_pic_update_cpu(mx, cpu); | |
156 | } | |
157 | } | |
158 | ||
159 | static void xtensa_mx_pic_ext_reg_write(void *opaque, hwaddr offset, | |
160 | uint64_t v, unsigned size) | |
161 | { | |
162 | struct XtensaMxPicCpu *mx_cpu = opaque; | |
163 | struct XtensaMxPic *mx = mx_cpu->mx; | |
164 | unsigned cpu; | |
165 | ||
166 | if (offset < MIROUT + mx->n_irq) { | |
167 | mx->mirout[offset - MIROUT] = v; | |
168 | for (cpu = 0; cpu < mx->n_cpu; ++cpu) { | |
169 | uint32_t mask = 1u << (offset - MIROUT); | |
170 | ||
171 | if (!(mx->cpu[cpu].mirout_cache & mask) != !(v & (1u << cpu))) { | |
172 | mx->cpu[cpu].mirout_cache ^= mask; | |
173 | xtensa_mx_pic_update_cpu(mx, cpu); | |
174 | } | |
175 | } | |
176 | } else if (offset >= MIPICAUSE && offset < MIPICAUSE + mx->n_cpu) { | |
177 | cpu = offset - MIPICAUSE; | |
178 | mx->cpu[cpu].mipicause &= ~v; | |
179 | xtensa_mx_pic_update_cpu(mx, cpu); | |
180 | } else if (offset >= MIPISET && offset < MIPISET + 16) { | |
181 | for (cpu = 0; cpu < mx->n_cpu; ++cpu) { | |
182 | if (v & (1u << cpu)) { | |
183 | mx->cpu[cpu].mipicause |= 1u << (offset - MIPISET); | |
184 | xtensa_mx_pic_update_cpu(mx, cpu); | |
185 | } | |
186 | } | |
187 | } else { | |
188 | uint32_t change = 0; | |
189 | uint32_t oldv, newv; | |
190 | const char *name = "???"; | |
191 | ||
192 | switch (offset) { | |
193 | case MIENG: | |
194 | change = mx->mieng & v; | |
195 | oldv = mx->mieng; | |
196 | mx->mieng &= ~v; | |
197 | newv = mx->mieng; | |
198 | name = "MIENG"; | |
199 | break; | |
200 | ||
201 | case MIENGSET: | |
202 | change = ~mx->mieng & v; | |
203 | oldv = mx->mieng; | |
204 | mx->mieng |= v; | |
205 | newv = mx->mieng; | |
206 | name = "MIENG"; | |
207 | break; | |
208 | ||
209 | case MIASG: | |
210 | change = mx->miasg & v; | |
211 | oldv = mx->miasg; | |
212 | mx->miasg &= ~v; | |
213 | newv = mx->miasg; | |
214 | name = "MIASG"; | |
215 | break; | |
216 | ||
217 | case MIASGSET: | |
218 | change = ~mx->miasg & v; | |
219 | oldv = mx->miasg; | |
220 | mx->miasg |= v; | |
221 | newv = mx->miasg; | |
222 | name = "MIASG"; | |
223 | break; | |
224 | ||
225 | case MIPIPART: | |
226 | change = mx->mipipart ^ v; | |
227 | oldv = mx->mipipart; | |
228 | mx->mipipart = v; | |
229 | newv = mx->mipipart; | |
230 | name = "MIPIPART"; | |
231 | break; | |
232 | ||
233 | case MPSCORE: | |
234 | change = mx->runstall ^ v; | |
235 | oldv = mx->runstall; | |
236 | mx->runstall = v; | |
237 | newv = mx->runstall; | |
238 | name = "RUNSTALL"; | |
239 | for (cpu = 0; cpu < mx->n_cpu; ++cpu) { | |
240 | if (change & (1u << cpu)) { | |
241 | qemu_set_irq(mx->cpu[cpu].runstall, v & (1u << cpu)); | |
242 | } | |
243 | } | |
244 | break; | |
245 | ||
246 | case CCON: | |
247 | mx_cpu->ccon = v & 0x1; | |
248 | break; | |
249 | ||
250 | default: | |
251 | qemu_log_mask(LOG_GUEST_ERROR, | |
252 | "unknown WER in MX PIC range: 0x%08x = 0x%08x\n", | |
253 | (uint32_t)offset, (uint32_t)v); | |
254 | break; | |
255 | } | |
256 | if (change) { | |
257 | qemu_log_mask(CPU_LOG_INT, | |
258 | "%s: %s changed by CPU %d: %08x -> %08x\n", | |
259 | __func__, name, (int)(mx_cpu - mx->cpu), | |
260 | oldv, newv); | |
261 | xtensa_mx_pic_update_all(mx); | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | static const MemoryRegionOps xtensa_mx_pic_ops = { | |
267 | .read = xtensa_mx_pic_ext_reg_read, | |
268 | .write = xtensa_mx_pic_ext_reg_write, | |
269 | .endianness = DEVICE_NATIVE_ENDIAN, | |
270 | .valid = { | |
271 | .unaligned = true, | |
272 | }, | |
273 | }; | |
274 | ||
275 | MemoryRegion *xtensa_mx_pic_register_cpu(XtensaMxPic *mx, | |
276 | qemu_irq *irq, | |
277 | qemu_irq runstall) | |
278 | { | |
279 | struct XtensaMxPicCpu *mx_cpu = mx->cpu + mx->n_cpu; | |
280 | ||
281 | mx_cpu->mx = mx; | |
282 | mx_cpu->irq = irq; | |
283 | mx_cpu->runstall = runstall; | |
284 | ||
285 | memory_region_init_io(&mx_cpu->reg, NULL, &xtensa_mx_pic_ops, mx_cpu, | |
286 | "mx_pic", 0x280); | |
287 | ||
288 | ++mx->n_cpu; | |
289 | return &mx_cpu->reg; | |
290 | } | |
291 | ||
292 | static void xtensa_mx_pic_set_irq(void *opaque, int irq, int active) | |
293 | { | |
294 | XtensaMxPic *mx = opaque; | |
295 | ||
296 | if (irq < mx->n_irq) { | |
297 | uint32_t old_irq_state = mx->ext_irq_state; | |
298 | ||
299 | if (active) { | |
300 | mx->ext_irq_state |= 1u << irq; | |
301 | } else { | |
302 | mx->ext_irq_state &= ~(1u << irq); | |
303 | } | |
304 | if (old_irq_state != mx->ext_irq_state) { | |
305 | qemu_log_mask(CPU_LOG_INT, | |
306 | "%s: IRQ %d, active: %d, ext_irq_state: %08x -> %08x\n", | |
307 | __func__, irq, active, | |
308 | old_irq_state, mx->ext_irq_state); | |
309 | xtensa_mx_pic_update_all(mx); | |
310 | } | |
311 | } else { | |
312 | qemu_log_mask(LOG_GUEST_ERROR, "%s: IRQ %d out of range\n", | |
313 | __func__, irq); | |
314 | } | |
315 | } | |
316 | ||
317 | XtensaMxPic *xtensa_mx_pic_init(unsigned n_irq) | |
318 | { | |
319 | XtensaMxPic *mx = calloc(1, sizeof(XtensaMxPic)); | |
320 | ||
321 | mx->n_irq = n_irq + 1; | |
322 | mx->irq_inputs = qemu_allocate_irqs(xtensa_mx_pic_set_irq, mx, | |
323 | mx->n_irq); | |
324 | return mx; | |
325 | } | |
326 | ||
327 | void xtensa_mx_pic_reset(void *opaque) | |
328 | { | |
329 | XtensaMxPic *mx = opaque; | |
330 | unsigned i; | |
331 | ||
332 | mx->ext_irq_state = 0; | |
333 | mx->mieng = mx->n_irq < 32 ? (1u << mx->n_irq) - 1 : ~0u; | |
334 | mx->miasg = 0; | |
335 | mx->mipipart = 0; | |
336 | for (i = 0; i < mx->n_irq; ++i) { | |
337 | mx->mirout[i] = 1; | |
338 | } | |
339 | for (i = 0; i < mx->n_cpu; ++i) { | |
340 | mx->cpu[i].mipicause = 0; | |
341 | mx->cpu[i].mirout_cache = i ? 0 : mx->mieng; | |
342 | mx->cpu[i].irq_state_cache = 0; | |
343 | mx->cpu[i].ccon = 0; | |
344 | } | |
345 | mx->runstall = (1u << mx->n_cpu) - 2; | |
346 | for (i = 0; i < mx->n_cpu; ++i) { | |
347 | qemu_set_irq(mx->cpu[i].runstall, i > 0); | |
348 | } | |
349 | } | |
350 | ||
351 | qemu_irq *xtensa_mx_pic_get_extints(XtensaMxPic *mx) | |
352 | { | |
353 | return mx->irq_inputs + 1; | |
354 | } |