| 1 | /* |
| 2 | * HPPA interrupt helper routines |
| 3 | * |
| 4 | * Copyright (c) 2017 Richard Henderson |
| 5 | * |
| 6 | * This library is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public |
| 8 | * License as published by the Free Software Foundation; either |
| 9 | * version 2.1 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * This library is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| 18 | */ |
| 19 | |
| 20 | #include "qemu/osdep.h" |
| 21 | #include "qemu/main-loop.h" |
| 22 | #include "qemu/log.h" |
| 23 | #include "cpu.h" |
| 24 | #include "exec/helper-proto.h" |
| 25 | #include "hw/core/cpu.h" |
| 26 | #include "hw/hppa/hppa_hardware.h" |
| 27 | #include "qemu/plugin.h" |
| 28 | |
| 29 | static void eval_interrupt(HPPACPU *cpu) |
| 30 | { |
| 31 | CPUState *cs = CPU(cpu); |
| 32 | if (cpu->env.cr[CR_EIRR]) { |
| 33 | cpu_interrupt(cs, CPU_INTERRUPT_HARD); |
| 34 | } else { |
| 35 | cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | /* Each CPU has a word mapped into the GSC bus. Anything on the GSC bus |
| 40 | * can write to this word to raise an external interrupt on the target CPU. |
| 41 | * This includes the system controller (DINO) for regular devices, or |
| 42 | * another CPU for SMP interprocessor interrupts. |
| 43 | */ |
| 44 | static uint64_t io_eir_read(void *opaque, hwaddr addr, unsigned size) |
| 45 | { |
| 46 | HPPACPU *cpu = opaque; |
| 47 | |
| 48 | /* ??? What does a read of this register over the GSC bus do? */ |
| 49 | return cpu->env.cr[CR_EIRR]; |
| 50 | } |
| 51 | |
| 52 | static void io_eir_write(void *opaque, hwaddr addr, |
| 53 | uint64_t data, unsigned size) |
| 54 | { |
| 55 | HPPACPU *cpu = opaque; |
| 56 | CPUHPPAState *env = &cpu->env; |
| 57 | int widthm1 = 31; |
| 58 | int le_bit; |
| 59 | |
| 60 | /* The default PSW.W controls the width of EIRR. */ |
| 61 | if (hppa_is_pa20(env) && env->cr[CR_PSW_DEFAULT] & PDC_PSW_WIDE_BIT) { |
| 62 | widthm1 = 63; |
| 63 | } |
| 64 | le_bit = ~data & widthm1; |
| 65 | |
| 66 | env->cr[CR_EIRR] |= 1ull << le_bit; |
| 67 | eval_interrupt(cpu); |
| 68 | } |
| 69 | |
| 70 | const MemoryRegionOps hppa_io_eir_ops = { |
| 71 | .read = io_eir_read, |
| 72 | .write = io_eir_write, |
| 73 | .valid.min_access_size = 4, |
| 74 | .valid.max_access_size = 4, |
| 75 | .impl.min_access_size = 4, |
| 76 | .impl.max_access_size = 4, |
| 77 | }; |
| 78 | |
| 79 | void hppa_cpu_alarm_timer(void *opaque) |
| 80 | { |
| 81 | /* Raise interrupt 0. */ |
| 82 | io_eir_write(opaque, 0, 0, 4); |
| 83 | } |
| 84 | |
| 85 | void HELPER(write_eirr)(CPUHPPAState *env, target_ulong val) |
| 86 | { |
| 87 | env->cr[CR_EIRR] &= ~val; |
| 88 | bql_lock(); |
| 89 | eval_interrupt(env_archcpu(env)); |
| 90 | bql_unlock(); |
| 91 | } |
| 92 | |
| 93 | void hppa_cpu_do_interrupt(CPUState *cs) |
| 94 | { |
| 95 | HPPACPU *cpu = HPPA_CPU(cs); |
| 96 | CPUHPPAState *env = &cpu->env; |
| 97 | int i = cs->exception_index; |
| 98 | uint64_t old_psw, old_gva_offset_mask; |
| 99 | uint64_t last_pc = cs->cc->get_pc(cs); |
| 100 | |
| 101 | /* As documented in pa2.0 -- interruption handling. */ |
| 102 | /* step 1 */ |
| 103 | env->cr[CR_IPSW] = old_psw = cpu_hppa_get_psw(env); |
| 104 | old_gva_offset_mask = env->gva_offset_mask; |
| 105 | |
| 106 | /* step 2 -- Note PSW_W is masked out again for pa1.x */ |
| 107 | cpu_hppa_put_psw(env, |
| 108 | (env->cr[CR_PSW_DEFAULT] & PDC_PSW_WIDE_BIT ? PSW_W : 0) | |
| 109 | (i == EXCP_HPMC ? PSW_M : 0)); |
| 110 | |
| 111 | /* step 3 */ |
| 112 | /* |
| 113 | * IIASQ is the top bits of the virtual address, or zero if translation |
| 114 | * is disabled -- with PSW_W == 0, this will reduce to the space. |
| 115 | */ |
| 116 | if (old_psw & PSW_C) { |
| 117 | env->cr[CR_IIASQ] = |
| 118 | hppa_form_gva_mask(old_gva_offset_mask, env->iasq_f, env->iaoq_f) >> 32; |
| 119 | env->cr_back[0] = |
| 120 | hppa_form_gva_mask(old_gva_offset_mask, env->iasq_b, env->iaoq_b) >> 32; |
| 121 | } else { |
| 122 | env->cr[CR_IIASQ] = 0; |
| 123 | env->cr_back[0] = 0; |
| 124 | } |
| 125 | /* IIAOQ is the full offset for wide mode, or 32 bits for narrow mode. */ |
| 126 | if (old_psw & PSW_W) { |
| 127 | env->cr[CR_IIAOQ] = env->iaoq_f; |
| 128 | env->cr_back[1] = env->iaoq_b; |
| 129 | } else { |
| 130 | env->cr[CR_IIAOQ] = (uint32_t)env->iaoq_f; |
| 131 | env->cr_back[1] = (uint32_t)env->iaoq_b; |
| 132 | } |
| 133 | |
| 134 | if (old_psw & PSW_Q) { |
| 135 | /* step 5 */ |
| 136 | /* ISR and IOR will be set elsewhere. */ |
| 137 | switch (i) { |
| 138 | case EXCP_ILL: |
| 139 | case EXCP_BREAK: |
| 140 | case EXCP_OVERFLOW: |
| 141 | case EXCP_COND: |
| 142 | case EXCP_PRIV_REG: |
| 143 | case EXCP_PRIV_OPR: |
| 144 | /* IIR set via translate.c. */ |
| 145 | break; |
| 146 | |
| 147 | case EXCP_ASSIST: |
| 148 | case EXCP_DTLB_MISS: |
| 149 | case EXCP_NA_ITLB_MISS: |
| 150 | case EXCP_NA_DTLB_MISS: |
| 151 | case EXCP_DMAR: |
| 152 | case EXCP_DMPI: |
| 153 | case EXCP_UNALIGN: |
| 154 | case EXCP_DMP: |
| 155 | case EXCP_DMB: |
| 156 | case EXCP_TLB_DIRTY: |
| 157 | case EXCP_PAGE_REF: |
| 158 | case EXCP_ASSIST_EMU: |
| 159 | { |
| 160 | /* Avoid reading directly from the virtual address, lest we |
| 161 | raise another exception from some sort of TLB issue. */ |
| 162 | /* ??? An alternate fool-proof method would be to store the |
| 163 | instruction data into the unwind info. That's probably |
| 164 | a bit too much in the way of extra storage required. */ |
| 165 | vaddr vaddr = env->iaoq_f & -4; |
| 166 | hwaddr paddr = vaddr; |
| 167 | |
| 168 | if (old_psw & PSW_C) { |
| 169 | int prot, t; |
| 170 | |
| 171 | vaddr = hppa_form_gva_mask(old_gva_offset_mask, |
| 172 | env->iasq_f, vaddr); |
| 173 | t = hppa_get_physical_address(env, vaddr, MMU_KERNEL_IDX, |
| 174 | 0, 0, &paddr, &prot); |
| 175 | if (t >= 0) { |
| 176 | /* We can't re-load the instruction. */ |
| 177 | env->cr[CR_IIR] = 0; |
| 178 | break; |
| 179 | } |
| 180 | } |
| 181 | env->cr[CR_IIR] = ldl_be_phys(cs->as, paddr); |
| 182 | if (i == EXCP_ASSIST) { |
| 183 | /* stuff insn code into bits of FP exception register #1 */ |
| 184 | env->fr[0] |= (env->cr[CR_IIR] & 0x03ffffff); |
| 185 | } |
| 186 | } |
| 187 | break; |
| 188 | |
| 189 | default: |
| 190 | /* Other exceptions do not set IIR. */ |
| 191 | break; |
| 192 | } |
| 193 | |
| 194 | /* step 6 */ |
| 195 | env->shadow[0] = env->gr[1]; |
| 196 | env->shadow[1] = env->gr[8]; |
| 197 | env->shadow[2] = env->gr[9]; |
| 198 | env->shadow[3] = env->gr[16]; |
| 199 | env->shadow[4] = env->gr[17]; |
| 200 | env->shadow[5] = env->gr[24]; |
| 201 | env->shadow[6] = env->gr[25]; |
| 202 | } |
| 203 | |
| 204 | /* step 7 */ |
| 205 | if (i == EXCP_TOC) { |
| 206 | hwaddr pdc_toc_addr = FIRMWARE_START; |
| 207 | |
| 208 | /* for 64-bit include the high bits of PDC */ |
| 209 | pdc_toc_addr |= ((uint64_t) FIRMWARE_HIGH) << 32; |
| 210 | env->iaoq_f = hppa_form_gva(env, 0, pdc_toc_addr); |
| 211 | |
| 212 | /* help SeaBIOS and provide iaoq_b and iasq_back in shadow regs */ |
| 213 | env->gr[24] = env->cr_back[0]; |
| 214 | env->gr[25] = env->cr_back[1]; |
| 215 | } else { |
| 216 | env->iaoq_f = hppa_form_gva(env, 0, env->cr[CR_IVA] + 32 * i); |
| 217 | } |
| 218 | env->iaoq_b = hppa_form_gva(env, 0, env->iaoq_f + 4); |
| 219 | env->iasq_f = 0; |
| 220 | env->iasq_b = 0; |
| 221 | |
| 222 | switch (i) { |
| 223 | case EXCP_HPMC: |
| 224 | case EXCP_POWER_FAIL: |
| 225 | case EXCP_RC: |
| 226 | case EXCP_EXT_INTERRUPT: |
| 227 | case EXCP_LPMC: |
| 228 | case EXCP_PER_INTERRUPT: |
| 229 | case EXCP_TOC: |
| 230 | qemu_plugin_vcpu_interrupt_cb(cs, last_pc); |
| 231 | break; |
| 232 | default: |
| 233 | qemu_plugin_vcpu_exception_cb(cs, last_pc); |
| 234 | break; |
| 235 | } |
| 236 | |
| 237 | if (qemu_loglevel_mask(CPU_LOG_INT)) { |
| 238 | static const char * const names[] = { |
| 239 | [EXCP_HPMC] = "high priority machine check", |
| 240 | [EXCP_POWER_FAIL] = "power fail interrupt", |
| 241 | [EXCP_RC] = "recovery counter trap", |
| 242 | [EXCP_EXT_INTERRUPT] = "external interrupt", |
| 243 | [EXCP_LPMC] = "low priority machine check", |
| 244 | [EXCP_ITLB_MISS] = "instruction tlb miss fault", |
| 245 | [EXCP_IMP] = "instruction memory protection trap", |
| 246 | [EXCP_ILL] = "illegal instruction trap", |
| 247 | [EXCP_BREAK] = "break instruction trap", |
| 248 | [EXCP_PRIV_OPR] = "privileged operation trap", |
| 249 | [EXCP_PRIV_REG] = "privileged register trap", |
| 250 | [EXCP_OVERFLOW] = "overflow trap", |
| 251 | [EXCP_COND] = "conditional trap", |
| 252 | [EXCP_ASSIST] = "assist exception trap", |
| 253 | [EXCP_DTLB_MISS] = "data tlb miss fault", |
| 254 | [EXCP_NA_ITLB_MISS] = "non-access instruction tlb miss", |
| 255 | [EXCP_NA_DTLB_MISS] = "non-access data tlb miss", |
| 256 | [EXCP_DMP] = "data memory protection trap", |
| 257 | [EXCP_DMB] = "data memory break trap", |
| 258 | [EXCP_TLB_DIRTY] = "tlb dirty bit trap", |
| 259 | [EXCP_PAGE_REF] = "page reference trap", |
| 260 | [EXCP_ASSIST_EMU] = "assist emulation trap", |
| 261 | [EXCP_HPT] = "high-privilege transfer trap", |
| 262 | [EXCP_LPT] = "low-privilege transfer trap", |
| 263 | [EXCP_TB] = "taken branch trap", |
| 264 | [EXCP_DMAR] = "data memory access rights trap", |
| 265 | [EXCP_DMPI] = "data memory protection id trap", |
| 266 | [EXCP_UNALIGN] = "unaligned data reference trap", |
| 267 | [EXCP_PER_INTERRUPT] = "performance monitor interrupt", |
| 268 | [EXCP_SYSCALL] = "syscall", |
| 269 | [EXCP_SYSCALL_LWS] = "syscall-lws", |
| 270 | [EXCP_TOC] = "TOC (transfer of control)", |
| 271 | }; |
| 272 | |
| 273 | FILE *logfile = qemu_log_trylock(); |
| 274 | if (logfile) { |
| 275 | const char *name = NULL; |
| 276 | |
| 277 | if (i >= 0 && i < ARRAY_SIZE(names)) { |
| 278 | name = names[i]; |
| 279 | } |
| 280 | if (name) { |
| 281 | fprintf(logfile, "INT: cpu %d %s\n", cs->cpu_index, name); |
| 282 | } else { |
| 283 | fprintf(logfile, "INT: cpu %d unknown %d\n", cs->cpu_index, i); |
| 284 | } |
| 285 | hppa_cpu_dump_state(cs, logfile, 0); |
| 286 | qemu_log_unlock(logfile); |
| 287 | } |
| 288 | } |
| 289 | cs->exception_index = -1; |
| 290 | } |
| 291 | |
| 292 | bool hppa_cpu_exec_interrupt(CPUState *cs, int interrupt_request) |
| 293 | { |
| 294 | HPPACPU *cpu = HPPA_CPU(cs); |
| 295 | CPUHPPAState *env = &cpu->env; |
| 296 | |
| 297 | if (interrupt_request & CPU_INTERRUPT_NMI) { |
| 298 | /* Raise TOC (NMI) interrupt */ |
| 299 | cpu_reset_interrupt(cs, CPU_INTERRUPT_NMI); |
| 300 | cs->exception_index = EXCP_TOC; |
| 301 | hppa_cpu_do_interrupt(cs); |
| 302 | return true; |
| 303 | } |
| 304 | |
| 305 | /* If interrupts are requested and enabled, raise them. */ |
| 306 | if ((interrupt_request & CPU_INTERRUPT_HARD) |
| 307 | && (env->psw & PSW_I) |
| 308 | && (env->cr[CR_EIRR] & env->cr[CR_EIEM])) { |
| 309 | cs->exception_index = EXCP_EXT_INTERRUPT; |
| 310 | hppa_cpu_do_interrupt(cs); |
| 311 | return true; |
| 312 | } |
| 313 | return false; |
| 314 | } |