]>
Commit | Line | Data |
---|---|---|
65c5b75c MC |
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" | |
65c5b75c MC |
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 { | |
aad5ac23 | 115 | qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n"); |
65c5b75c MC |
116 | } |
117 | } else { | |
aad5ac23 AF |
118 | qemu_log_mask(LOG_GUEST_ERROR, |
119 | "ignoring pmpcfg write - out of bounds\n"); | |
65c5b75c MC |
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); | |
71a150bc | 141 | target_ulong base = (a & ~(((target_ulong)1 << t1) - 1)) << 2; |
65c5b75c MC |
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, | |
cc0fdb29 | 231 | target_ulong size, pmp_priv_t privs, target_ulong mode) |
65c5b75c MC |
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); | |
49db9fa1 | 248 | e = pmp_is_in_range(env, i, addr + size - 1); |
65c5b75c MC |
249 | |
250 | /* partially inside */ | |
251 | if ((s + e) == 1) { | |
aad5ac23 AF |
252 | qemu_log_mask(LOG_GUEST_ERROR, |
253 | "pmp violation - access is partially inside\n"); | |
65c5b75c MC |
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); | |
65c5b75c | 261 | |
f8162068 HA |
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)) { | |
65c5b75c | 267 | allowed_privs = PMP_READ | PMP_WRITE | PMP_EXEC; |
cc0fdb29 | 268 | if ((mode != PRV_M) || pmp_is_locked(env, i)) { |
65c5b75c MC |
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) { | |
cc0fdb29 | 284 | if (mode == PRV_M) { |
65c5b75c MC |
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)) { | |
aad5ac23 AF |
311 | qemu_log_mask(LOG_GUEST_ERROR, |
312 | "ignoring pmpcfg write - incorrect address\n"); | |
65c5b75c MC |
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; | |
4a9b31b8 | 331 | target_ulong val = 0; |
65c5b75c MC |
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 { | |
aad5ac23 AF |
359 | qemu_log_mask(LOG_GUEST_ERROR, |
360 | "ignoring pmpaddr write - locked\n"); | |
65c5b75c MC |
361 | } |
362 | } else { | |
aad5ac23 AF |
363 | qemu_log_mask(LOG_GUEST_ERROR, |
364 | "ignoring pmpaddr write - out of bounds\n"); | |
65c5b75c MC |
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 { | |
aad5ac23 AF |
380 | qemu_log_mask(LOG_GUEST_ERROR, |
381 | "ignoring pmpaddr read - out of bounds\n"); | |
65c5b75c MC |
382 | return 0; |
383 | } | |
384 | } | |
385 | ||
386 | #endif |