target/hexagon/cpu-param.h | 2 +- target/hexagon/cpu.h | 21 +++ target/hexagon/hex_mmu.h | 26 ++++ target/hexagon/internal.h | 9 ++ target/hexagon/sys_macros.h | 3 + target/hexagon/cpu.c | 36 +++++ target/hexagon/hex_mmu.c | 268 ++++++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 target/hexagon/hex_mmu.h create mode 100644 target/hexagon/hex_mmu.c
Add hex_mmu.[ch], cpu mode helpers, and additional includes/stubs
that integrate the TLB device with the CPU model.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/cpu-param.h | 2 +-
target/hexagon/cpu.h | 21 +++
target/hexagon/hex_mmu.h | 26 ++++
target/hexagon/internal.h | 9 ++
target/hexagon/sys_macros.h | 3 +
target/hexagon/cpu.c | 36 +++++
target/hexagon/hex_mmu.c | 268 ++++++++++++++++++++++++++++++++++++
7 files changed, 364 insertions(+), 1 deletion(-)
create mode 100644 target/hexagon/hex_mmu.h
create mode 100644 target/hexagon/hex_mmu.c
diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h
index 1f0f22a7968..bfe9a868d63 100644
--- a/target/hexagon/cpu-param.h
+++ b/target/hexagon/cpu-param.h
@@ -18,7 +18,7 @@
#ifndef HEXAGON_CPU_PARAM_H
#define HEXAGON_CPU_PARAM_H
-#define TARGET_PAGE_BITS 16 /* 64K pages */
+#define TARGET_PAGE_BITS 12 /* 4K pages */
#define TARGET_VIRT_ADDR_SPACE_BITS 32
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 7ba1d3047df..dbdc456d732 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -27,6 +27,9 @@
#define SREG_WRITES_MAX 2
#endif
+typedef struct HexagonTLBState HexagonTLBState;
+typedef struct HexagonGlobalRegState HexagonGlobalRegState;
+
#include "cpu-qom.h"
#include "exec/cpu-common.h"
#include "exec/target_long.h"
@@ -39,6 +42,7 @@
#error "Hexagon does not support system emulation"
#endif
+
#define NUM_PREGS 4
#define TOTAL_PER_THREAD_REGS 64
@@ -47,10 +51,13 @@
#define REG_WRITES_MAX 32
#define PRED_WRITES_MAX 5 /* 4 insns + endloop */
#define VSTORES_MAX 2
+#define MAX_TLB_ENTRIES 1024
#define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU
#ifndef CONFIG_USER_ONLY
#define CPU_INTERRUPT_SWI CPU_INTERRUPT_TGT_INT_0
+#define CPU_INTERRUPT_K0_UNLOCK CPU_INTERRUPT_TGT_INT_1
+#define CPU_INTERRUPT_TLB_UNLOCK CPU_INTERRUPT_TGT_INT_2
#define HEX_CPU_MODE_USER 1
#define HEX_CPU_MODE_GUEST 2
@@ -67,6 +74,12 @@
#define MMU_GUEST_IDX 1
#define MMU_KERNEL_IDX 2
+typedef enum {
+ HEX_LOCK_UNLOCKED = 0,
+ HEX_LOCK_WAITING = 1,
+ HEX_LOCK_OWNER = 2,
+ HEX_LOCK_QUEUED = 3
+} hex_lock_state_t;
#endif
@@ -128,6 +141,10 @@ typedef struct CPUArchState {
/* This alias of CPUState.cpu_index is used by imported sources: */
uint32_t threadId;
+ hex_lock_state_t tlb_lock_state;
+ hex_lock_state_t k0_lock_state;
+ uint32_t tlb_lock_count;
+ uint32_t k0_lock_count;
uint64_t t_cycle_count;
#endif
uint32_t next_PC;
@@ -178,6 +195,10 @@ struct ArchCPU {
bool lldb_compat;
target_ulong lldb_stack_adjust;
bool short_circuit;
+#ifndef CONFIG_USER_ONLY
+ HexagonTLBState *tlb;
+ uint32_t htid;
+#endif
};
#include "cpu_bits.h"
diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h
new file mode 100644
index 00000000000..4f556c715a9
--- /dev/null
+++ b/target/hexagon/hex_mmu.h
@@ -0,0 +1,26 @@
+/*
+ * 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"
+#include "monitor/monitor.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, uint32_t 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(Monitor *mon, CPUHexagonState *env);
+#endif
diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h
index 33d73ed18d1..4338914efb5 100644
--- a/target/hexagon/internal.h
+++ b/target/hexagon/internal.h
@@ -36,6 +36,15 @@ void G_NORETURN do_raise_exception(CPUHexagonState *env,
uint32_t PC,
uintptr_t retaddr);
+#define hexagon_cpu_mmu_enabled(env) ({ \
+ HexagonCPU *cpu = env_archcpu(env); \
+ cpu->globalregs ? \
+ GET_SYSCFG_FIELD(SYSCFG_MMUEN, \
+ hexagon_globalreg_read(cpu->globalregs, \
+ HEX_SREG_SYSCFG, (env)->threadId)) : \
+ 0; \
+})
+
#ifndef CONFIG_USER_ONLY
extern const VMStateDescription vmstate_hexagon_cpu;
#endif
diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h
index 09d78b002e6..8b81e1ca0af 100644
--- a/target/hexagon/sys_macros.h
+++ b/target/hexagon/sys_macros.h
@@ -139,6 +139,9 @@
#define fDCINVIDX(REG)
#define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */
+#define fSET_TLB_LOCK() hex_tlb_lock(env);
+#define fCLEAR_TLB_LOCK() hex_tlb_unlock(env);
+
#define fTLB_IDXMASK(INDEX) \
((INDEX) & (fPOW2_ROUNDUP( \
fCAST4u(hexagon_tlb_get_num_entries(env_archcpu(env)->tlb))) - 1))
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 626100d43fd..73aca0a4217 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -23,9 +23,17 @@
#include "qapi/error.h"
#include "hw/core/qdev-properties.h"
#include "fpu/softfloat-helpers.h"
+#include "hw/hexagon/hexagon_tlb.h"
#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 ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model)
{
@@ -43,6 +51,11 @@ static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model)
}
static const Property hexagon_cpu_properties[] = {
+#ifndef 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,
qdev_prop_uint32, target_ulong),
@@ -269,7 +282,11 @@ static TCGTBCPUState hexagon_get_tb_cpu_state(CPUState *cs)
}
#ifndef CONFIG_USER_ONLY
+ hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX,
+ cpu_mmu_index(env_cpu(env), false));
hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, 1);
+#else
+ hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX);
#endif
return (TCGTBCPUState){ .pc = pc, .flags = hex_flags };
@@ -289,11 +306,15 @@ 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);
HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(obj);
CPUHexagonState *env = cpu_env(cs);
+#ifndef CONFIG_USER_ONLY
+ HexagonCPU *cpu = HEXAGON_CPU(cs);
+#endif
if (mcc->parent_phases.hold) {
mcc->parent_phases.hold(obj, type);
@@ -307,7 +328,14 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type)
memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS);
memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS);
env->wait_next_pc = 0;
+ env->tlb_lock_state = HEX_LOCK_UNLOCKED;
+ env->k0_lock_state = HEX_LOCK_UNLOCKED;
+ env->tlb_lock_count = 0;
+ env->k0_lock_count = 0;
env->next_PC = 0;
+
+ env->t_sreg[HEX_SREG_HTID] = cpu->htid;
+ env->threadId = cpu->htid;
#endif
env->cause_code = HEX_EVENT_NONE;
}
@@ -337,7 +365,15 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp)
hexagon_hvx_gdb_write_register,
gdb_find_static_feature("hexagon-hvx.xml"));
+#ifndef CONFIG_USER_ONLY
+ if (!HEXAGON_CPU(dev)->tlb) {
+ error_setg(errp, "hexagon cpu requires 'tlb' link property to be set");
+ return;
+ }
+#endif
+
qemu_init_vcpu(cs);
+
cpu_reset(cs);
mcc->parent_realize(dev, errp);
}
diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
new file mode 100644
index 00000000000..c921e82b377
--- /dev/null
+++ b/target/hexagon/hex_mmu.c
@@ -0,0 +1,268 @@
+/*
+ * 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 "hw/hexagon/hexagon_tlb.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+
+static inline void hex_log_tlbw(uint32_t index, uint64_t entry)
+{
+ qemu_log_mask(CPU_LOG_MMU,
+ "tlbw[%03" PRIu32 "]: 0x%016" PRIx64 "\n",
+ index, entry);
+}
+
+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, 63, 1);
+ 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, uint32_t 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(Monitor *mon, CPUHexagonState *env)
+{
+ HexagonCPU *cpu = env_archcpu(env);
+ hexagon_tlb_dump(mon, 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 = %" PRIu32 ": %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));
+
+ HexagonCPU *cpu = env_archcpu(env);
+ uint32_t syscfg = cpu->globalregs ?
+ hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+ env->threadId) : 0;
+ 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%" PRIx32 ", 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 */
+ HexagonCPU *cpu = env_archcpu(env);
+ uint32_t syscfg = cpu->globalregs ?
+ hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+ env->threadId) : 0;
+ 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 %" PRIu32 " 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");
+ }
+
+}
--
2.34.1
On 6/10/2026 10:28 PM, Brian Cain wrote:
> Add hex_mmu.[ch], cpu mode helpers, and additional includes/stubs
> that integrate the TLB device with the CPU model.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> target/hexagon/cpu-param.h | 2 +-
> target/hexagon/cpu.h | 21 +++
> target/hexagon/hex_mmu.h | 26 ++++
> target/hexagon/internal.h | 9 ++
> target/hexagon/sys_macros.h | 3 +
> target/hexagon/cpu.c | 36 +++++
> target/hexagon/hex_mmu.c | 268 ++++++++++++++++++++++++++++++++++++
> 7 files changed, 364 insertions(+), 1 deletion(-)
> create mode 100644 target/hexagon/hex_mmu.h
> create mode 100644 target/hexagon/hex_mmu.c
>
> diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h
> index 1f0f22a7968..bfe9a868d63 100644
> --- a/target/hexagon/cpu-param.h
> +++ b/target/hexagon/cpu-param.h
> @@ -18,7 +18,7 @@
> #ifndef HEXAGON_CPU_PARAM_H
> #define HEXAGON_CPU_PARAM_H
>
> -#define TARGET_PAGE_BITS 16 /* 64K pages */
> +#define TARGET_PAGE_BITS 12 /* 4K pages */
>
For information and people reviewing this series, this change has been
tested internally with our test suite, for user and system mode.
> #define TARGET_VIRT_ADDR_SPACE_BITS 32
>
> diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
> index 7ba1d3047df..dbdc456d732 100644
> --- a/target/hexagon/cpu.h
> +++ b/target/hexagon/cpu.h
> @@ -27,6 +27,9 @@
> #define SREG_WRITES_MAX 2
> #endif
>
> +typedef struct HexagonTLBState HexagonTLBState;
> +typedef struct HexagonGlobalRegState HexagonGlobalRegState;
> +
> #include "cpu-qom.h"
> #include "exec/cpu-common.h"
> #include "exec/target_long.h"
> @@ -39,6 +42,7 @@
> #error "Hexagon does not support system emulation"
> #endif
>
> +
> #define NUM_PREGS 4
> #define TOTAL_PER_THREAD_REGS 64
>
> @@ -47,10 +51,13 @@
> #define REG_WRITES_MAX 32
> #define PRED_WRITES_MAX 5 /* 4 insns + endloop */
> #define VSTORES_MAX 2
> +#define MAX_TLB_ENTRIES 1024
>
> #define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU
> #ifndef CONFIG_USER_ONLY
> #define CPU_INTERRUPT_SWI CPU_INTERRUPT_TGT_INT_0
> +#define CPU_INTERRUPT_K0_UNLOCK CPU_INTERRUPT_TGT_INT_1
> +#define CPU_INTERRUPT_TLB_UNLOCK CPU_INTERRUPT_TGT_INT_2
>
> #define HEX_CPU_MODE_USER 1
> #define HEX_CPU_MODE_GUEST 2
> @@ -67,6 +74,12 @@
> #define MMU_GUEST_IDX 1
> #define MMU_KERNEL_IDX 2
>
> +typedef enum {
> + HEX_LOCK_UNLOCKED = 0,
> + HEX_LOCK_WAITING = 1,
> + HEX_LOCK_OWNER = 2,
> + HEX_LOCK_QUEUED = 3
> +} hex_lock_state_t;
> #endif
>
>
> @@ -128,6 +141,10 @@ typedef struct CPUArchState {
>
> /* This alias of CPUState.cpu_index is used by imported sources: */
> uint32_t threadId;
> + hex_lock_state_t tlb_lock_state;
> + hex_lock_state_t k0_lock_state;
> + uint32_t tlb_lock_count;
> + uint32_t k0_lock_count;
> uint64_t t_cycle_count;
> #endif
> uint32_t next_PC;
> @@ -178,6 +195,10 @@ struct ArchCPU {
> bool lldb_compat;
> target_ulong lldb_stack_adjust;
> bool short_circuit;
> +#ifndef CONFIG_USER_ONLY
> + HexagonTLBState *tlb;
> + uint32_t htid;
> +#endif
> };
>
> #include "cpu_bits.h"
> diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h
> new file mode 100644
> index 00000000000..4f556c715a9
> --- /dev/null
> +++ b/target/hexagon/hex_mmu.h
> @@ -0,0 +1,26 @@
> +/*
> + * 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"
> +#include "monitor/monitor.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, uint32_t 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(Monitor *mon, CPUHexagonState *env);
> +#endif
> diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h
> index 33d73ed18d1..4338914efb5 100644
> --- a/target/hexagon/internal.h
> +++ b/target/hexagon/internal.h
> @@ -36,6 +36,15 @@ void G_NORETURN do_raise_exception(CPUHexagonState *env,
> uint32_t PC,
> uintptr_t retaddr);
>
> +#define hexagon_cpu_mmu_enabled(env) ({ \
> + HexagonCPU *cpu = env_archcpu(env); \
> + cpu->globalregs ? \
> + GET_SYSCFG_FIELD(SYSCFG_MMUEN, \
> + hexagon_globalreg_read(cpu->globalregs, \
> + HEX_SREG_SYSCFG, (env)->threadId)) : \
> + 0; \
> +})
> +
> #ifndef CONFIG_USER_ONLY
> extern const VMStateDescription vmstate_hexagon_cpu;
> #endif
> diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h
> index 09d78b002e6..8b81e1ca0af 100644
> --- a/target/hexagon/sys_macros.h
> +++ b/target/hexagon/sys_macros.h
> @@ -139,6 +139,9 @@
> #define fDCINVIDX(REG)
> #define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */
>
> +#define fSET_TLB_LOCK() hex_tlb_lock(env);
> +#define fCLEAR_TLB_LOCK() hex_tlb_unlock(env);
> +
> #define fTLB_IDXMASK(INDEX) \
> ((INDEX) & (fPOW2_ROUNDUP( \
> fCAST4u(hexagon_tlb_get_num_entries(env_archcpu(env)->tlb))) - 1))
> diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
> index 626100d43fd..73aca0a4217 100644
> --- a/target/hexagon/cpu.c
> +++ b/target/hexagon/cpu.c
> @@ -23,9 +23,17 @@
> #include "qapi/error.h"
> #include "hw/core/qdev-properties.h"
> #include "fpu/softfloat-helpers.h"
> +#include "hw/hexagon/hexagon_tlb.h"
> #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 ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model)
> {
> @@ -43,6 +51,11 @@ static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model)
> }
>
> static const Property hexagon_cpu_properties[] = {
> +#ifndef 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,
> qdev_prop_uint32, target_ulong),
> @@ -269,7 +282,11 @@ static TCGTBCPUState hexagon_get_tb_cpu_state(CPUState *cs)
> }
>
> #ifndef CONFIG_USER_ONLY
> + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX,
> + cpu_mmu_index(env_cpu(env), false));
> hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, 1);
> +#else
> + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX);
> #endif
>
> return (TCGTBCPUState){ .pc = pc, .flags = hex_flags };
> @@ -289,11 +306,15 @@ 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);
> HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(obj);
> CPUHexagonState *env = cpu_env(cs);
> +#ifndef CONFIG_USER_ONLY
> + HexagonCPU *cpu = HEXAGON_CPU(cs);
> +#endif
>
> if (mcc->parent_phases.hold) {
> mcc->parent_phases.hold(obj, type);
> @@ -307,7 +328,14 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type)
> memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS);
> memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS);
> env->wait_next_pc = 0;
> + env->tlb_lock_state = HEX_LOCK_UNLOCKED;
> + env->k0_lock_state = HEX_LOCK_UNLOCKED;
> + env->tlb_lock_count = 0;
> + env->k0_lock_count = 0;
> env->next_PC = 0;
> +
> + env->t_sreg[HEX_SREG_HTID] = cpu->htid;
> + env->threadId = cpu->htid;
> #endif
> env->cause_code = HEX_EVENT_NONE;
> }
> @@ -337,7 +365,15 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp)
> hexagon_hvx_gdb_write_register,
> gdb_find_static_feature("hexagon-hvx.xml"));
>
> +#ifndef CONFIG_USER_ONLY
> + if (!HEXAGON_CPU(dev)->tlb) {
> + error_setg(errp, "hexagon cpu requires 'tlb' link property to be set");
> + return;
> + }
> +#endif
> +
> qemu_init_vcpu(cs);
> +
> cpu_reset(cs);
> mcc->parent_realize(dev, errp);
> }
> diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
> new file mode 100644
> index 00000000000..c921e82b377
> --- /dev/null
> +++ b/target/hexagon/hex_mmu.c
> @@ -0,0 +1,268 @@
> +/*
> + * 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 "hw/hexagon/hexagon_tlb.h"
> +#include "hw/hexagon/hexagon_globalreg.h"
> +
> +static inline void hex_log_tlbw(uint32_t index, uint64_t entry)
> +{
> + qemu_log_mask(CPU_LOG_MMU,
> + "tlbw[%03" PRIu32 "]: 0x%016" PRIx64 "\n",
> + index, entry);
> +}
> +
> +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, 63, 1);
> + 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, uint32_t 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(Monitor *mon, CPUHexagonState *env)
> +{
> + HexagonCPU *cpu = env_archcpu(env);
> + hexagon_tlb_dump(mon, 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 = %" PRIu32 ": %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));
> +
> + HexagonCPU *cpu = env_archcpu(env);
> + uint32_t syscfg = cpu->globalregs ?
> + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
> + env->threadId) : 0;
> + 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%" PRIx32 ", 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 */
> + HexagonCPU *cpu = env_archcpu(env);
> + uint32_t syscfg = cpu->globalregs ?
> + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
> + env->threadId) : 0;
> + 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 %" PRIu32 " 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");
> + }
> +
> +}
For the rest, it looks good to me.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Regards,
Pierrick
© 2016 - 2026 Red Hat, Inc.