1 /*
2  * RISC-V Emulation Helpers for QEMU.
3  *
4  * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu
5  * Copyright (c) 2017-2018 SiFive, Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms and conditions of the GNU General Public License,
9  * version 2 or later, as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "qemu/osdep.h"
21 #include "qemu/log.h"
22 #include "cpu.h"
23 #include "qemu/main-loop.h"
24 #include "exec/exec-all.h"
25 #include "exec/helper-proto.h"
26 #ifdef TARGET_CHERI
27 #include "cheri-helper-utils.h"
28 #endif
29 
30 
exception_str(uint32_t exception)31 static inline const char* exception_str(uint32_t exception)
32 {
33     // See Table 3.6 In privileged ISA spec (20190608-Priv-MSU-Ratified)
34     switch(exception) {
35     case RISCV_EXCP_INST_ADDR_MIS: return "Instruction address misaligned";
36     case RISCV_EXCP_INST_ACCESS_FAULT: return "Instruction access fault";
37     case RISCV_EXCP_ILLEGAL_INST: return "Illegal instruction";
38     case RISCV_EXCP_BREAKPOINT: return "Breakpoint";
39     case RISCV_EXCP_LOAD_ADDR_MIS: return "Load address misaligned";
40     case RISCV_EXCP_LOAD_ACCESS_FAULT: return "Load access fault";
41     case RISCV_EXCP_STORE_AMO_ADDR_MIS: return "Store/AMO address misaligned";
42     case RISCV_EXCP_STORE_AMO_ACCESS_FAULT: return "Store/AMO access fault";
43     case RISCV_EXCP_U_ECALL: return "Environment call from U-mode";
44     case RISCV_EXCP_S_ECALL: return "Environment call from S-mode";
45     case RISCV_EXCP_VS_ECALL: return "Environment call from H-mode";
46     case RISCV_EXCP_M_ECALL: return "Environment call from M-mode";
47     case RISCV_EXCP_INST_PAGE_FAULT: return "Instruction page fault";
48     case RISCV_EXCP_LOAD_PAGE_FAULT: return "Load page fault";
49     // 14 Reserved for future standard use
50     case RISCV_EXCP_STORE_PAGE_FAULT: return "Store/AMO page fault";
51     // 16–23 Reserved for future standard use
52     case RISCV_EXCP_INST_GUEST_PAGE_FAULT: return "Guest instruction page fault";
53     case RISCV_EXCP_LOAD_GUEST_ACCESS_FAULT: return "Guest load page fault";
54     case RISCV_EXCP_STORE_GUEST_AMO_ACCESS_FAULT: return "Guest store/AMO page fault";
55     // 24-31 Reserved for custom use (we use 28/0x1c) for CHERI
56     case RISCV_EXCP_CHERI: return "CHERI fault";
57     // 32–47 Reserved for future standard use
58     // 48-63 Reserved for custom use
59     // >64 Reserved for future standard use
60     default: return "Unknown exception";
61     }
62 }
63 
64 /* Exceptions processing helpers */
riscv_raise_exception(CPURISCVState * env,uint32_t exception,uintptr_t pc)65 void QEMU_NORETURN riscv_raise_exception(CPURISCVState *env,
66                                           uint32_t exception, uintptr_t pc)
67 {
68     CPUState *cs = env_cpu(env);
69     qemu_log_mask(CPU_LOG_INT, "%s: %s (%d)\n", __func__, exception_str(exception), exception);
70     cs->exception_index = exception;
71     // Expand this call to print debug info: cpu_loop_exit_restore(cs, pc);
72     if (pc) {
73         cpu_restore_state(cs, pc, true);
74     }
75     if (exception == RISCV_EXCP_ILLEGAL_INST) {
76         // Try to fetch the faulting instruction and store it in badaddr
77         uint32_t opcode = 0;
78         int ret = cpu_memory_rw_debug(env_cpu(env), PC_ADDR(env),
79                                       (uint8_t *)&opcode, sizeof(opcode),
80                                       /*is_write=*/false);
81         opcode = tswap32(opcode); // FIXME is this needed?
82         if (ret != 0) {
83             warn_report("RISCV_EXCP_ILLEGAL_INST: Could not read %zu bytes at "
84                         "vaddr 0x" TARGET_FMT_lx "\r\n",
85                         sizeof(opcode), PC_ADDR(env));
86         } else {
87             env->badaddr = opcode;
88         }
89     }
90     cpu_loop_exit(cs);
91 }
92 
helper_raise_exception(CPURISCVState * env,uint32_t exception)93 void helper_raise_exception(CPURISCVState *env, uint32_t exception)
94 {
95     riscv_raise_exception(env, exception, 0);
96 }
97 
helper_csrrw(CPURISCVState * env,target_ulong src,target_ulong csr)98 target_ulong helper_csrrw(CPURISCVState *env, target_ulong src,
99         target_ulong csr)
100 {
101     target_ulong val = 0;
102     if (riscv_csrrw(env, csr, &val, src, -1, GETPC()) < 0) {
103         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
104     }
105     return val;
106 }
107 
helper_csrrs(CPURISCVState * env,target_ulong src,target_ulong csr,target_ulong rs1_pass)108 target_ulong helper_csrrs(CPURISCVState *env, target_ulong src,
109         target_ulong csr, target_ulong rs1_pass)
110 {
111     target_ulong val = 0;
112     if (riscv_csrrw(env, csr, &val, -1, rs1_pass ? src : 0, GETPC()) < 0) {
113         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
114     }
115     return val;
116 }
117 
helper_csrrc(CPURISCVState * env,target_ulong src,target_ulong csr,target_ulong rs1_pass)118 target_ulong helper_csrrc(CPURISCVState *env, target_ulong src,
119         target_ulong csr, target_ulong rs1_pass)
120 {
121     target_ulong val = 0;
122     if (riscv_csrrw(env, csr, &val, 0, rs1_pass ? src : 0, GETPC()) < 0) {
123         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
124     }
125     return val;
126 }
127 
128 #ifndef CONFIG_USER_ONLY
129 
helper_sret(CPURISCVState * env,target_ulong cpu_pc_deb)130 target_ulong helper_sret(CPURISCVState *env, target_ulong cpu_pc_deb)
131 {
132     target_ulong prev_priv, prev_virt, mstatus;
133 
134     if (!(env->priv >= PRV_S)) {
135         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
136     }
137 
138     target_ulong retpc = GET_SPECIAL_REG(env, sepc, SEPCC);
139 
140     if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) {
141         riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC());
142     }
143 
144     if (env->priv_ver >= PRIV_VERSION_1_10_0 &&
145         get_field(env->mstatus, MSTATUS_TSR) && !(env->priv >= PRV_M)) {
146         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
147     }
148 
149     mstatus = env->mstatus;
150 
151     if (riscv_has_ext(env, RVH) && !riscv_cpu_virt_enabled(env)) {
152         /* We support Hypervisor extensions and virtulisation is disabled */
153         target_ulong hstatus = env->hstatus;
154 
155         prev_priv = get_field(mstatus, MSTATUS_SPP);
156         prev_virt = get_field(hstatus, HSTATUS_SPV);
157 
158         hstatus = set_field(hstatus, HSTATUS_SPV,
159                                  get_field(hstatus, HSTATUS_SP2V));
160         mstatus = set_field(mstatus, MSTATUS_SPP,
161                             get_field(hstatus, HSTATUS_SP2P));
162         hstatus = set_field(hstatus, HSTATUS_SP2V, 0);
163         hstatus = set_field(hstatus, HSTATUS_SP2P, 0);
164         mstatus = set_field(mstatus, SSTATUS_SIE,
165                             get_field(mstatus, SSTATUS_SPIE));
166         mstatus = set_field(mstatus, SSTATUS_SPIE, 1);
167 
168         env->mstatus = mstatus;
169         env->hstatus = hstatus;
170 
171         if (prev_virt) {
172             riscv_cpu_swap_hypervisor_regs(env);
173         }
174 
175         riscv_cpu_set_virt_enabled(env, prev_virt);
176     } else {
177         prev_priv = get_field(mstatus, MSTATUS_SPP);
178 
179         mstatus = set_field(mstatus,
180             env->priv_ver >= PRIV_VERSION_1_10_0 ?
181             MSTATUS_SIE : MSTATUS_UIE << prev_priv,
182             get_field(mstatus, MSTATUS_SPIE));
183         mstatus = set_field(mstatus, MSTATUS_SPIE, 1);
184         mstatus = set_field(mstatus, MSTATUS_SPP, PRV_U);
185         env->mstatus = mstatus;
186     }
187 
188     riscv_cpu_set_mode(env, prev_priv);
189 
190 #ifdef TARGET_CHERI
191     qemu_log_mask_and_addr(CPU_LOG_INSTR, cpu_get_recent_pc(env),
192                            "%s: Updating PCC from SEPCC: " PRINT_CAP_FMTSTR "\n",
193                            __func__, PRINT_CAP_ARGS(&env->SEPCC));
194     cheri_update_pcc_for_exc_return(&env->PCC, &env->SEPCC, retpc);
195 #endif
196     return retpc;
197 }
198 
helper_mret(CPURISCVState * env,target_ulong cpu_pc_deb)199 target_ulong helper_mret(CPURISCVState *env, target_ulong cpu_pc_deb)
200 {
201     if (!(env->priv >= PRV_M)) {
202         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
203     }
204 
205     target_ulong retpc = GET_SPECIAL_REG(env, mepc, MEPCC);
206     if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) {
207         riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC());
208     }
209 
210     target_ulong mstatus = env->mstatus;
211     target_ulong prev_priv = get_field(mstatus, MSTATUS_MPP);
212     target_ulong prev_virt = MSTATUS_MPV_ISSET(env);
213     mstatus = set_field(mstatus,
214         env->priv_ver >= PRIV_VERSION_1_10_0 ?
215         MSTATUS_MIE : MSTATUS_UIE << prev_priv,
216         get_field(mstatus, MSTATUS_MPIE));
217     mstatus = set_field(mstatus, MSTATUS_MPIE, 1);
218     mstatus = set_field(mstatus, MSTATUS_MPP, PRV_U);
219 #ifdef TARGET_RISCV32
220     env->mstatush = set_field(env->mstatush, MSTATUS_MPV, 0);
221 #else
222     mstatus = set_field(mstatus, MSTATUS_MPV, 0);
223 #endif
224     env->mstatus = mstatus;
225     riscv_cpu_set_mode(env, prev_priv);
226 
227     if (riscv_has_ext(env, RVH)) {
228         if (prev_virt) {
229             riscv_cpu_swap_hypervisor_regs(env);
230         }
231 
232         riscv_cpu_set_virt_enabled(env, prev_virt);
233     }
234 
235 #ifdef TARGET_CHERI
236     qemu_log_mask_and_addr(CPU_LOG_INSTR, cpu_get_recent_pc(env),
237                            "%s: Updating PCC from MEPCC: " PRINT_CAP_FMTSTR "\n",
238                            __func__, PRINT_CAP_ARGS(&env->MEPCC));
239     cheri_update_pcc_for_exc_return(&env->PCC, &env->MEPCC, retpc);
240 #endif
241     return retpc;
242 }
243 
244 #ifdef CONFIG_MIPS_LOG_INSTR
HELPER(log_gpr_write)245 void HELPER(log_gpr_write)(uint32_t regnum, target_ulong value, target_ulong pc)
246 {
247     qemu_log_mask_and_addr(CPU_LOG_INSTR, pc,
248                            "    Write %s = " TARGET_FMT_lx "\n",
249                            riscv_int_regnames[regnum], value);
250 }
251 #endif
252 
helper_wfi(CPURISCVState * env)253 void helper_wfi(CPURISCVState *env)
254 {
255     CPUState *cs = env_cpu(env);
256 
257     if ((env->priv == PRV_S &&
258         env->priv_ver >= PRIV_VERSION_1_10_0 &&
259         get_field(env->mstatus, MSTATUS_TW)) ||
260         riscv_cpu_virt_enabled(env)) {
261         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
262     } else {
263         cs->halted = 1;
264         cs->exception_index = EXCP_HLT;
265         cpu_loop_exit(cs);
266     }
267 }
268 
helper_tlb_flush(CPURISCVState * env)269 void helper_tlb_flush(CPURISCVState *env)
270 {
271     CPUState *cs = env_cpu(env);
272     if (!(env->priv >= PRV_S) ||
273         (env->priv == PRV_S &&
274          env->priv_ver >= PRIV_VERSION_1_10_0 &&
275          get_field(env->mstatus, MSTATUS_TVM))) {
276         riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
277     } else {
278         tlb_flush(cs);
279     }
280 }
281 
282 #endif /* !CONFIG_USER_ONLY */
283