| 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| 2 | /* |
| 3 | * LoongArch CPU parameters for QEMU. |
| 4 | * |
| 5 | * Copyright (c) 2025 Loongson Technology Corporation Limited |
| 6 | */ |
| 7 | #include "qemu/osdep.h" |
| 8 | #include "qemu/accel.h" |
| 9 | #include "qemu/error-report.h" |
| 10 | #include "qemu/log.h" |
| 11 | #include "qemu/plugin.h" |
| 12 | #include "accel/tcg/cpu-ldst.h" |
| 13 | #include "accel/tcg/cpu-loop.h" |
| 14 | #include "accel/tcg/cpu-ops.h" |
| 15 | #include "exec/translation-block.h" |
| 16 | #include "exec/target_page.h" |
| 17 | #include "tcg/debug-assert.h" |
| 18 | #include "tcg_loongarch.h" |
| 19 | #include "internals.h" |
| 20 | |
| 21 | struct TypeExcp { |
| 22 | int32_t exccode; |
| 23 | const char * const name; |
| 24 | }; |
| 25 | |
| 26 | static const struct TypeExcp excp_names[] = { |
| 27 | {EXCCODE_INT, "Interrupt"}, |
| 28 | {EXCCODE_PIL, "Page invalid exception for load"}, |
| 29 | {EXCCODE_PIS, "Page invalid exception for store"}, |
| 30 | {EXCCODE_PIF, "Page invalid exception for fetch"}, |
| 31 | {EXCCODE_PME, "Page modified exception"}, |
| 32 | {EXCCODE_PNR, "Page Not Readable exception"}, |
| 33 | {EXCCODE_PNX, "Page Not Executable exception"}, |
| 34 | {EXCCODE_PPI, "Page Privilege error"}, |
| 35 | {EXCCODE_ADEF, "Address error for instruction fetch"}, |
| 36 | {EXCCODE_ADEM, "Address error for Memory access"}, |
| 37 | {EXCCODE_SYS, "Syscall"}, |
| 38 | {EXCCODE_BRK, "Break"}, |
| 39 | {EXCCODE_INE, "Instruction Non-Existent"}, |
| 40 | {EXCCODE_IPE, "Instruction privilege error"}, |
| 41 | {EXCCODE_FPD, "Floating Point Disabled"}, |
| 42 | {EXCCODE_FPE, "Floating Point Exception"}, |
| 43 | {EXCCODE_DBP, "Debug breakpoint"}, |
| 44 | {EXCCODE_BCE, "Bound Check Exception"}, |
| 45 | {EXCCODE_SXD, "128 bit vector instructions Disable exception"}, |
| 46 | {EXCCODE_ASXD, "256 bit vector instructions Disable exception"}, |
| 47 | {EXCP_HLT, "EXCP_HLT"}, |
| 48 | }; |
| 49 | |
| 50 | static const char *loongarch_exception_name(int32_t exception) |
| 51 | { |
| 52 | int i; |
| 53 | |
| 54 | for (i = 0; i < ARRAY_SIZE(excp_names); i++) { |
| 55 | if (excp_names[i].exccode == exception) { |
| 56 | return excp_names[i].name; |
| 57 | } |
| 58 | } |
| 59 | return "Unknown"; |
| 60 | } |
| 61 | |
| 62 | void G_NORETURN do_raise_exception(CPULoongArchState *env, |
| 63 | uint32_t exception, |
| 64 | uintptr_t pc) |
| 65 | { |
| 66 | CPUState *cs = env_cpu(env); |
| 67 | |
| 68 | qemu_log_mask(CPU_LOG_INT, "%s: exception: %d (%s)\n", |
| 69 | __func__, |
| 70 | exception, |
| 71 | loongarch_exception_name(exception)); |
| 72 | cs->exception_index = exception; |
| 73 | |
| 74 | cpu_loop_exit_restore(cs, pc); |
| 75 | } |
| 76 | |
| 77 | #ifndef CONFIG_USER_ONLY |
| 78 | static void loongarch_cpu_do_interrupt(CPUState *cs) |
| 79 | { |
| 80 | CPULoongArchState *env = cpu_env(cs); |
| 81 | CPUSysState *sys = env_sys(env); |
| 82 | bool update_badinstr = 1; |
| 83 | int cause = -1; |
| 84 | bool tlbfill = FIELD_EX64(sys->CSR_TLBRERA, CSR_TLBRERA, ISTLBR); |
| 85 | uint32_t vec_size = FIELD_EX64(sys->CSR_ECFG, CSR_ECFG, VS); |
| 86 | uint64_t last_pc = env->pc; |
| 87 | |
| 88 | if (cs->exception_index != EXCCODE_INT) { |
| 89 | qemu_log_mask(CPU_LOG_INT, |
| 90 | "%s enter: pc " TARGET_FMT_lx " ERA " TARGET_FMT_lx |
| 91 | " TLBRERA " TARGET_FMT_lx " exception: %d (%s)\n", |
| 92 | __func__, env->pc, sys->CSR_ERA, sys->CSR_TLBRERA, |
| 93 | cs->exception_index, |
| 94 | loongarch_exception_name(cs->exception_index)); |
| 95 | } |
| 96 | |
| 97 | switch (cs->exception_index) { |
| 98 | case EXCCODE_DBP: |
| 99 | sys->CSR_DBG = FIELD_DP64(sys->CSR_DBG, CSR_DBG, DCL, 1); |
| 100 | sys->CSR_DBG = FIELD_DP64(sys->CSR_DBG, CSR_DBG, ECODE, 0xC); |
| 101 | goto set_DERA; |
| 102 | set_DERA: |
| 103 | sys->CSR_DERA = env->pc; |
| 104 | sys->CSR_DBG = FIELD_DP64(sys->CSR_DBG, CSR_DBG, DST, 1); |
| 105 | set_pc(env, sys->CSR_EENTRY + 0x480); |
| 106 | break; |
| 107 | case EXCCODE_INT: |
| 108 | if (FIELD_EX64(sys->CSR_DBG, CSR_DBG, DST)) { |
| 109 | sys->CSR_DBG = FIELD_DP64(sys->CSR_DBG, CSR_DBG, DEI, 1); |
| 110 | goto set_DERA; |
| 111 | } |
| 112 | QEMU_FALLTHROUGH; |
| 113 | case EXCCODE_PIF: |
| 114 | case EXCCODE_PNX: |
| 115 | case EXCCODE_ADEF: |
| 116 | cause = cs->exception_index; |
| 117 | update_badinstr = 0; |
| 118 | break; |
| 119 | case EXCCODE_BCE: |
| 120 | sys->CSR_BADV = env->pc; |
| 121 | QEMU_FALLTHROUGH; |
| 122 | case EXCCODE_SYS: |
| 123 | case EXCCODE_BRK: |
| 124 | case EXCCODE_INE: |
| 125 | case EXCCODE_IPE: |
| 126 | case EXCCODE_FPD: |
| 127 | case EXCCODE_FPE: |
| 128 | case EXCCODE_SXD: |
| 129 | case EXCCODE_ASXD: |
| 130 | case EXCCODE_ADEM: |
| 131 | case EXCCODE_PIL: |
| 132 | case EXCCODE_PIS: |
| 133 | case EXCCODE_PME: |
| 134 | case EXCCODE_PNR: |
| 135 | case EXCCODE_PPI: |
| 136 | cause = cs->exception_index; |
| 137 | break; |
| 138 | default: |
| 139 | qemu_log("Error: exception(%d) has not been supported\n", |
| 140 | cs->exception_index); |
| 141 | abort(); |
| 142 | } |
| 143 | |
| 144 | if (update_badinstr) { |
| 145 | MemOpIdx oi = make_memop_idx(MO_LEUL, cpu_mmu_index(cs, true)); |
| 146 | |
| 147 | sys->CSR_BADI = cpu_ldl_code_mmu(env, env->pc, oi, 0); |
| 148 | } |
| 149 | |
| 150 | /* Save PLV and IE */ |
| 151 | if (tlbfill) { |
| 152 | sys->CSR_TLBRPRMD = FIELD_DP64(sys->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV, |
| 153 | FIELD_EX64(sys->CSR_CRMD, |
| 154 | CSR_CRMD, PLV)); |
| 155 | sys->CSR_TLBRPRMD = FIELD_DP64(sys->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE, |
| 156 | FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, IE)); |
| 157 | /* set the DA mode */ |
| 158 | sys->CSR_CRMD = FIELD_DP64(sys->CSR_CRMD, CSR_CRMD, DA, 1); |
| 159 | sys->CSR_CRMD = FIELD_DP64(sys->CSR_CRMD, CSR_CRMD, PG, 0); |
| 160 | sys->CSR_TLBRERA = FIELD_DP64(sys->CSR_TLBRERA, CSR_TLBRERA, |
| 161 | PC, (env->pc >> 2)); |
| 162 | } else { |
| 163 | sys->CSR_ESTAT = FIELD_DP64(sys->CSR_ESTAT, CSR_ESTAT, ECODE, |
| 164 | EXCODE_MCODE(cause)); |
| 165 | sys->CSR_ESTAT = FIELD_DP64(sys->CSR_ESTAT, CSR_ESTAT, ESUBCODE, |
| 166 | EXCODE_SUBCODE(cause)); |
| 167 | sys->CSR_PRMD = FIELD_DP64(sys->CSR_PRMD, CSR_PRMD, PPLV, |
| 168 | FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, PLV)); |
| 169 | sys->CSR_PRMD = FIELD_DP64(sys->CSR_PRMD, CSR_PRMD, PIE, |
| 170 | FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, IE)); |
| 171 | sys->CSR_ERA = env->pc; |
| 172 | } |
| 173 | |
| 174 | sys->CSR_CRMD = FIELD_DP64(sys->CSR_CRMD, CSR_CRMD, PLV, 0); |
| 175 | sys->CSR_CRMD = FIELD_DP64(sys->CSR_CRMD, CSR_CRMD, IE, 0); |
| 176 | |
| 177 | if (vec_size) { |
| 178 | vec_size = (1 << vec_size) * 4; |
| 179 | } |
| 180 | |
| 181 | if (cs->exception_index == EXCCODE_INT) { |
| 182 | /* Interrupt */ |
| 183 | uint32_t vector = 0; |
| 184 | uint32_t pending = FIELD_EX64(sys->CSR_ESTAT, CSR_ESTAT, IS); |
| 185 | pending &= FIELD_EX64(sys->CSR_ECFG, CSR_ECFG, LIE); |
| 186 | |
| 187 | /* Find the highest-priority interrupt. */ |
| 188 | vector = 31 - clz32(pending); |
| 189 | set_pc(env, sys->CSR_EENTRY + \ |
| 190 | (EXCCODE_EXTERNAL_INT + vector) * vec_size); |
| 191 | qemu_log_mask(CPU_LOG_INT, |
| 192 | "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx |
| 193 | " cause %d\n" " A " TARGET_FMT_lx " D " |
| 194 | TARGET_FMT_lx " vector = %d ExC " TARGET_FMT_lx "ExS" |
| 195 | TARGET_FMT_lx "\n", |
| 196 | __func__, env->pc, sys->CSR_ERA, |
| 197 | cause, sys->CSR_BADV, sys->CSR_DERA, vector, |
| 198 | sys->CSR_ECFG, sys->CSR_ESTAT); |
| 199 | qemu_plugin_vcpu_interrupt_cb(cs, last_pc); |
| 200 | } else { |
| 201 | if (tlbfill) { |
| 202 | set_pc(env, sys->CSR_TLBRENTRY); |
| 203 | } else { |
| 204 | set_pc(env, sys->CSR_EENTRY + EXCODE_MCODE(cause) * vec_size); |
| 205 | } |
| 206 | qemu_log_mask(CPU_LOG_INT, |
| 207 | "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx |
| 208 | " cause %d%s\n, ESTAT " TARGET_FMT_lx |
| 209 | " EXCFG " TARGET_FMT_lx " BADVA " TARGET_FMT_lx |
| 210 | "BADI " TARGET_FMT_lx " SYS_NUM " TARGET_FMT_lu |
| 211 | " cpu %d asid " TARGET_FMT_lx "\n", __func__, env->pc, |
| 212 | tlbfill ? sys->CSR_TLBRERA : sys->CSR_ERA, |
| 213 | cause, tlbfill ? "(refill)" : "", sys->CSR_ESTAT, |
| 214 | sys->CSR_ECFG, |
| 215 | tlbfill ? sys->CSR_TLBRBADV : sys->CSR_BADV, |
| 216 | sys->CSR_BADI, env->gpr[11], cs->cpu_index, |
| 217 | sys->CSR_ASID); |
| 218 | qemu_plugin_vcpu_exception_cb(cs, last_pc); |
| 219 | } |
| 220 | cs->exception_index = -1; |
| 221 | } |
| 222 | |
| 223 | static void loongarch_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr, |
| 224 | vaddr addr, unsigned size, |
| 225 | MMUAccessType access_type, |
| 226 | int mmu_idx, MemTxAttrs attrs, |
| 227 | MemTxResult response, |
| 228 | uintptr_t retaddr) |
| 229 | { |
| 230 | CPULoongArchState *env = cpu_env(cs); |
| 231 | CPUSysState *sys = env_sys(env); |
| 232 | |
| 233 | sys->CSR_BADV = addr; |
| 234 | if (access_type == MMU_INST_FETCH) { |
| 235 | do_raise_exception(env, EXCCODE_ADEF, retaddr); |
| 236 | } else { |
| 237 | do_raise_exception(env, EXCCODE_ADEM, retaddr); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | static inline bool cpu_loongarch_hw_interrupts_enabled(CPULoongArchState *env) |
| 242 | { |
| 243 | bool ret = 0; |
| 244 | CPUSysState *sys = env_sys(env); |
| 245 | |
| 246 | ret = (FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, IE) && |
| 247 | !(FIELD_EX64(sys->CSR_DBG, CSR_DBG, DST))); |
| 248 | |
| 249 | return ret; |
| 250 | } |
| 251 | |
| 252 | static bool loongarch_cpu_exec_interrupt(CPUState *cs, int interrupt_request) |
| 253 | { |
| 254 | if (interrupt_request & CPU_INTERRUPT_HARD) { |
| 255 | CPULoongArchState *env = cpu_env(cs); |
| 256 | |
| 257 | if (cpu_loongarch_hw_interrupts_enabled(env) && |
| 258 | cpu_loongarch_hw_interrupts_pending(env)) { |
| 259 | /* Raise it */ |
| 260 | cs->exception_index = EXCCODE_INT; |
| 261 | loongarch_cpu_do_interrupt(cs); |
| 262 | return true; |
| 263 | } |
| 264 | } |
| 265 | return false; |
| 266 | } |
| 267 | |
| 268 | static vaddr loongarch_pointer_wrap(CPUState *cs, int mmu_idx, |
| 269 | vaddr result, vaddr base) |
| 270 | { |
| 271 | return is_va32(cpu_env(cs)) ? (uint32_t)result : result; |
| 272 | } |
| 273 | #endif |
| 274 | |
| 275 | static TCGTBCPUState loongarch_get_tb_cpu_state(CPUState *cs) |
| 276 | { |
| 277 | CPULoongArchState *env = cpu_env(cs); |
| 278 | CPUSysState *sys = env_sys(env); |
| 279 | uint32_t flags; |
| 280 | |
| 281 | flags = sys->CSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK); |
| 282 | flags |= FIELD_EX64(sys->CSR_EUEN, CSR_EUEN, FPE) * HW_FLAGS_EUEN_FPE; |
| 283 | flags |= FIELD_EX64(sys->CSR_EUEN, CSR_EUEN, SXE) * HW_FLAGS_EUEN_SXE; |
| 284 | flags |= FIELD_EX64(sys->CSR_EUEN, CSR_EUEN, ASXE) * HW_FLAGS_EUEN_ASXE; |
| 285 | flags |= is_va32(env) * HW_FLAGS_VA32; |
| 286 | |
| 287 | return (TCGTBCPUState){ .pc = env->pc, .flags = flags }; |
| 288 | } |
| 289 | |
| 290 | static void loongarch_cpu_synchronize_from_tb(CPUState *cs, |
| 291 | const TranslationBlock *tb) |
| 292 | { |
| 293 | tcg_debug_assert(!tcg_cflags_has(cs, CF_PCREL)); |
| 294 | set_pc(cpu_env(cs), tb->pc); |
| 295 | } |
| 296 | |
| 297 | static void loongarch_restore_state_to_opc(CPUState *cs, |
| 298 | const TranslationBlock *tb, |
| 299 | const uint64_t *data) |
| 300 | { |
| 301 | set_pc(cpu_env(cs), data[0]); |
| 302 | } |
| 303 | |
| 304 | static int loongarch_cpu_mmu_index(CPUState *cs, bool ifetch) |
| 305 | { |
| 306 | CPULoongArchState *env = cpu_env(cs); |
| 307 | CPUSysState *sys = env_sys(env); |
| 308 | |
| 309 | if (FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, PG)) { |
| 310 | return FIELD_EX64(sys->CSR_CRMD, CSR_CRMD, PLV); |
| 311 | } |
| 312 | return MMU_DA_IDX; |
| 313 | } |
| 314 | |
| 315 | const TCGCPUOps loongarch_tcg_ops = { |
| 316 | .guest_default_memory_order = 0, |
| 317 | .mttcg_supported = true, |
| 318 | |
| 319 | .initialize = loongarch_translate_init, |
| 320 | .translate_code = loongarch_translate_code, |
| 321 | .get_tb_cpu_state = loongarch_get_tb_cpu_state, |
| 322 | .synchronize_from_tb = loongarch_cpu_synchronize_from_tb, |
| 323 | .restore_state_to_opc = loongarch_restore_state_to_opc, |
| 324 | .mmu_index = loongarch_cpu_mmu_index, |
| 325 | |
| 326 | #ifndef CONFIG_USER_ONLY |
| 327 | .tlb_fill = loongarch_cpu_tlb_fill, |
| 328 | .pointer_wrap = loongarch_pointer_wrap, |
| 329 | .cpu_exec_interrupt = loongarch_cpu_exec_interrupt, |
| 330 | .cpu_exec_halt = loongarch_cpu_has_work, |
| 331 | .cpu_exec_reset = cpu_reset, |
| 332 | .do_interrupt = loongarch_cpu_do_interrupt, |
| 333 | .do_transaction_failed = loongarch_cpu_do_transaction_failed, |
| 334 | #endif |
| 335 | }; |