1 | /* |
2 | * QEMU RISC-V PMP (Physical Memory Protection) |
3 | * |
4 | * Author: Daire McNamara, daire.mcnamara@emdalo.com |
5 | * Ivan Griffin, ivan.griffin@emdalo.com |
6 | * |
7 | * This provides a RISC-V Physical Memory Protection implementation |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify it |
10 | * under the terms and conditions of the GNU General Public License, |
11 | * version 2 or later, as published by the Free Software Foundation. |
12 | * |
13 | * This program is distributed in the hope it will be useful, but WITHOUT |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
16 | * more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License along with |
19 | * this program. If not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | /* |
23 | * PMP (Physical Memory Protection) is as-of-yet unused and needs testing. |
24 | */ |
25 | |
26 | #include "qemu/osdep.h" |
27 | #include "qemu/log.h" |
28 | #include "qapi/error.h" |
29 | #include "cpu.h" |
30 | |
31 | #ifndef CONFIG_USER_ONLY |
32 | |
33 | #define RISCV_DEBUG_PMP 0 |
34 | #define PMP_DEBUG(fmt, ...) \ |
35 | do { \ |
36 | if (RISCV_DEBUG_PMP) { \ |
37 | qemu_log_mask(LOG_TRACE, "%s: " fmt "\n", __func__, ##__VA_ARGS__);\ |
38 | } \ |
39 | } while (0) |
40 | |
41 | static void pmp_write_cfg(CPURISCVState *env, uint32_t addr_index, |
42 | uint8_t val); |
43 | static uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t addr_index); |
44 | static void pmp_update_rule(CPURISCVState *env, uint32_t pmp_index); |
45 | |
46 | /* |
47 | * Accessor method to extract address matching type 'a field' from cfg reg |
48 | */ |
49 | static inline uint8_t pmp_get_a_field(uint8_t cfg) |
50 | { |
51 | uint8_t a = cfg >> 3; |
52 | return a & 0x3; |
53 | } |
54 | |
55 | /* |
56 | * Check whether a PMP is locked or not. |
57 | */ |
58 | static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) |
59 | { |
60 | |
61 | if (env->pmp_state.pmp[pmp_index].cfg_reg & PMP_LOCK) { |
62 | return 1; |
63 | } |
64 | |
65 | /* Top PMP has no 'next' to check */ |
66 | if ((pmp_index + 1u) >= MAX_RISCV_PMPS) { |
67 | return 0; |
68 | } |
69 | |
70 | /* In TOR mode, need to check the lock bit of the next pmp |
71 | * (if there is a next) |
72 | */ |
73 | const uint8_t a_field = |
74 | pmp_get_a_field(env->pmp_state.pmp[pmp_index + 1].cfg_reg); |
75 | if ((env->pmp_state.pmp[pmp_index + 1u].cfg_reg & PMP_LOCK) && |
76 | (PMP_AMATCH_TOR == a_field)) { |
77 | return 1; |
78 | } |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | /* |
84 | * Count the number of active rules. |
85 | */ |
86 | static inline uint32_t pmp_get_num_rules(CPURISCVState *env) |
87 | { |
88 | return env->pmp_state.num_rules; |
89 | } |
90 | |
91 | /* |
92 | * Accessor to get the cfg reg for a specific PMP/HART |
93 | */ |
94 | static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) |
95 | { |
96 | if (pmp_index < MAX_RISCV_PMPS) { |
97 | return env->pmp_state.pmp[pmp_index].cfg_reg; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | |
104 | /* |
105 | * Accessor to set the cfg reg for a specific PMP/HART |
106 | * Bounds checks and relevant lock bit. |
107 | */ |
108 | static void pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) |
109 | { |
110 | if (pmp_index < MAX_RISCV_PMPS) { |
111 | if (!pmp_is_locked(env, pmp_index)) { |
112 | env->pmp_state.pmp[pmp_index].cfg_reg = val; |
113 | pmp_update_rule(env, pmp_index); |
114 | } else { |
115 | qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n" ); |
116 | } |
117 | } else { |
118 | qemu_log_mask(LOG_GUEST_ERROR, |
119 | "ignoring pmpcfg write - out of bounds\n" ); |
120 | } |
121 | } |
122 | |
123 | static void pmp_decode_napot(target_ulong a, target_ulong *sa, target_ulong *ea) |
124 | { |
125 | /* |
126 | aaaa...aaa0 8-byte NAPOT range |
127 | aaaa...aa01 16-byte NAPOT range |
128 | aaaa...a011 32-byte NAPOT range |
129 | ... |
130 | aa01...1111 2^XLEN-byte NAPOT range |
131 | a011...1111 2^(XLEN+1)-byte NAPOT range |
132 | 0111...1111 2^(XLEN+2)-byte NAPOT range |
133 | 1111...1111 Reserved |
134 | */ |
135 | if (a == -1) { |
136 | *sa = 0u; |
137 | *ea = -1; |
138 | return; |
139 | } else { |
140 | target_ulong t1 = ctz64(~a); |
141 | target_ulong base = (a & ~(((target_ulong)1 << t1) - 1)) << 2; |
142 | target_ulong range = ((target_ulong)1 << (t1 + 3)) - 1; |
143 | *sa = base; |
144 | *ea = base + range; |
145 | } |
146 | } |
147 | |
148 | |
149 | /* Convert cfg/addr reg values here into simple 'sa' --> start address and 'ea' |
150 | * end address values. |
151 | * This function is called relatively infrequently whereas the check that |
152 | * an address is within a pmp rule is called often, so optimise that one |
153 | */ |
154 | static void pmp_update_rule(CPURISCVState *env, uint32_t pmp_index) |
155 | { |
156 | int i; |
157 | |
158 | env->pmp_state.num_rules = 0; |
159 | |
160 | uint8_t this_cfg = env->pmp_state.pmp[pmp_index].cfg_reg; |
161 | target_ulong this_addr = env->pmp_state.pmp[pmp_index].addr_reg; |
162 | target_ulong prev_addr = 0u; |
163 | target_ulong sa = 0u; |
164 | target_ulong ea = 0u; |
165 | |
166 | if (pmp_index >= 1u) { |
167 | prev_addr = env->pmp_state.pmp[pmp_index - 1].addr_reg; |
168 | } |
169 | |
170 | switch (pmp_get_a_field(this_cfg)) { |
171 | case PMP_AMATCH_OFF: |
172 | sa = 0u; |
173 | ea = -1; |
174 | break; |
175 | |
176 | case PMP_AMATCH_TOR: |
177 | sa = prev_addr << 2; /* shift up from [xx:0] to [xx+2:2] */ |
178 | ea = (this_addr << 2) - 1u; |
179 | break; |
180 | |
181 | case PMP_AMATCH_NA4: |
182 | sa = this_addr << 2; /* shift up from [xx:0] to [xx+2:2] */ |
183 | ea = (this_addr + 4u) - 1u; |
184 | break; |
185 | |
186 | case PMP_AMATCH_NAPOT: |
187 | pmp_decode_napot(this_addr, &sa, &ea); |
188 | break; |
189 | |
190 | default: |
191 | sa = 0u; |
192 | ea = 0u; |
193 | break; |
194 | } |
195 | |
196 | env->pmp_state.addr[pmp_index].sa = sa; |
197 | env->pmp_state.addr[pmp_index].ea = ea; |
198 | |
199 | for (i = 0; i < MAX_RISCV_PMPS; i++) { |
200 | const uint8_t a_field = |
201 | pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); |
202 | if (PMP_AMATCH_OFF != a_field) { |
203 | env->pmp_state.num_rules++; |
204 | } |
205 | } |
206 | } |
207 | |
208 | static int pmp_is_in_range(CPURISCVState *env, int pmp_index, target_ulong addr) |
209 | { |
210 | int result = 0; |
211 | |
212 | if ((addr >= env->pmp_state.addr[pmp_index].sa) |
213 | && (addr <= env->pmp_state.addr[pmp_index].ea)) { |
214 | result = 1; |
215 | } else { |
216 | result = 0; |
217 | } |
218 | |
219 | return result; |
220 | } |
221 | |
222 | |
223 | /* |
224 | * Public Interface |
225 | */ |
226 | |
227 | /* |
228 | * Check if the address has required RWX privs to complete desired operation |
229 | */ |
230 | bool pmp_hart_has_privs(CPURISCVState *env, target_ulong addr, |
231 | target_ulong size, pmp_priv_t privs, target_ulong mode) |
232 | { |
233 | int i = 0; |
234 | int ret = -1; |
235 | target_ulong s = 0; |
236 | target_ulong e = 0; |
237 | pmp_priv_t allowed_privs = 0; |
238 | |
239 | /* Short cut if no rules */ |
240 | if (0 == pmp_get_num_rules(env)) { |
241 | return true; |
242 | } |
243 | |
244 | /* 1.10 draft priv spec states there is an implicit order |
245 | from low to high */ |
246 | for (i = 0; i < MAX_RISCV_PMPS; i++) { |
247 | s = pmp_is_in_range(env, i, addr); |
248 | e = pmp_is_in_range(env, i, addr + size - 1); |
249 | |
250 | /* partially inside */ |
251 | if ((s + e) == 1) { |
252 | qemu_log_mask(LOG_GUEST_ERROR, |
253 | "pmp violation - access is partially inside\n" ); |
254 | ret = 0; |
255 | break; |
256 | } |
257 | |
258 | /* fully inside */ |
259 | const uint8_t a_field = |
260 | pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); |
261 | |
262 | /* |
263 | * If the PMP entry is not off and the address is in range, do the priv |
264 | * check |
265 | */ |
266 | if (((s + e) == 2) && (PMP_AMATCH_OFF != a_field)) { |
267 | allowed_privs = PMP_READ | PMP_WRITE | PMP_EXEC; |
268 | if ((mode != PRV_M) || pmp_is_locked(env, i)) { |
269 | allowed_privs &= env->pmp_state.pmp[i].cfg_reg; |
270 | } |
271 | |
272 | if ((privs & allowed_privs) == privs) { |
273 | ret = 1; |
274 | break; |
275 | } else { |
276 | ret = 0; |
277 | break; |
278 | } |
279 | } |
280 | } |
281 | |
282 | /* No rule matched */ |
283 | if (ret == -1) { |
284 | if (mode == PRV_M) { |
285 | ret = 1; /* Privileged spec v1.10 states if no PMP entry matches an |
286 | * M-Mode access, the access succeeds */ |
287 | } else { |
288 | ret = 0; /* Other modes are not allowed to succeed if they don't |
289 | * match a rule, but there are rules. We've checked for |
290 | * no rule earlier in this function. */ |
291 | } |
292 | } |
293 | |
294 | return ret == 1 ? true : false; |
295 | } |
296 | |
297 | |
298 | /* |
299 | * Handle a write to a pmpcfg CSP |
300 | */ |
301 | void pmpcfg_csr_write(CPURISCVState *env, uint32_t reg_index, |
302 | target_ulong val) |
303 | { |
304 | int i; |
305 | uint8_t cfg_val; |
306 | |
307 | PMP_DEBUG("hart " TARGET_FMT_ld ": reg%d, val: 0x" TARGET_FMT_lx, |
308 | env->mhartid, reg_index, val); |
309 | |
310 | if ((reg_index & 1) && (sizeof(target_ulong) == 8)) { |
311 | qemu_log_mask(LOG_GUEST_ERROR, |
312 | "ignoring pmpcfg write - incorrect address\n" ); |
313 | return; |
314 | } |
315 | |
316 | for (i = 0; i < sizeof(target_ulong); i++) { |
317 | cfg_val = (val >> 8 * i) & 0xff; |
318 | pmp_write_cfg(env, (reg_index * sizeof(target_ulong)) + i, |
319 | cfg_val); |
320 | } |
321 | } |
322 | |
323 | |
324 | /* |
325 | * Handle a read from a pmpcfg CSP |
326 | */ |
327 | target_ulong pmpcfg_csr_read(CPURISCVState *env, uint32_t reg_index) |
328 | { |
329 | int i; |
330 | target_ulong cfg_val = 0; |
331 | target_ulong val = 0; |
332 | |
333 | for (i = 0; i < sizeof(target_ulong); i++) { |
334 | val = pmp_read_cfg(env, (reg_index * sizeof(target_ulong)) + i); |
335 | cfg_val |= (val << (i * 8)); |
336 | } |
337 | |
338 | PMP_DEBUG("hart " TARGET_FMT_ld ": reg%d, val: 0x" TARGET_FMT_lx, |
339 | env->mhartid, reg_index, cfg_val); |
340 | |
341 | return cfg_val; |
342 | } |
343 | |
344 | |
345 | /* |
346 | * Handle a write to a pmpaddr CSP |
347 | */ |
348 | void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, |
349 | target_ulong val) |
350 | { |
351 | PMP_DEBUG("hart " TARGET_FMT_ld ": addr%d, val: 0x" TARGET_FMT_lx, |
352 | env->mhartid, addr_index, val); |
353 | |
354 | if (addr_index < MAX_RISCV_PMPS) { |
355 | if (!pmp_is_locked(env, addr_index)) { |
356 | env->pmp_state.pmp[addr_index].addr_reg = val; |
357 | pmp_update_rule(env, addr_index); |
358 | } else { |
359 | qemu_log_mask(LOG_GUEST_ERROR, |
360 | "ignoring pmpaddr write - locked\n" ); |
361 | } |
362 | } else { |
363 | qemu_log_mask(LOG_GUEST_ERROR, |
364 | "ignoring pmpaddr write - out of bounds\n" ); |
365 | } |
366 | } |
367 | |
368 | |
369 | /* |
370 | * Handle a read from a pmpaddr CSP |
371 | */ |
372 | target_ulong pmpaddr_csr_read(CPURISCVState *env, uint32_t addr_index) |
373 | { |
374 | PMP_DEBUG("hart " TARGET_FMT_ld ": addr%d, val: 0x" TARGET_FMT_lx, |
375 | env->mhartid, addr_index, |
376 | env->pmp_state.pmp[addr_index].addr_reg); |
377 | if (addr_index < MAX_RISCV_PMPS) { |
378 | return env->pmp_state.pmp[addr_index].addr_reg; |
379 | } else { |
380 | qemu_log_mask(LOG_GUEST_ERROR, |
381 | "ignoring pmpaddr read - out of bounds\n" ); |
382 | return 0; |
383 | } |
384 | } |
385 | |
386 | #endif |
387 | |