From: Brian Cain <[email protected]>
Reviewed-by: Taylor Simpson <[email protected]>
Signed-off-by: Brian Cain <[email protected]>
---
include/hw/hexagon/hexagon_tlb.h | 47 ++++
target/hexagon/cpu-param.h | 4 +
target/hexagon/cpu.h | 7 +
target/hexagon/cpu_helper.h | 2 +
target/hexagon/hex_mmu.h | 25 ++
target/hexagon/internal.h | 8 +
hw/hexagon/hexagon_tlb.c | 457 +++++++++++++++++++++++++++++++
target/hexagon/cpu.c | 24 +-
target/hexagon/cpu_helper.c | 8 +
target/hexagon/hex_mmu.c | 273 ++++++++++++++++++
target/hexagon/machine.c | 1 +
target/hexagon/translate.c | 2 +-
12 files changed, 856 insertions(+), 2 deletions(-)
create mode 100644 include/hw/hexagon/hexagon_tlb.h
create mode 100644 target/hexagon/hex_mmu.h
create mode 100644 hw/hexagon/hexagon_tlb.c
create mode 100644 target/hexagon/hex_mmu.c
diff --git a/include/hw/hexagon/hexagon_tlb.h b/include/hw/hexagon/hexagon_tlb.h
new file mode 100644
index 00000000000..799234f5074
--- /dev/null
+++ b/include/hw/hexagon/hexagon_tlb.h
@@ -0,0 +1,47 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_TLB_H
+#define HW_HEXAGON_TLB_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "exec/hwaddr.h"
+#include "exec/mmu-access-type.h"
+#include "exec/target_long.h"
+
+#define TYPE_HEXAGON_TLB "hexagon-tlb"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonTLBState, HEXAGON_TLB)
+
+struct HexagonTLBState {
+ SysBusDevice parent_obj;
+
+ uint32_t num_entries;
+ uint64_t *entries;
+};
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index);
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value);
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+ target_ulong VA, MMUAccessType access_type,
+ hwaddr *PA, int *prot, uint64_t *size,
+ int32_t *excp, int *cause_code, int mmu_idx);
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+ uint32_t VA, int *cause_code);
+
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+ uint64_t index);
+
+void hexagon_tlb_dump(HexagonTLBState *tlb);
+
+bool hexagon_tlb_dump_entry(FILE *f, uint64_t entry);
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb);
+
+#endif /* HW_HEXAGON_TLB_H */
diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h
index ccaf6a9d28d..d414ca89d69 100644
--- a/target/hexagon/cpu-param.h
+++ b/target/hexagon/cpu-param.h
@@ -18,7 +18,11 @@
#ifndef HEXAGON_CPU_PARAM_H
#define HEXAGON_CPU_PARAM_H
+#ifdef CONFIG_USER_ONLY
#define TARGET_PAGE_BITS 16 /* 64K pages */
+#else
+#define TARGET_PAGE_BITS 12 /* 4K pages */
+#endif
#define TARGET_PHYS_ADDR_SPACE_BITS 36
#define TARGET_VIRT_ADDR_SPACE_BITS 32
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 41865d853eb..3f4f8516f2f 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -36,6 +36,9 @@
#error "Hexagon does not support system emulation"
#endif
+#ifndef CONFIG_USER_ONLY
+#endif
+
#define NUM_PREGS 4
#define TOTAL_PER_THREAD_REGS 64
@@ -185,12 +188,16 @@ struct ArchCPU {
bool lldb_compat;
target_ulong lldb_stack_adjust;
bool short_circuit;
+#ifndef CONFIG_USER_ONLY
struct HexagonTLBState *tlb;
+ uint32_t htid;
+#endif
};
#include "cpu_bits.h"
FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1)
+FIELD(TB_FLAGS, MMU_INDEX, 1, 3)
G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env,
uint32_t exception,
diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h
index 3d7238c3b06..95928fcd060 100644
--- a/target/hexagon/cpu_helper.h
+++ b/target/hexagon/cpu_helper.h
@@ -14,6 +14,8 @@ uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState
*env);
void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t);
void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t);
void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t);
+void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old);
+int get_exe_mode(CPUHexagonState *env);
static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg,
uint32_t val)
diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h
new file mode 100644
index 00000000000..32a99b64ff1
--- /dev/null
+++ b/target/hexagon/hex_mmu.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_MMU_H
+#define HEXAGON_MMU_H
+
+#include "cpu.h"
+
+extern void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value);
+extern uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t
VA);
+extern void hex_mmu_on(CPUHexagonState *env);
+extern void hex_mmu_off(CPUHexagonState *env);
+extern void hex_mmu_mode_change(CPUHexagonState *env);
+extern bool hex_tlb_find_match(CPUHexagonState *env, target_ulong VA,
+ MMUAccessType access_type, hwaddr *PA, int
*prot,
+ uint64_t *size, int32_t *excp, int mmu_idx);
+extern int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry,
+ uint64_t index);
+extern void hex_tlb_lock(CPUHexagonState *env);
+extern void hex_tlb_unlock(CPUHexagonState *env);
+void dump_mmu(CPUHexagonState *env);
+#endif
diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h
index d94f84b46a7..1ab50828f26 100644
--- a/target/hexagon/internal.h
+++ b/target/hexagon/internal.h
@@ -36,6 +36,14 @@ void G_NORETURN do_raise_exception(CPUHexagonState *env,
target_ulong PC,
uintptr_t retaddr);
+#define hexagon_cpu_mmu_enabled(env) ({ \
+ HexagonCPU *cpu = env_archcpu(env); \
+ cpu->globalregs ? \
+ GET_SYSCFG_FIELD(SYSCFG_MMUEN, \
+ arch_get_system_reg(env, HEX_SREG_SYSCFG)) : \
+ 0; \
+})
+
#ifndef CONFIG_USER_ONLY
extern const VMStateDescription vmstate_hexagon_cpu;
#endif
diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c
new file mode 100644
index 00000000000..6b676fa6593
--- /dev/null
+++ b/hw/hexagon/hexagon_tlb.c
@@ -0,0 +1,457 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/resettable.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "target/hexagon/cpu.h"
+#include "target/hexagon/reg_fields.h"
+#include "target/hexagon/cpu_bits.h"
+
+#define fEXTRACTU_BITS(INREG, WIDTH, OFFSET) \
+ ((WIDTH) ? extract64((INREG), (OFFSET), (WIDTH)) : 0LL)
+
+#define GET_TLB_FIELD(ENTRY, FIELD) \
+ ((uint64_t)fEXTRACTU_BITS(ENTRY, reg_field_info[FIELD].width, \
+ reg_field_info[FIELD].offset))
+
+/* PPD (physical page descriptor) */
+static inline uint64_t GET_PPD(uint64_t entry)
+{
+ return GET_TLB_FIELD(entry, PTE_PPD) |
+ (GET_TLB_FIELD(entry, PTE_PA35) << reg_field_info[PTE_PPD].width);
+}
+
+#define NO_ASID (1 << 8)
+
+typedef enum {
+ PGSIZE_4K,
+ PGSIZE_16K,
+ PGSIZE_64K,
+ PGSIZE_256K,
+ PGSIZE_1M,
+ PGSIZE_4M,
+ PGSIZE_16M,
+ PGSIZE_64M,
+ PGSIZE_256M,
+ PGSIZE_1G,
+ NUM_PGSIZE_TYPES
+} tlb_pgsize_t;
+
+static const char *pgsize_str[NUM_PGSIZE_TYPES] = {
+ "4K",
+ "16K",
+ "64K",
+ "256K",
+ "1M",
+ "4M",
+ "16M",
+ "64M",
+ "256M",
+ "1G",
+};
+
+#define INVALID_MASK 0xffffffffLL
+
+static const uint64_t encmask_2_mask[] = {
+ 0x0fffLL, /* 4k, 0000 */
+ 0x3fffLL, /* 16k, 0001 */
+ 0xffffLL, /* 64k, 0010 */
+ 0x3ffffLL, /* 256k, 0011 */
+ 0xfffffLL, /* 1m, 0100 */
+ 0x3fffffLL, /* 4m, 0101 */
+ 0xffffffLL, /* 16m, 0110 */
+ 0x3ffffffLL, /* 64m, 0111 */
+ 0xfffffffLL, /* 256m, 1000 */
+ 0x3fffffffLL, /* 1g, 1001 */
+ INVALID_MASK, /* RSVD, 0111 */
+};
+
+static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry)
+{
+ if (entry == 0) {
+ qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n",
+ __func__);
+ return 0;
+ }
+ tlb_pgsize_t size = ctz64(entry);
+ g_assert(size < NUM_PGSIZE_TYPES);
+ return size;
+}
+
+static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry)
+{
+ return 1ull << (TARGET_PAGE_BITS + 2 * hex_tlb_pgsize_type(entry));
+}
+
+static inline uint64_t hex_tlb_phys_page_num(uint64_t entry)
+{
+ uint32_t ppd = GET_PPD(entry);
+ return ppd >> 1;
+}
+
+static inline uint64_t hex_tlb_phys_addr(uint64_t entry)
+{
+ uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)];
+ uint64_t pagenum = hex_tlb_phys_page_num(entry);
+ uint64_t PA = (pagenum << TARGET_PAGE_BITS) & (~pagemask);
+ return PA;
+}
+
+static inline uint64_t hex_tlb_virt_addr(uint64_t entry)
+{
+ return (uint64_t)GET_TLB_FIELD(entry, PTE_VPN) << TARGET_PAGE_BITS;
+}
+
+bool hexagon_tlb_dump_entry(FILE *f, uint64_t entry)
+{
+ if (GET_TLB_FIELD(entry, PTE_V)) {
+ fprintf(f, "0x%016" PRIx64 ": ", entry);
+ uint64_t PA = hex_tlb_phys_addr(entry);
+ uint64_t VA = hex_tlb_virt_addr(entry);
+ fprintf(f, "V:%" PRId64 " G:%" PRId64
+ " A1:%" PRId64 " A0:%" PRId64,
+ GET_TLB_FIELD(entry, PTE_V),
+ GET_TLB_FIELD(entry, PTE_G),
+ GET_TLB_FIELD(entry, PTE_ATR1),
+ GET_TLB_FIELD(entry, PTE_ATR0));
+ fprintf(f, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64,
+ GET_TLB_FIELD(entry, PTE_ASID), VA);
+ fprintf(f,
+ " X:%" PRId64 " W:%" PRId64 " R:%" PRId64
+ " U:%" PRId64 " C:%" PRId64,
+ GET_TLB_FIELD(entry, PTE_X),
+ GET_TLB_FIELD(entry, PTE_W),
+ GET_TLB_FIELD(entry, PTE_R),
+ GET_TLB_FIELD(entry, PTE_U),
+ GET_TLB_FIELD(entry, PTE_C));
+ fprintf(f, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")", PA,
+ pgsize_str[hex_tlb_pgsize_type(entry)],
+ hex_tlb_page_size_bytes(entry));
+ fprintf(f, "\n");
+ return true;
+ }
+
+ /* Not valid */
+ return false;
+}
+
+static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid,
+ uint64_t VA)
+{
+ if (GET_TLB_FIELD(entry, PTE_V)) {
+ if (GET_TLB_FIELD(entry, PTE_G)) {
+ /* Global entry - ignore ASID */
+ } else if (asid != NO_ASID) {
+ uint32_t tlb_asid = GET_TLB_FIELD(entry, PTE_ASID);
+ if (tlb_asid != asid) {
+ return false;
+ }
+ }
+
+ uint64_t page_size = hex_tlb_page_size_bytes(entry);
+ uint64_t page_start =
+ ROUND_DOWN(hex_tlb_virt_addr(entry), page_size);
+ if (page_start <= VA && VA < page_start + page_size) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static inline void hex_tlb_entry_get_perm(uint64_t entry,
+ MMUAccessType access_type,
+ int mmu_idx, int *prot,
+ int32_t *excp, int *cause_code)
+{
+ bool perm_x = GET_TLB_FIELD(entry, PTE_X);
+ bool perm_w = GET_TLB_FIELD(entry, PTE_W);
+ bool perm_r = GET_TLB_FIELD(entry, PTE_R);
+ bool perm_u = GET_TLB_FIELD(entry, PTE_U);
+ bool user_idx = mmu_idx == MMU_USER_IDX;
+
+ if (mmu_idx == MMU_KERNEL_IDX) {
+ *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ return;
+ }
+
+ *prot = PAGE_VALID;
+ switch (access_type) {
+ case MMU_INST_FETCH:
+ if (user_idx && !perm_u) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_FETCH_NO_UPAGE;
+ } else if (!perm_x) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_FETCH_NO_XPAGE;
+ }
+ break;
+ case MMU_DATA_LOAD:
+ if (user_idx && !perm_u) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_PRIV_NO_UREAD;
+ } else if (!perm_r) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_PRIV_NO_READ;
+ }
+ break;
+ case MMU_DATA_STORE:
+ if (user_idx && !perm_u) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_PRIV_NO_UWRITE;
+ } else if (!perm_w) {
+ *excp = HEX_EVENT_PRECISE;
+ *cause_code = HEX_CAUSE_PRIV_NO_WRITE;
+ }
+ break;
+ }
+
+ if (!user_idx || perm_u) {
+ if (perm_x) {
+ *prot |= PAGE_EXEC;
+ }
+ if (perm_r) {
+ *prot |= PAGE_READ;
+ }
+ if (perm_w) {
+ *prot |= PAGE_WRITE;
+ }
+ }
+}
+
+static inline bool hex_tlb_entry_match(uint64_t entry, uint8_t asid,
+ target_ulong VA,
+ MMUAccessType access_type, hwaddr *PA,
+ int *prot, uint64_t *size,
+ int32_t *excp, int *cause_code,
+ int mmu_idx)
+{
+ if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+ hex_tlb_entry_get_perm(entry, access_type, mmu_idx, prot, excp,
+ cause_code);
+ *PA = hex_tlb_phys_addr(entry);
+ *size = hex_tlb_page_size_bytes(entry);
+ return true;
+ }
+ return false;
+}
+
+static bool hex_tlb_is_match(uint64_t entry1, uint64_t entry2,
+ bool consider_gbit)
+{
+ bool valid1 = GET_TLB_FIELD(entry1, PTE_V);
+ bool valid2 = GET_TLB_FIELD(entry2, PTE_V);
+ uint64_t size1 = hex_tlb_page_size_bytes(entry1);
+ uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1);
+ uint64_t size2 = hex_tlb_page_size_bytes(entry2);
+ uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2);
+ int asid1 = GET_TLB_FIELD(entry1, PTE_ASID);
+ int asid2 = GET_TLB_FIELD(entry2, PTE_ASID);
+ bool gbit1 = GET_TLB_FIELD(entry1, PTE_G);
+ bool gbit2 = GET_TLB_FIELD(entry2, PTE_G);
+
+ if (!valid1 || !valid2) {
+ return false;
+ }
+
+ if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) ||
+ ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) {
+ if (asid1 == asid2) {
+ return true;
+ }
+ if ((consider_gbit && gbit1) || gbit2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Public API */
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index)
+{
+ g_assert(index < tlb->num_entries);
+ return tlb->entries[index];
+}
+
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value)
+{
+ g_assert(index < tlb->num_entries);
+ tlb->entries[index] = value;
+}
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+ target_ulong VA, MMUAccessType access_type,
+ hwaddr *PA, int *prot, uint64_t *size,
+ int32_t *excp, int *cause_code, int mmu_idx)
+{
+ *PA = 0;
+ *prot = 0;
+ *size = 0;
+ *excp = 0;
+ *cause_code = 0;
+
+ for (uint32_t i = 0; i < tlb->num_entries; i++) {
+ if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type,
+ PA, prot, size, excp, cause_code, mmu_idx)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+ uint32_t VA, int *cause_code)
+{
+ uint32_t not_found = 0x80000000;
+ uint32_t idx = not_found;
+
+ for (uint32_t i = 0; i < tlb->num_entries; i++) {
+ uint64_t entry = tlb->entries[i];
+ if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+ if (idx != not_found) {
+ *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
+ break;
+ }
+ idx = i;
+ }
+ }
+
+ if (idx == not_found) {
+ qemu_log_mask(CPU_LOG_MMU,
+ "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => NOT FOUND\n",
+ __func__, asid, VA);
+ } else {
+ qemu_log_mask(CPU_LOG_MMU,
+ "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => %d\n",
+ __func__, asid, VA, idx);
+ }
+
+ return idx;
+}
+
+/*
+ * Return codes:
+ * 0 or positive index of match
+ * -1 multiple matches
+ * -2 no match
+ */
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+ uint64_t index)
+{
+ int matches = 0;
+ int last_match = 0;
+
+ for (uint32_t i = 0; i < tlb->num_entries; i++) {
+ if (hex_tlb_is_match(entry, tlb->entries[i], false)) {
+ matches++;
+ last_match = i;
+ }
+ }
+
+ if (matches == 1) {
+ return last_match;
+ }
+ if (matches == 0) {
+ return -2;
+ }
+ return -1;
+}
+
+void hexagon_tlb_dump(HexagonTLBState *tlb)
+{
+ for (uint32_t i = 0; i < tlb->num_entries; i++) {
+ hexagon_tlb_dump_entry(stdout, tlb->entries[i]);
+ }
+}
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb)
+{
+ return tlb->num_entries;
+}
+
+/* QOM lifecycle */
+
+static void hexagon_tlb_init(Object *obj)
+{
+}
+
+static void hexagon_tlb_realize(DeviceState *dev, Error **errp)
+{
+ HexagonTLBState *s = HEXAGON_TLB(dev);
+
+ if (s->num_entries == 0 || s->num_entries > MAX_TLB_ENTRIES) {
+ error_setg(errp, "Invalid TLB num-entries: %" PRIu32,
+ s->num_entries);
+ return;
+ }
+ s->entries = g_new0(uint64_t, s->num_entries);
+}
+
+static void hexagon_tlb_finalize(Object *obj)
+{
+ HexagonTLBState *s = HEXAGON_TLB(obj);
+ g_free(s->entries);
+ s->entries = NULL;
+}
+
+static void hexagon_tlb_reset_hold(Object *obj, ResetType type)
+{
+ HexagonTLBState *s = HEXAGON_TLB(obj);
+ if (s->entries) {
+ memset(s->entries, 0, sizeof(uint64_t) * s->num_entries);
+ }
+}
+
+static const VMStateDescription vmstate_hexagon_tlb = {
+ .name = "hexagon-tlb",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(num_entries, HexagonTLBState),
+ VMSTATE_VARRAY_UINT32_ALLOC(entries, HexagonTLBState, num_entries,
+ 0, vmstate_info_uint64, uint64_t),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const Property hexagon_tlb_properties[] = {
+ DEFINE_PROP_UINT32("num-entries", HexagonTLBState, num_entries,
+ MAX_TLB_ENTRIES),
+};
+
+static void hexagon_tlb_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = hexagon_tlb_realize;
+ rc->phases.hold = hexagon_tlb_reset_hold;
+ dc->vmsd = &vmstate_hexagon_tlb;
+ dc->user_creatable = false;
+ device_class_set_props(dc, hexagon_tlb_properties);
+}
+
+static const TypeInfo hexagon_tlb_info = {
+ .name = TYPE_HEXAGON_TLB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(HexagonTLBState),
+ .instance_init = hexagon_tlb_init,
+ .instance_finalize = hexagon_tlb_finalize,
+ .class_init = hexagon_tlb_class_init,
+};
+
+static void hexagon_tlb_register_types(void)
+{
+ type_register_static(&hexagon_tlb_info);
+}
+
+type_init(hexagon_tlb_register_types)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 7469d8a1966..69b5c56b55f 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -27,6 +27,13 @@
#include "tcg/tcg.h"
#include "exec/gdbstub.h"
#include "accel/tcg/cpu-ops.h"
+#include "cpu_helper.h"
+#include "hex_mmu.h"
+
+#ifndef CONFIG_USER_ONLY
+#include "sys_macros.h"
+#include "accel/tcg/cpu-ldst.h"
+#endif
static void hexagon_v66_cpu_init(Object *obj) { }
static void hexagon_v67_cpu_init(Object *obj) { }
@@ -54,6 +61,7 @@ static const Property hexagon_cpu_properties[] = {
#if !defined(CONFIG_USER_ONLY)
DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
HexagonTLBState *),
+ DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0),
#endif
DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false),
DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0,
@@ -280,6 +288,13 @@ static TCGTBCPUState hexagon_get_tb_cpu_state(CPUState *cs)
hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0);
}
+#ifndef CONFIG_USER_ONLY
+ hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX,
+ cpu_mmu_index(env_cpu(env), false));
+#else
+ hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX);
+#endif
+
return (TCGTBCPUState){ .pc = pc, .flags = hex_flags };
}
@@ -297,6 +312,7 @@ static void hexagon_restore_state_to_opc(CPUState *cs,
cpu_env(cs)->gpr[HEX_REG_PC] = data[0];
}
+
static void hexagon_cpu_reset_hold(Object *obj, ResetType type)
{
CPUState *cs = CPU(obj);
@@ -312,9 +328,14 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType
type)
/* Default NaN value: sign bit set, all frac bits set */
set_float_default_nan_pattern(0b11111111, &env->fp_status);
#ifndef CONFIG_USER_ONLY
+ HexagonCPU *cpu = HEXAGON_CPU(cs);
+
memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS);
memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS);
- env->threadId = cs->cpu_index;
+
+ env->t_sreg[HEX_SREG_HTID] = cpu->htid;
+ env->threadId = cpu->htid;
+ hexagon_cpu_soft_reset(env);
env->tlb_lock_state = HEX_LOCK_UNLOCKED;
env->k0_lock_state = HEX_LOCK_UNLOCKED;
env->tlb_lock_count = 0;
@@ -350,6 +371,7 @@ static void hexagon_cpu_realize(DeviceState *dev, Error
**errp)
gdb_find_static_feature("hexagon-hvx.xml"), 0);
qemu_init_vcpu(cs);
+
cpu_reset(cs);
mcc->parent_realize(dev, errp);
}
diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c
index 8e11cbb20dd..cab40b36cdc 100644
--- a/target/hexagon/cpu_helper.c
+++ b/target/hexagon/cpu_helper.c
@@ -66,5 +66,13 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env,
uint64_t cycles)
g_assert_not_reached();
}
+void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old)
+{
+ g_assert_not_reached();
+}
+int get_exe_mode(CPUHexagonState *env)
+{
+ g_assert_not_reached();
+}
#endif
diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
new file mode 100644
index 00000000000..d130a46c285
--- /dev/null
+++ b/target/hexagon/hex_mmu.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/qemu-print.h"
+#include "cpu.h"
+#include "system/cpus.h"
+#include "internal.h"
+#include "exec/cpu-interrupt.h"
+#include "cpu_helper.h"
+#include "exec/cputlb.h"
+#include "hex_mmu.h"
+#include "macros.h"
+#include "sys_macros.h"
+#include "reg_fields.h"
+#include "hw/hexagon/hexagon_tlb.h"
+
+static inline void hex_log_tlbw(uint32_t index, uint64_t entry)
+{
+ if (qemu_loglevel_mask(CPU_LOG_MMU)) {
+ if (qemu_log_enabled()) {
+ FILE *logfile = qemu_log_trylock();
+ if (logfile) {
+ fprintf(logfile, "tlbw[%03d]: ", index);
+ if (!hexagon_tlb_dump_entry(logfile, entry)) {
+ fprintf(logfile, "invalid\n");
+ }
+ qemu_log_unlock(logfile);
+ }
+ }
+ }
+}
+
+void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value)
+{
+ uint32_t myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(index));
+ HexagonTLBState *tlb = env_archcpu(env)->tlb;
+ uint64_t old_entry = hexagon_tlb_read(tlb, myidx);
+
+ bool old_entry_valid = extract64(old_entry,
+ reg_field_info[PTE_V].offset,
+ reg_field_info[PTE_V].width);
+ if (old_entry_valid && hexagon_cpu_mmu_enabled(env)) {
+ CPUState *cs = env_cpu(env);
+ tlb_flush(cs);
+ }
+ hexagon_tlb_write(tlb, myidx, value);
+ hex_log_tlbw(myidx, value);
+}
+
+void hex_mmu_on(CPUHexagonState *env)
+{
+ CPUState *cs = env_cpu(env);
+ qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned on!\n");
+ tlb_flush(cs);
+}
+
+void hex_mmu_off(CPUHexagonState *env)
+{
+ CPUState *cs = env_cpu(env);
+ qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned off!\n");
+ tlb_flush(cs);
+}
+
+void hex_mmu_mode_change(CPUHexagonState *env)
+{
+ qemu_log_mask(CPU_LOG_MMU, "Hexagon mode change!\n");
+ CPUState *cs = env_cpu(env);
+ tlb_flush(cs);
+}
+
+bool hex_tlb_find_match(CPUHexagonState *env, target_ulong VA,
+ MMUAccessType access_type, hwaddr *PA, int *prot,
+ uint64_t *size, int32_t *excp, int mmu_idx)
+{
+ HexagonCPU *cpu = env_archcpu(env);
+ uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+ uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
+ int cause_code = 0;
+
+ bool found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type,
+ PA, prot, size, excp, &cause_code,
+ mmu_idx);
+ if (cause_code) {
+ env->cause_code = cause_code;
+ }
+ return found;
+}
+
+/* Called from tlbp instruction */
+uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA)
+{
+ HexagonCPU *cpu = env_archcpu(env);
+ uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
+ int cause_code = 0;
+
+ uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA, &cause_code);
+ if (cause_code) {
+ env->cause_code = cause_code;
+ }
+ return result;
+}
+
+/*
+ * Return codes:
+ * 0 or positive index of match
+ * -1 multiple matches
+ * -2 no match
+ */
+int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, uint64_t index)
+{
+ HexagonCPU *cpu = env_archcpu(env);
+ return hexagon_tlb_check_overlap(cpu->tlb, entry, index);
+}
+
+void dump_mmu(CPUHexagonState *env)
+{
+ HexagonCPU *cpu = env_archcpu(env);
+ hexagon_tlb_dump(cpu->tlb);
+}
+
+static inline void print_thread(const char *str, CPUState *cs)
+{
+ g_assert(bql_locked());
+ CPUHexagonState *thread = cpu_env(cs);
+ bool is_stopped = cpu_is_stopped(cs);
+ int exe_mode = get_exe_mode(thread);
+ hex_lock_state_t lock_state = thread->tlb_lock_state;
+ qemu_log_mask(CPU_LOG_MMU,
+ "%s: threadId = %d: %s, exe_mode = %s, tlb_lock_state = %s\n",
+ str,
+ thread->threadId,
+ is_stopped ? "stopped" : "running",
+ exe_mode == HEX_EXE_MODE_OFF ? "off" :
+ exe_mode == HEX_EXE_MODE_RUN ? "run" :
+ exe_mode == HEX_EXE_MODE_WAIT ? "wait" :
+ exe_mode == HEX_EXE_MODE_DEBUG ? "debug" :
+ "unknown",
+ lock_state == HEX_LOCK_UNLOCKED ? "unlocked" :
+ lock_state == HEX_LOCK_WAITING ? "waiting" :
+ lock_state == HEX_LOCK_OWNER ? "owner" :
+ "unknown");
+}
+
+static inline void print_thread_states(const char *str)
+{
+ CPUState *cs;
+ CPU_FOREACH(cs) {
+ print_thread(str, cs);
+ }
+}
+
+void hex_tlb_lock(CPUHexagonState *env)
+{
+ qemu_log_mask(CPU_LOG_MMU, "hex_tlb_lock: " TARGET_FMT_ld "\n",
+ env->threadId);
+ BQL_LOCK_GUARD();
+ g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
+
+ uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG);
+ uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
+ if (tlb_lock) {
+ if (env->tlb_lock_state == HEX_LOCK_QUEUED) {
+ env->next_PC += 4;
+ env->tlb_lock_count++;
+ env->tlb_lock_state = HEX_LOCK_OWNER;
+ SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
+ return;
+ }
+ if (env->tlb_lock_state == HEX_LOCK_OWNER) {
+ qemu_log_mask(CPU_LOG_MMU | LOG_GUEST_ERROR,
+ "Double tlblock at PC: 0x%x, thread may hang\n",
+ env->next_PC);
+ env->next_PC += 4;
+ CPUState *cs = env_cpu(env);
+ cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+ return;
+ }
+ env->tlb_lock_state = HEX_LOCK_WAITING;
+ CPUState *cs = env_cpu(env);
+ cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+ } else {
+ env->next_PC += 4;
+ env->tlb_lock_count++;
+ env->tlb_lock_state = HEX_LOCK_OWNER;
+ SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
+ }
+
+ if (qemu_loglevel_mask(CPU_LOG_MMU)) {
+ qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_lock:\n");
+ print_thread_states("\tThread");
+ }
+}
+
+void hex_tlb_unlock(CPUHexagonState *env)
+{
+ BQL_LOCK_GUARD();
+ g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
+
+ /* Nothing to do if the TLB isn't locked by this thread */
+ uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG);
+ uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
+ if ((tlb_lock == 0) ||
+ (env->tlb_lock_state != HEX_LOCK_OWNER)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "thread %d attempted to tlbunlock without having the "
+ "lock, tlb_lock state = %d\n",
+ env->threadId, env->tlb_lock_state);
+ g_assert(env->tlb_lock_state != HEX_LOCK_WAITING);
+ return;
+ }
+
+ env->tlb_lock_count--;
+ env->tlb_lock_state = HEX_LOCK_UNLOCKED;
+ SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 0);
+
+ /* Look for a thread to unlock */
+ unsigned int this_threadId = env->threadId;
+ CPUHexagonState *unlock_thread = NULL;
+ CPUState *cs;
+ CPU_FOREACH(cs) {
+ CPUHexagonState *thread = cpu_env(cs);
+
+ /*
+ * The hardware implements round-robin fairness, so we look for threads
+ * starting at env->threadId + 1 and incrementing modulo the number of
+ * threads.
+ *
+ * To implement this, we check if thread is a earlier in the modulo
+ * sequence than unlock_thread.
+ * if unlock thread is higher than this thread
+ * thread must be between this thread and unlock_thread
+ * else
+ * thread higher than this thread is ahead of unlock_thread
+ * thread must be lower then unlock thread
+ */
+ if (thread->tlb_lock_state == HEX_LOCK_WAITING) {
+ if (!unlock_thread) {
+ unlock_thread = thread;
+ } else if (unlock_thread->threadId > this_threadId) {
+ if (this_threadId < thread->threadId &&
+ thread->threadId < unlock_thread->threadId) {
+ unlock_thread = thread;
+ }
+ } else {
+ if (thread->threadId > this_threadId) {
+ unlock_thread = thread;
+ }
+ if (thread->threadId < unlock_thread->threadId) {
+ unlock_thread = thread;
+ }
+ }
+ }
+ }
+ if (unlock_thread) {
+ cs = env_cpu(unlock_thread);
+ print_thread("\tWaiting thread found", cs);
+ unlock_thread->tlb_lock_state = HEX_LOCK_QUEUED;
+ SET_SYSCFG_FIELD(unlock_thread, SYSCFG_TLBLOCK, 1);
+ cpu_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK);
+ }
+
+ if (qemu_loglevel_mask(CPU_LOG_MMU)) {
+ qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_unlock:\n");
+ print_thread_states("\tThread");
+ }
+
+}
diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c
index 78152184569..50e2e420182 100644
--- a/target/hexagon/machine.c
+++ b/target/hexagon/machine.c
@@ -26,6 +26,7 @@ const VMStateDescription vmstate_hexagon_cpu = {
VMSTATE_UINTTL(env.threadId, HexagonCPU),
VMSTATE_UINTTL(env.cause_code, HexagonCPU),
VMSTATE_UINTTL(env.wait_next_pc, HexagonCPU),
+
VMSTATE_END_OF_LIST()
},
};
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index f3dc62cec1d..1341720831b 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -987,7 +987,7 @@ static void hexagon_tr_init_disas_context(DisasContextBase
*dcbase,
HexagonCPU *hex_cpu = env_archcpu(cpu_env(cs));
uint32_t hex_flags = dcbase->tb->flags;
- ctx->mem_idx = MMU_USER_IDX;
+ ctx->mem_idx = FIELD_EX32(hex_flags, TB_FLAGS, MMU_INDEX);
ctx->num_packets = 0;
ctx->num_insns = 0;
ctx->num_hvx_insns = 0;
--
2.34.1