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)