@samitouri / QOSamiQemu / commits / 075e03706a

hw/hexagon: Add hexagon TLB device implementation

Add the hexagon TLB QOM device model implementation. Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>

Brian Cain committed Jun 22, 2026 at 15:28 UTC 075e03706aff6906d59340312a419f39dd10c0a0
1 file changed +467
hw/hexagon/hexagon_tlb.c new
+467
@@ -0,0 +1,467 @@
1 +/*
2 + * Hexagon TLB QOM Device
3 + *
4 + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
5 + * SPDX-License-Identifier: GPL-2.0-or-later
6 + */
7 +
8 +#include "qemu/osdep.h"
9 +#include "qemu/log.h"
10 +#include "hw/hexagon/hexagon_tlb.h"
11 +#include "hw/core/qdev-properties.h"
12 +#include "hw/core/resettable.h"
13 +#include "migration/vmstate.h"
14 +#include "monitor/monitor.h"
15 +#include "qapi/error.h"
16 +#include "exec/page-protection.h"
17 +#include "exec/target_page.h"
18 +#include "target/hexagon/cpu.h"
19 +#include "target/hexagon/cpu_bits.h"
20 +
21 +/* PTE (TLB entry) field extraction */
22 +#define GET_PTE_PPD(entry) extract64((entry), 0, 24)
23 +#define GET_PTE_C(entry) extract64((entry), 24, 4)
24 +#define GET_PTE_U(entry) extract64((entry), 28, 1)
25 +#define GET_PTE_R(entry) extract64((entry), 29, 1)
26 +#define GET_PTE_W(entry) extract64((entry), 30, 1)
27 +#define GET_PTE_X(entry) extract64((entry), 31, 1)
28 +#define GET_PTE_VPN(entry) extract64((entry), 32, 20)
29 +#define GET_PTE_ASID(entry) extract64((entry), 52, 7)
30 +#define GET_PTE_ATR0(entry) extract64((entry), 59, 1)
31 +#define GET_PTE_ATR1(entry) extract64((entry), 60, 1)
32 +#define GET_PTE_PA35(entry) extract64((entry), 61, 1)
33 +#define GET_PTE_G(entry) extract64((entry), 62, 1)
34 +#define GET_PTE_V(entry) extract64((entry), 63, 1)
35 +
36 +/* PPD (physical page descriptor) */
37 +static inline uint64_t GET_PPD(uint64_t entry)
38 +{
39 + return GET_PTE_PPD(entry) | (GET_PTE_PA35(entry) << 24);
40 +}
41 +
42 +#define NO_ASID (1 << 8)
43 +
44 +typedef enum {
45 + PGSIZE_4K,
46 + PGSIZE_16K,
47 + PGSIZE_64K,
48 + PGSIZE_256K,
49 + PGSIZE_1M,
50 + PGSIZE_4M,
51 + PGSIZE_16M,
52 + PGSIZE_64M,
53 + PGSIZE_256M,
54 + PGSIZE_1G,
55 +} tlb_pgsize_t;
56 +
57 +#define NUM_PGSIZE_TYPES (PGSIZE_1G + 1)
58 +
59 +static const char *pgsize_str[NUM_PGSIZE_TYPES] = {
60 + "4K",
61 + "16K",
62 + "64K",
63 + "256K",
64 + "1M",
65 + "4M",
66 + "16M",
67 + "64M",
68 + "256M",
69 + "1G",
70 +};
71 +
72 +#define INVALID_MASK 0xffffffffLL
73 +
74 +static const uint64_t encmask_2_mask[] = {
75 + 0x0fffLL, /* 4k, 0000 */
76 + 0x3fffLL, /* 16k, 0001 */
77 + 0xffffLL, /* 64k, 0010 */
78 + 0x3ffffLL, /* 256k, 0011 */
79 + 0xfffffLL, /* 1m, 0100 */
80 + 0x3fffffLL, /* 4m, 0101 */
81 + 0xffffffLL, /* 16m, 0110 */
82 + 0x3ffffffLL, /* 64m, 0111 */
83 + 0xfffffffLL, /* 256m, 1000 */
84 + 0x3fffffffLL, /* 1g, 1001 */
85 + INVALID_MASK, /* RSVD, 1010 */
86 +};
87 +
88 +static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry)
89 +{
90 + if (entry == 0) {
91 + qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n",
92 + __func__);
93 + return 0;
94 + }
95 + tlb_pgsize_t size = ctz64(entry);
96 + g_assert(size < NUM_PGSIZE_TYPES);
97 + return size;
98 +}
99 +
100 +static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry)
101 +{
102 + return 1ull << (qemu_target_page_bits() + 2 * hex_tlb_pgsize_type(entry));
103 +}
104 +
105 +static inline uint64_t hex_tlb_phys_page_num(uint64_t entry)
106 +{
107 + uint32_t ppd = GET_PPD(entry);
108 + return ppd >> 1;
109 +}
110 +
111 +static inline uint64_t hex_tlb_phys_addr(uint64_t entry)
112 +{
113 + uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)];
114 + uint64_t pagenum = hex_tlb_phys_page_num(entry);
115 + uint64_t PA = (pagenum << qemu_target_page_bits()) & (~pagemask);
116 + return PA;
117 +}
118 +
119 +static inline uint64_t hex_tlb_virt_addr(uint64_t entry)
120 +{
121 + return (uint64_t)GET_PTE_VPN(entry) << qemu_target_page_bits();
122 +}
123 +
124 +bool hexagon_tlb_dump_entry(Monitor *mon, uint64_t entry)
125 +{
126 + if (GET_PTE_V(entry)) {
127 + uint64_t PA = hex_tlb_phys_addr(entry);
128 + uint64_t VA = hex_tlb_virt_addr(entry);
129 + monitor_printf(mon, "0x%016" PRIx64 ": ", entry);
130 + monitor_printf(mon, "V:%" PRId64 " G:%" PRId64
131 + " A1:%" PRId64 " A0:%" PRId64,
132 + GET_PTE_V(entry),
133 + GET_PTE_G(entry),
134 + GET_PTE_ATR1(entry),
135 + GET_PTE_ATR0(entry));
136 + monitor_printf(mon, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64,
137 + GET_PTE_ASID(entry), VA);
138 + monitor_printf(mon,
139 + " X:%" PRId64 " W:%" PRId64 " R:%" PRId64
140 + " U:%" PRId64 " C:%" PRId64,
141 + GET_PTE_X(entry),
142 + GET_PTE_W(entry),
143 + GET_PTE_R(entry),
144 + GET_PTE_U(entry),
145 + GET_PTE_C(entry));
146 + monitor_printf(mon, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")",
147 + PA, pgsize_str[hex_tlb_pgsize_type(entry)],
148 + hex_tlb_page_size_bytes(entry));
149 + monitor_printf(mon, "\n");
150 + return true;
151 + }
152 +
153 + /* Not valid */
154 + return false;
155 +}
156 +
157 +static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid,
158 + uint64_t VA)
159 +{
160 + if (GET_PTE_V(entry)) {
161 + if (GET_PTE_G(entry)) {
162 + /* Global entry - ignore ASID */
163 + } else if (asid != NO_ASID) {
164 + uint32_t tlb_asid = GET_PTE_ASID(entry);
165 + if (tlb_asid != asid) {
166 + return false;
167 + }
168 + }
169 +
170 + uint64_t page_size = hex_tlb_page_size_bytes(entry);
171 + uint64_t page_start =
172 + ROUND_DOWN(hex_tlb_virt_addr(entry), page_size);
173 + if (page_start <= VA && VA < page_start + page_size) {
174 + return true;
175 + }
176 + }
177 + return false;
178 +}
179 +
180 +static inline void hex_tlb_entry_get_perm(uint64_t entry,
181 + MMUAccessType access_type,
182 + int mmu_idx, int *prot,
183 + int32_t *excp, int *cause_code)
184 +{
185 + bool perm_x = GET_PTE_X(entry);
186 + bool perm_w = GET_PTE_W(entry);
187 + bool perm_r = GET_PTE_R(entry);
188 + bool perm_u = GET_PTE_U(entry);
189 + bool user_idx = mmu_idx == MMU_USER_IDX;
190 +
191 + if (mmu_idx == MMU_KERNEL_IDX) {
192 + *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC;
193 + return;
194 + }
195 +
196 + *prot = PAGE_VALID;
197 + switch (access_type) {
198 + case MMU_INST_FETCH:
199 + if (user_idx && !perm_u) {
200 + *excp = HEX_EVENT_PRECISE;
201 + *cause_code = HEX_CAUSE_FETCH_NO_UPAGE;
202 + } else if (!perm_x) {
203 + *excp = HEX_EVENT_PRECISE;
204 + *cause_code = HEX_CAUSE_FETCH_NO_XPAGE;
205 + }
206 + break;
207 + case MMU_DATA_LOAD:
208 + if (user_idx && !perm_u) {
209 + *excp = HEX_EVENT_PRECISE;
210 + *cause_code = HEX_CAUSE_PRIV_NO_UREAD;
211 + } else if (!perm_r) {
212 + *excp = HEX_EVENT_PRECISE;
213 + *cause_code = HEX_CAUSE_PRIV_NO_READ;
214 + }
215 + break;
216 + case MMU_DATA_STORE:
217 + if (user_idx && !perm_u) {
218 + *excp = HEX_EVENT_PRECISE;
219 + *cause_code = HEX_CAUSE_PRIV_NO_UWRITE;
220 + } else if (!perm_w) {
221 + *excp = HEX_EVENT_PRECISE;
222 + *cause_code = HEX_CAUSE_PRIV_NO_WRITE;
223 + }
224 + break;
225 + }
226 +
227 + if (!user_idx || perm_u) {
228 + if (perm_x) {
229 + *prot |= PAGE_EXEC;
230 + }
231 + if (perm_r) {
232 + *prot |= PAGE_READ;
233 + }
234 + if (perm_w) {
235 + *prot |= PAGE_WRITE;
236 + }
237 + }
238 +}
239 +
240 +static inline bool hex_tlb_entry_match(uint64_t entry, uint8_t asid,
241 + uint32_t VA,
242 + MMUAccessType access_type, hwaddr *PA,
243 + int *prot, uint64_t *size,
244 + int32_t *excp, int *cause_code,
245 + int mmu_idx)
246 +{
247 + if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
248 + hex_tlb_entry_get_perm(entry, access_type, mmu_idx, prot, excp,
249 + cause_code);
250 + *PA = hex_tlb_phys_addr(entry);
251 + *size = hex_tlb_page_size_bytes(entry);
252 + return true;
253 + }
254 + return false;
255 +}
256 +
257 +static bool hex_tlb_is_match(uint64_t entry1, uint64_t entry2,
258 + bool consider_gbit)
259 +{
260 + bool valid1 = GET_PTE_V(entry1);
261 + bool valid2 = GET_PTE_V(entry2);
262 + uint64_t size1 = hex_tlb_page_size_bytes(entry1);
263 + uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1);
264 + uint64_t size2 = hex_tlb_page_size_bytes(entry2);
265 + uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2);
266 + int asid1 = GET_PTE_ASID(entry1);
267 + int asid2 = GET_PTE_ASID(entry2);
268 + bool gbit1 = GET_PTE_G(entry1);
269 + bool gbit2 = GET_PTE_G(entry2);
270 +
271 + if (!valid1 || !valid2) {
272 + return false;
273 + }
274 +
275 + if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) ||
276 + ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) {
277 + if (asid1 == asid2) {
278 + return true;
279 + }
280 + if ((consider_gbit && gbit1) || gbit2) {
281 + return true;
282 + }
283 + }
284 + return false;
285 +}
286 +
287 +/* Public API */
288 +
289 +uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index)
290 +{
291 + g_assert(index < tlb->num_entries);
292 + return tlb->entries[index];
293 +}
294 +
295 +void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value)
296 +{
297 + g_assert(index < tlb->num_entries);
298 + tlb->entries[index] = value;
299 +}
300 +
301 +bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
302 + uint32_t VA, MMUAccessType access_type,
303 + hwaddr *PA, int *prot, uint64_t *size,
304 + int32_t *excp, int *cause_code, int mmu_idx)
305 +{
306 + *PA = 0;
307 + *prot = 0;
308 + *size = 0;
309 + *excp = 0;
310 + *cause_code = 0;
311 +
312 + for (uint32_t i = 0; i < tlb->num_entries; i++) {
313 + if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type,
314 + PA, prot, size, excp, cause_code, mmu_idx)) {
315 + return true;
316 + }
317 + }
318 + return false;
319 +}
320 +
321 +uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
322 + uint32_t VA, int *cause_code)
323 +{
324 + uint32_t not_found = 0x80000000;
325 + uint32_t idx = not_found;
326 +
327 + for (uint32_t i = 0; i < tlb->num_entries; i++) {
328 + uint64_t entry = tlb->entries[i];
329 + if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
330 + if (idx != not_found) {
331 + *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
332 + break;
333 + }
334 + idx = i;
335 + }
336 + }
337 +
338 + if (idx == not_found) {
339 + qemu_log_mask(CPU_LOG_MMU,
340 + "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => NOT FOUND\n",
341 + __func__, asid, VA);
342 + } else {
343 + qemu_log_mask(CPU_LOG_MMU,
344 + "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => %d\n",
345 + __func__, asid, VA, idx);
346 + }
347 +
348 + return idx;
349 +}
350 +
351 +/*
352 + * Return codes:
353 + * 0 or positive index of match
354 + * -1 multiple matches
355 + * -2 no match
356 + */
357 +int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
358 + uint64_t index)
359 +{
360 + int matches = 0;
361 + int last_match = 0;
362 +
363 + for (uint32_t i = 0; i < tlb->num_entries; i++) {
364 + if (hex_tlb_is_match(entry, tlb->entries[i], false)) {
365 + matches++;
366 + last_match = i;
367 + }
368 + }
369 +
370 + if (matches == 1) {
371 + return last_match;
372 + }
373 + if (matches == 0) {
374 + return -2;
375 + }
376 + return -1;
377 +}
378 +
379 +void hexagon_tlb_dump(Monitor *mon, HexagonTLBState *tlb)
380 +{
381 + for (uint32_t i = 0; i < tlb->num_entries; i++) {
382 + hexagon_tlb_dump_entry(mon, tlb->entries[i]);
383 + }
384 +}
385 +
386 +uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb)
387 +{
388 + return tlb->num_entries;
389 +}
390 +
391 +/* QOM lifecycle */
392 +
393 +static void hexagon_tlb_init(Object *obj)
394 +{
395 +}
396 +
397 +static void hexagon_tlb_realize(DeviceState *dev, Error **errp)
398 +{
399 + HexagonTLBState *s = HEXAGON_TLB(dev);
400 +
401 + if (s->num_entries == 0 || s->num_entries > MAX_TLB_ENTRIES) {
402 + error_setg(errp, "Invalid TLB num-entries: %" PRIu32,
403 + s->num_entries);
404 + return;
405 + }
406 + s->entries = g_new0(uint64_t, s->num_entries);
407 +}
408 +
409 +static void hexagon_tlb_unrealize(DeviceState *dev)
410 +{
411 + HexagonTLBState *s = HEXAGON_TLB(dev);
412 + g_free(s->entries);
413 + s->entries = NULL;
414 +}
415 +
416 +static void hexagon_tlb_reset_hold(Object *obj, ResetType type)
417 +{
418 + HexagonTLBState *s = HEXAGON_TLB(obj);
419 + if (s->entries) {
420 + memset(s->entries, 0, sizeof(uint64_t) * s->num_entries);
421 + }
422 +}
423 +
424 +static const VMStateDescription vmstate_hexagon_tlb = {
425 + .name = "hexagon-tlb",
426 + .version_id = 0,
427 + .minimum_version_id = 0,
428 + .fields = (const VMStateField[]) {
429 + VMSTATE_UINT32(num_entries, HexagonTLBState),
430 + VMSTATE_VARRAY_UINT32_ALLOC(entries, HexagonTLBState, num_entries,
431 + 0, vmstate_info_uint64, uint64_t),
432 + VMSTATE_END_OF_LIST()
433 + },
434 +};
435 +
436 +static const Property hexagon_tlb_properties[] = {
437 + DEFINE_PROP_UINT32("num-entries", HexagonTLBState, num_entries,
438 + MAX_TLB_ENTRIES),
439 +};
440 +
441 +static void hexagon_tlb_class_init(ObjectClass *klass, const void *data)
442 +{
443 + DeviceClass *dc = DEVICE_CLASS(klass);
444 + ResettableClass *rc = RESETTABLE_CLASS(klass);
445 +
446 + dc->realize = hexagon_tlb_realize;
447 + dc->unrealize = hexagon_tlb_unrealize;
448 + rc->phases.hold = hexagon_tlb_reset_hold;
449 + dc->vmsd = &vmstate_hexagon_tlb;
450 + dc->user_creatable = false;
451 + device_class_set_props(dc, hexagon_tlb_properties);
452 +}
453 +
454 +static const TypeInfo hexagon_tlb_info = {
455 + .name = TYPE_HEXAGON_TLB,
456 + .parent = TYPE_SYS_BUS_DEVICE,
457 + .instance_size = sizeof(HexagonTLBState),
458 + .instance_init = hexagon_tlb_init,
459 + .class_init = hexagon_tlb_class_init,
460 +};
461 +
462 +static void hexagon_tlb_register_types(void)
463 +{
464 + type_register_static(&hexagon_tlb_info);
465 +}
466 +
467 +type_init(hexagon_tlb_register_types)