]>
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" | |
30 | #include "qemu-common.h" | |
31 | ||
32 | #ifndef CONFIG_USER_ONLY | |
33 | ||
34 | #define RISCV_DEBUG_PMP 0 | |
35 | #define PMP_DEBUG(fmt, ...) \ | |
36 | do { \ | |
37 | if (RISCV_DEBUG_PMP) { \ | |
38 | qemu_log_mask(LOG_TRACE, "%s: " fmt "\n", __func__, ##__VA_ARGS__);\ | |
39 | } \ | |
40 | } while (0) | |
41 | ||
42 | static void pmp_write_cfg(CPURISCVState *env, uint32_t addr_index, | |
43 | uint8_t val); | |
44 | static uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t addr_index); | |
45 | static void pmp_update_rule(CPURISCVState *env, uint32_t pmp_index); | |
46 | ||
47 | /* | |
48 | * Accessor method to extract address matching type 'a field' from cfg reg | |
49 | */ | |
50 | static inline uint8_t pmp_get_a_field(uint8_t cfg) | |
51 | { | |
52 | uint8_t a = cfg >> 3; | |
53 | return a & 0x3; | |
54 | } | |
55 | ||
56 | /* | |
57 | * Check whether a PMP is locked or not. | |
58 | */ | |
59 | static inline int pmp_is_locked(CPURISCVState *env, uint32_t pmp_index) | |
60 | { | |
61 | ||
62 | if (env->pmp_state.pmp[pmp_index].cfg_reg & PMP_LOCK) { | |
63 | return 1; | |
64 | } | |
65 | ||
66 | /* Top PMP has no 'next' to check */ | |
67 | if ((pmp_index + 1u) >= MAX_RISCV_PMPS) { | |
68 | return 0; | |
69 | } | |
70 | ||
71 | /* In TOR mode, need to check the lock bit of the next pmp | |
72 | * (if there is a next) | |
73 | */ | |
74 | const uint8_t a_field = | |
75 | pmp_get_a_field(env->pmp_state.pmp[pmp_index + 1].cfg_reg); | |
76 | if ((env->pmp_state.pmp[pmp_index + 1u].cfg_reg & PMP_LOCK) && | |
77 | (PMP_AMATCH_TOR == a_field)) { | |
78 | return 1; | |
79 | } | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
84 | /* | |
85 | * Count the number of active rules. | |
86 | */ | |
87 | static inline uint32_t pmp_get_num_rules(CPURISCVState *env) | |
88 | { | |
89 | return env->pmp_state.num_rules; | |
90 | } | |
91 | ||
92 | /* | |
93 | * Accessor to get the cfg reg for a specific PMP/HART | |
94 | */ | |
95 | static inline uint8_t pmp_read_cfg(CPURISCVState *env, uint32_t pmp_index) | |
96 | { | |
97 | if (pmp_index < MAX_RISCV_PMPS) { | |
98 | return env->pmp_state.pmp[pmp_index].cfg_reg; | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | ||
105 | /* | |
106 | * Accessor to set the cfg reg for a specific PMP/HART | |
107 | * Bounds checks and relevant lock bit. | |
108 | */ | |
109 | static void pmp_write_cfg(CPURISCVState *env, uint32_t pmp_index, uint8_t val) | |
110 | { | |
111 | if (pmp_index < MAX_RISCV_PMPS) { | |
112 | if (!pmp_is_locked(env, pmp_index)) { | |
113 | env->pmp_state.pmp[pmp_index].cfg_reg = val; | |
114 | pmp_update_rule(env, pmp_index); | |
115 | } else { | |
aad5ac23 | 116 | qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpcfg write - locked\n"); |
65c5b75c MC |
117 | } |
118 | } else { | |
aad5ac23 AF |
119 | qemu_log_mask(LOG_GUEST_ERROR, |
120 | "ignoring pmpcfg write - out of bounds\n"); | |
65c5b75c MC |
121 | } |
122 | } | |
123 | ||
124 | static void pmp_decode_napot(target_ulong a, target_ulong *sa, target_ulong *ea) | |
125 | { | |
126 | /* | |
127 | aaaa...aaa0 8-byte NAPOT range | |
128 | aaaa...aa01 16-byte NAPOT range | |
129 | aaaa...a011 32-byte NAPOT range | |
130 | ... | |
131 | aa01...1111 2^XLEN-byte NAPOT range | |
132 | a011...1111 2^(XLEN+1)-byte NAPOT range | |
133 | 0111...1111 2^(XLEN+2)-byte NAPOT range | |
134 | 1111...1111 Reserved | |
135 | */ | |
136 | if (a == -1) { | |
137 | *sa = 0u; | |
138 | *ea = -1; | |
139 | return; | |
140 | } else { | |
141 | target_ulong t1 = ctz64(~a); | |
71a150bc | 142 | target_ulong base = (a & ~(((target_ulong)1 << t1) - 1)) << 2; |
65c5b75c MC |
143 | target_ulong range = ((target_ulong)1 << (t1 + 3)) - 1; |
144 | *sa = base; | |
145 | *ea = base + range; | |
146 | } | |
147 | } | |
148 | ||
149 | ||
150 | /* Convert cfg/addr reg values here into simple 'sa' --> start address and 'ea' | |
151 | * end address values. | |
152 | * This function is called relatively infrequently whereas the check that | |
153 | * an address is within a pmp rule is called often, so optimise that one | |
154 | */ | |
155 | static void pmp_update_rule(CPURISCVState *env, uint32_t pmp_index) | |
156 | { | |
157 | int i; | |
158 | ||
159 | env->pmp_state.num_rules = 0; | |
160 | ||
161 | uint8_t this_cfg = env->pmp_state.pmp[pmp_index].cfg_reg; | |
162 | target_ulong this_addr = env->pmp_state.pmp[pmp_index].addr_reg; | |
163 | target_ulong prev_addr = 0u; | |
164 | target_ulong sa = 0u; | |
165 | target_ulong ea = 0u; | |
166 | ||
167 | if (pmp_index >= 1u) { | |
168 | prev_addr = env->pmp_state.pmp[pmp_index - 1].addr_reg; | |
169 | } | |
170 | ||
171 | switch (pmp_get_a_field(this_cfg)) { | |
172 | case PMP_AMATCH_OFF: | |
173 | sa = 0u; | |
174 | ea = -1; | |
175 | break; | |
176 | ||
177 | case PMP_AMATCH_TOR: | |
178 | sa = prev_addr << 2; /* shift up from [xx:0] to [xx+2:2] */ | |
179 | ea = (this_addr << 2) - 1u; | |
180 | break; | |
181 | ||
182 | case PMP_AMATCH_NA4: | |
183 | sa = this_addr << 2; /* shift up from [xx:0] to [xx+2:2] */ | |
184 | ea = (this_addr + 4u) - 1u; | |
185 | break; | |
186 | ||
187 | case PMP_AMATCH_NAPOT: | |
188 | pmp_decode_napot(this_addr, &sa, &ea); | |
189 | break; | |
190 | ||
191 | default: | |
192 | sa = 0u; | |
193 | ea = 0u; | |
194 | break; | |
195 | } | |
196 | ||
197 | env->pmp_state.addr[pmp_index].sa = sa; | |
198 | env->pmp_state.addr[pmp_index].ea = ea; | |
199 | ||
200 | for (i = 0; i < MAX_RISCV_PMPS; i++) { | |
201 | const uint8_t a_field = | |
202 | pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); | |
203 | if (PMP_AMATCH_OFF != a_field) { | |
204 | env->pmp_state.num_rules++; | |
205 | } | |
206 | } | |
207 | } | |
208 | ||
209 | static int pmp_is_in_range(CPURISCVState *env, int pmp_index, target_ulong addr) | |
210 | { | |
211 | int result = 0; | |
212 | ||
213 | if ((addr >= env->pmp_state.addr[pmp_index].sa) | |
214 | && (addr <= env->pmp_state.addr[pmp_index].ea)) { | |
215 | result = 1; | |
216 | } else { | |
217 | result = 0; | |
218 | } | |
219 | ||
220 | return result; | |
221 | } | |
222 | ||
223 | ||
224 | /* | |
225 | * Public Interface | |
226 | */ | |
227 | ||
228 | /* | |
229 | * Check if the address has required RWX privs to complete desired operation | |
230 | */ | |
231 | bool pmp_hart_has_privs(CPURISCVState *env, target_ulong addr, | |
232 | target_ulong size, pmp_priv_t privs) | |
233 | { | |
234 | int i = 0; | |
235 | int ret = -1; | |
236 | target_ulong s = 0; | |
237 | target_ulong e = 0; | |
238 | pmp_priv_t allowed_privs = 0; | |
239 | ||
240 | /* Short cut if no rules */ | |
241 | if (0 == pmp_get_num_rules(env)) { | |
242 | return true; | |
243 | } | |
244 | ||
245 | /* 1.10 draft priv spec states there is an implicit order | |
246 | from low to high */ | |
247 | for (i = 0; i < MAX_RISCV_PMPS; i++) { | |
248 | s = pmp_is_in_range(env, i, addr); | |
249 | e = pmp_is_in_range(env, i, addr + size); | |
250 | ||
251 | /* partially inside */ | |
252 | if ((s + e) == 1) { | |
aad5ac23 AF |
253 | qemu_log_mask(LOG_GUEST_ERROR, |
254 | "pmp violation - access is partially inside\n"); | |
65c5b75c MC |
255 | ret = 0; |
256 | break; | |
257 | } | |
258 | ||
259 | /* fully inside */ | |
260 | const uint8_t a_field = | |
261 | pmp_get_a_field(env->pmp_state.pmp[i].cfg_reg); | |
262 | if ((s + e) == 2) { | |
263 | if (PMP_AMATCH_OFF == a_field) { | |
264 | return 1; | |
265 | } | |
266 | ||
267 | allowed_privs = PMP_READ | PMP_WRITE | PMP_EXEC; | |
268 | if ((env->priv != 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 (env->priv == 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)) { | |
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 |