* [PATCH v2 01/11] target/hexagon: align exceptions for user/sysemu
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 17:33 ` [PATCH v2 02/11] tests/tcg/hexagon: check priv instructions raise SIGILL Brian Cain
` (9 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
System mode reports an exception as cs->exception_index = HEX_EVENT_* plus
env->cause_code = HEX_CAUSE_*, but translated code in user mode put the cause
code straight into exception_index, so cpu_loop() was decoding both forms.
gen_exception_decode_fail() and the misaligned-PC check used the raw form
unconditionally, so in system mode the cause code was misread as an event
number.
Use the {event, cause} everywhere and drop the duplicated cases
from cpu_loop(), which fixes HEX_CAUSE_PRIV_USER_NO_SINSN and
HEX_CAUSE_PRIV_USER_NO_GINSN. The misaligned PC is no longer zeroed
on its way out either, so it reaches the signal frame as si_addr instead
of whatever r31 held.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/translate.h | 2 +-
linux-user/hexagon/cpu_loop.c | 30 +++++++++++-------------------
target/hexagon/cpu.c | 3 ++-
target/hexagon/translate.c | 27 +++++++++------------------
4 files changed, 23 insertions(+), 39 deletions(-)
diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h
index 3c5773e2c73..00de2b0d2ec 100644
--- a/target/hexagon/translate.h
+++ b/target/hexagon/translate.h
@@ -330,7 +330,7 @@ extern TCGv_i32 hex_t_sreg[NUM_SREGS];
#endif
-void hex_gen_exception_end_tb(DisasContext *ctx, int excp);
+void hex_gen_exception_end_tb(DisasContext *ctx, int cause);
void process_store(DisasContext *ctx, int slot_num);
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index d7f73439dbc..e4ef97a1184 100644
--- a/linux-user/hexagon/cpu_loop.c
+++ b/linux-user/hexagon/cpu_loop.c
@@ -66,21 +66,22 @@ void cpu_loop(CPUHexagonState *env)
case HEX_CAUSE_FETCH_NO_UPAGE:
case HEX_CAUSE_PRIV_NO_UREAD:
case HEX_CAUSE_PRIV_NO_UWRITE:
- force_sig_fault(TARGET_SIGSEGV, TARGET_SEGV_MAPERR,
- env->gpr[HEX_REG_PC]);
-
- break;
+ force_sig_fault(TARGET_SIGSEGV, TARGET_SEGV_MAPERR,
+ env->gpr[HEX_REG_PC]);
+ break;
case HEX_CAUSE_PRIV_USER_NO_GINSN:
case HEX_CAUSE_PRIV_USER_NO_SINSN:
case HEX_CAUSE_INVALID_PACKET:
- force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC,
- env->gpr[HEX_REG_PC]);
- break;
+ case HEX_CAUSE_REG_WRITE_CONFLICT:
+ force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC,
+ env->gpr[HEX_REG_PC]);
+ break;
case HEX_CAUSE_MISALIGNED_LOAD:
case HEX_CAUSE_MISALIGNED_STORE:
- force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
- env->gpr[HEX_REG_PC]);
- break;
+ case HEX_CAUSE_PC_NOT_ALIGNED:
+ force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
+ env->gpr[HEX_REG_PC]);
+ break;
default:
EXCP_DUMP(env, "\nqemu: unhandled CPU precise exception "
"cause code 0x%x - aborting\n",
@@ -88,15 +89,6 @@ void cpu_loop(CPUHexagonState *env)
exit(EXIT_FAILURE);
}
break;
- case HEX_CAUSE_PC_NOT_ALIGNED:
- force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
- env->gpr[HEX_REG_R31]);
- break;
- case HEX_CAUSE_INVALID_PACKET:
- case HEX_CAUSE_REG_WRITE_CONFLICT:
- force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC,
- env->gpr[HEX_REG_PC]);
- break;
case EXCP_ATOMIC:
cpu_exec_step_atomic(cs);
break;
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 7067e5b70f7..0bbefc2fb87 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -323,7 +323,8 @@ static TCGTBCPUState hexagon_get_tb_cpu_state(CPUState *cs)
hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP, 1);
}
if (pc & PCALIGN_MASK) {
- hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0);
+ env->cause_code = HEX_CAUSE_PC_NOT_ALIGNED;
+ hexagon_raise_exception_err(env, HEX_EVENT_PRECISE, pc);
}
#ifndef CONFIG_USER_ONLY
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index 06a8159d283..5cfa60ca302 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -73,8 +73,8 @@ TCGv hex_vstore_pending[VSTORES_MAX];
#ifndef CONFIG_USER_ONLY
TCGv_i32 hex_greg[NUM_GREGS];
TCGv_i32 hex_t_sreg[NUM_SREGS];
-TCGv_i32 hex_cause_code;
#endif
+static TCGv_i32 hex_cause_code;
static const char * const hexagon_prednames[] = {
"p0", "p1", "p2", "p3"
@@ -128,19 +128,14 @@ intptr_t ctx_tmp_vreg_off(DisasContext *ctx, int regnum,
return offset;
}
-static void gen_exception(int excp, uint32_t PC)
+static void gen_precise_exception(int cause, uint32_t PC)
{
- gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp),
+ tcg_gen_movi_i32(hex_cause_code, cause);
+ gen_helper_raise_exception(tcg_env, tcg_constant_i32(HEX_EVENT_PRECISE),
tcg_constant_i32(PC));
}
#ifndef CONFIG_USER_ONLY
-static inline void gen_precise_exception(int excp, uint32_t PC)
-{
- tcg_gen_movi_i32(hex_cause_code, excp);
- gen_exception(HEX_EVENT_PRECISE, PC);
-}
-
static void gen_pcycle_counters(DisasContext *ctx)
{
if (ctx->pcycle_enabled) {
@@ -224,14 +219,10 @@ static void gen_end_tb(DisasContext *ctx)
ctx->base.is_jmp = DISAS_NORETURN;
}
-void hex_gen_exception_end_tb(DisasContext *ctx, int excp)
+void hex_gen_exception_end_tb(DisasContext *ctx, int cause)
{
gen_exec_counters(ctx);
-#ifdef CONFIG_USER_ONLY
- gen_exception(excp, ctx->pkt.pc);
-#else
- gen_precise_exception(excp, ctx->pkt.pc);
-#endif
+ gen_precise_exception(cause, ctx->pkt.pc);
ctx->base.is_jmp = DISAS_NORETURN;
}
@@ -239,13 +230,13 @@ void hex_gen_exception_end_tb(DisasContext *ctx, int excp)
* Generate exception for decode failures. Unlike gen_exception_end_tb,
* this is used when decode fails before ctx->next_PC is initialized.
*/
-static void gen_exception_decode_fail(DisasContext *ctx, int nwords, int excp)
+static void gen_exception_decode_fail(DisasContext *ctx, int nwords, int cause)
{
target_ulong fail_pc = ctx->base.pc_next + nwords * sizeof(uint32_t);
gen_exec_counters(ctx);
tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], fail_pc);
- gen_exception(excp, fail_pc);
+ gen_precise_exception(cause, fail_pc);
ctx->base.is_jmp = DISAS_NORETURN;
ctx->base.pc_next = fail_pc;
}
@@ -1361,9 +1352,9 @@ void hexagon_translate_init(void)
offsetof(CPUHexagonState, llsc_val), "llsc_val");
hex_llsc_val_i64 = tcg_global_mem_new_i64(tcg_env,
offsetof(CPUHexagonState, llsc_val_i64), "llsc_val_i64");
-#ifndef CONFIG_USER_ONLY
hex_cause_code = tcg_global_mem_new_i32(tcg_env,
offsetof(CPUHexagonState, cause_code), "cause_code");
+#ifndef CONFIG_USER_ONLY
hex_cycle_count = tcg_global_mem_new_i64(tcg_env,
offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count");
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 02/11] tests/tcg/hexagon: check priv instructions raise SIGILL
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
2026-09-01 17:33 ` [PATCH v2 01/11] target/hexagon: align exceptions for user/sysemu Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 17:33 ` [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion Brian Cain
` (8 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
A_PRIV and A_GUEST instructions executed from user mode raise
HEX_CAUSE_PRIV_USER_NO_SINSN and HEX_CAUSE_PRIV_USER_NO_GINSN, which aborted
qemu-hexagon until the preceding patch.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
tests/tcg/hexagon/privileged-insn.c | 79 +++++++++++++++++++++++++++++
tests/tcg/hexagon/Makefile.target | 1 +
2 files changed, 80 insertions(+)
create mode 100644 tests/tcg/hexagon/privileged-insn.c
diff --git a/tests/tcg/hexagon/privileged-insn.c b/tests/tcg/hexagon/privileged-insn.c
new file mode 100644
index 00000000000..306d4542ea4
--- /dev/null
+++ b/tests/tcg/hexagon/privileged-insn.c
@@ -0,0 +1,79 @@
+/*
+ * Test that privileged and guest-mode instructions raise SIGILL in user mode.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void *resume_pc;
+
+static void handle_sigill(int sig, siginfo_t *info, void *puc)
+{
+ ucontext_t *uc = (ucontext_t *)puc;
+
+ if (sig != SIGILL) {
+ _exit(EXIT_FAILURE);
+ }
+
+ uc->uc_mcontext.r0 = SIGILL;
+ uc->uc_mcontext.pc = (unsigned long)resume_pc;
+}
+
+static int test_priv_insn(void)
+{
+ int sig;
+
+ asm volatile(
+ "r0 = #0\n"
+ "r1 = ##1f\n"
+ "memw(%[pc]) = r1\n"
+ "stop(r0)\n"
+ "1:\n"
+ "%[sig] = r0\n"
+ : [sig] "=r"(sig)
+ : [pc] "r"(&resume_pc)
+ : "r0", "r1", "memory");
+
+ return sig;
+}
+
+static int test_guest_insn(void)
+{
+ int sig;
+
+ asm volatile(
+ "r0 = #0\n"
+ "r1 = ##1f\n"
+ "memw(%[pc]) = r1\n"
+ "r0 = g0\n"
+ "1:\n"
+ "%[sig] = r0\n"
+ : [sig] "=r"(sig)
+ : [pc] "r"(&resume_pc)
+ : "r0", "r1", "memory");
+
+ return sig;
+}
+
+int main()
+{
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_sigaction = handle_sigill;
+ act.sa_flags = SA_SIGINFO;
+ assert(sigaction(SIGILL, &act, NULL) == 0);
+
+ assert(test_priv_insn() == SIGILL);
+ assert(test_guest_insn() == SIGILL);
+
+ puts("PASS");
+ return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target
index 61adf6356e4..641f6bc10e7 100644
--- a/tests/tcg/hexagon/Makefile.target
+++ b/tests/tcg/hexagon/Makefile.target
@@ -58,6 +58,7 @@ HEX_TESTS += invalid-slots
HEX_TESTS += valid-slots
HEX_TESTS += invalid-encoding
HEX_TESTS += multiple-writes
+HEX_TESTS += privileged-insn
HEX_TESTS += unaligned_pc
HEX_TESTS += unaligned_data
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
2026-09-01 17:33 ` [PATCH v2 01/11] target/hexagon: align exceptions for user/sysemu Brian Cain
2026-09-01 17:33 ` [PATCH v2 02/11] tests/tcg/hexagon: check priv instructions raise SIGILL Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-02 21:30 ` Philippe Mathieu-Daudé
2026-09-01 17:33 ` [PATCH v2 04/11] target/hexagon: guard writes to unimplemented guest registers Brian Cain
` (7 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel
Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller,
Sid Manning
From: Sid Manning <sidneym@quicinc.com>
l2vic_set_irq() clears a level-triggered source's pending bit on
de-assertion, so a source that drops before it is latched into
int_status is not spuriously re-delivered on the next ciad.
Edge-triggered sources (int_type set) keep the pulse semantics they
already have via SOFT_INT/set_irq(level=1).
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
hw/intc/hex-l2vic.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
index f07ec850d49..a986f0bdf35 100644
--- a/hw/intc/hex-l2vic.c
+++ b/hw/intc/hex-l2vic.c
@@ -299,6 +299,8 @@ static void l2vic_set_irq(void *opaque, int irq, int level)
if (level) {
set_bit32(irq, s->int_pending);
+ } else if (!test_bit32(irq, s->int_type)) {
+ clear_bit32(irq, s->int_pending);
}
l2vic_update(s, irq);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion
2026-09-01 17:33 ` [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion Brian Cain
@ 2026-09-02 21:30 ` Philippe Mathieu-Daudé
2026-09-03 19:33 ` Brian Cain
0 siblings, 1 reply; 20+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-02 21:30 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller, Sid Manning
On 1/9/26 19:33, Brian Cain wrote:
> From: Sid Manning <sidneym@quicinc.com>
>
> l2vic_set_irq() clears a level-triggered source's pending bit on
> de-assertion, so a source that drops before it is latched into
> int_status is not spuriously re-delivered on the next ciad.
> Edge-triggered sources (int_type set) keep the pulse semantics they
> already have via SOFT_INT/set_irq(level=1).
>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> hw/intc/hex-l2vic.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
> index f07ec850d49..a986f0bdf35 100644
> --- a/hw/intc/hex-l2vic.c
> +++ b/hw/intc/hex-l2vic.c
> @@ -299,6 +299,8 @@ static void l2vic_set_irq(void *opaque, int irq, int level)
>
> if (level) {
> set_bit32(irq, s->int_pending);
> + } else if (!test_bit32(irq, s->int_type)) {
> + clear_bit32(irq, s->int_pending);
> }
> l2vic_update(s, irq);
I'm confused with l2vic_update() logic which seems to
duplicate a bit.
> }
hw/intc/hex-l2vic.c-95- /* Edge or Level interrupt */
hw/intc/hex-l2vic.c:96: DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX);
Not obvious, so 0:edge and 1:level. Ah no...
hw/intc/hex-l2vic.c-268- * Only auto-disable for edge-triggered
interrupts (type=1).
So 1:edge and 0:level?
Tiny helpers can help to make the code more readable:
static bool edge_triggered_irq(HexL2VICState *s, int irq)
{
return test_bit32(irq, s->int_type) == 1;
}
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion
2026-09-02 21:30 ` Philippe Mathieu-Daudé
@ 2026-09-03 19:33 ` Brian Cain
0 siblings, 0 replies; 20+ messages in thread
From: Brian Cain @ 2026-09-03 19:33 UTC (permalink / raw)
To: Philippe Mathieu-Daudé, qemu-devel
Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller, Sid Manning
On 9/2/2026 4:30 PM, Philippe Mathieu-Daudé wrote:
> On 1/9/26 19:33, Brian Cain wrote:
>> From: Sid Manning <sidneym@quicinc.com>
>>
>> l2vic_set_irq() clears a level-triggered source's pending bit on
>> de-assertion, so a source that drops before it is latched into
>> int_status is not spuriously re-delivered on the next ciad.
>> Edge-triggered sources (int_type set) keep the pulse semantics they
>> already have via SOFT_INT/set_irq(level=1).
>>
>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>> ---
>> hw/intc/hex-l2vic.c | 2 ++
>> 1 file changed, 2 insertions(+)
>>
>> diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
>> index f07ec850d49..a986f0bdf35 100644
>> --- a/hw/intc/hex-l2vic.c
>> +++ b/hw/intc/hex-l2vic.c
>> @@ -299,6 +299,8 @@ static void l2vic_set_irq(void *opaque, int irq,
>> int level)
>> if (level) {
>> set_bit32(irq, s->int_pending);
>> + } else if (!test_bit32(irq, s->int_type)) {
>> + clear_bit32(irq, s->int_pending);
>> }
>> l2vic_update(s, irq);
>
> I'm confused with l2vic_update() logic which seems to
> duplicate a bit.
>
>> }
>
> hw/intc/hex-l2vic.c-95- /* Edge or Level interrupt */
> hw/intc/hex-l2vic.c:96: DECLARE_BITMAP32(int_type,
> L2VIC_INTERRUPT_MAX);
>
> Not obvious, so 0:edge and 1:level. Ah no...
>
> hw/intc/hex-l2vic.c-268- * Only auto-disable for
> edge-triggered interrupts (type=1).
>
> So 1:edge and 0:level?
>
> Tiny helpers can help to make the code more readable:
Agreed! I like this change. Sent a follow up.
>
> static bool edge_triggered_irq(HexL2VICState *s, int irq)
> {
> return test_bit32(irq, s->int_type) == 1;
> }
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 04/11] target/hexagon: guard writes to unimplemented guest registers
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (2 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 03/11] hw/intc: clear pending bit on l2vic de-assertion Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 18:25 ` Pierrick Bouvier
2026-09-01 17:33 ` [PATCH v2 05/11] target/hexagon: take BQL when reading the system pcycle count Brian Cain
` (6 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
Guest register G4 and above have PMU-ish features that are yet
to be implemented. We'll skip these when we encounter this code
rather than fault on an assertion.
Note that the generated call to greg_writable() has the side-effect
of doing a LOG_UNIMP for these register accesses.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/translate.h | 4 +++-
target/hexagon/hex_common.py | 18 +++++++++++++++++-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h
index 00de2b0d2ec..d8628383f1d 100644
--- a/target/hexagon/translate.h
+++ b/target/hexagon/translate.h
@@ -97,7 +97,9 @@ bool is_gather_store_insn(DisasContext *ctx);
#ifndef CONFIG_USER_ONLY
static inline void ctx_log_greg_write(DisasContext *ctx, int rnum)
{
- assert(rnum <= HEX_GREG_G3);
+ if (rnum > HEX_GREG_G3) {
+ return;
+ }
ctx->greg_log[ctx->greg_log_idx] = rnum;
ctx->greg_log_idx++;
}
diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py
index e33d43e3ce0..c180c19b092 100755
--- a/target/hexagon/hex_common.py
+++ b/target/hexagon/hex_common.py
@@ -1097,11 +1097,24 @@ def analyze_write(self, f, tag, regno):
"""))
class GuestRegister(Register):
- pass
+ def gen_check_impl(self, f, regno):
+ if self.is_written():
+ f.write(code_fmt(f"""\
+ if (!greg_writable(insn->regno[{regno}],
+ {str(self.is_pair()).lower()})) {{
+ return;
+ }}
+ """))
+ else:
+ f.write(code_fmt(f"""\
+ check_greg_impl(insn->regno[{regno}],
+ {str(self.is_pair()).lower()});
+ """))
class GuestDest(GuestRegister, Single, Dest):
def decl_tcg(self, f, tag, regno):
self.decl_reg_num(f, regno)
+ self.gen_check_impl(f, regno)
f.write(code_fmt(f"""\
TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
"""))
@@ -1121,6 +1134,7 @@ def decl_reg_num(self, f, regno):
"""))
def decl_tcg(self, f, tag, regno):
self.decl_reg_num(f, regno)
+ self.gen_check_impl(f, regno)
f.write(code_fmt(f"""\
TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
gen_read_greg({self.reg_tcg()}, {self.reg_num});
@@ -1131,6 +1145,7 @@ def analyze_read(self, f, regno):
class GuestPairDest(GuestRegister, Pair, Dest):
def decl_tcg(self, f, tag, regno):
self.decl_reg_num(f, regno)
+ self.gen_check_impl(f, regno)
f.write(code_fmt(f"""\
TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
"""))
@@ -1150,6 +1165,7 @@ def decl_reg_num(self, f, regno):
"""))
def decl_tcg(self, f, tag, regno):
self.decl_reg_num(f, regno)
+ self.gen_check_impl(f, regno)
f.write(code_fmt(f"""\
TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
gen_read_greg_pair({self.reg_tcg()}, {self.reg_num});
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 04/11] target/hexagon: guard writes to unimplemented guest registers
2026-09-01 17:33 ` [PATCH v2 04/11] target/hexagon: guard writes to unimplemented guest registers Brian Cain
@ 2026-09-01 18:25 ` Pierrick Bouvier
0 siblings, 0 replies; 20+ messages in thread
From: Pierrick Bouvier @ 2026-09-01 18:25 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Laurent Vivier, Helge Deller
On 9/1/2026 10:33 AM, Brian Cain wrote:
> Guest register G4 and above have PMU-ish features that are yet
> to be implemented. We'll skip these when we encounter this code
> rather than fault on an assertion.
>
> Note that the generated call to greg_writable() has the side-effect
> of doing a LOG_UNIMP for these register accesses.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> target/hexagon/translate.h | 4 +++-
> target/hexagon/hex_common.py | 18 +++++++++++++++++-
> 2 files changed, 20 insertions(+), 2 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 05/11] target/hexagon: take BQL when reading the system pcycle count
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (3 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 04/11] target/hexagon: guard writes to unimplemented guest registers Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 17:33 ` [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE Brian Cain
` (5 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
hexagon_get_sys_pcycle_count() iterates all CPUs, so take the BQL with
BQL_LOCK_GUARD() instead of asserting the caller already holds it.
Convert the matching setters the same way, which keeps the locking
contract symmetric and makes the read-modify-write in the _low/_high
setters atomic. BQL_LOCK_GUARD() is a no-op when the lock is already
held, so the nested guards on the guest-register read path cost nothing.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/cpu_helper.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c
index 64c5746c6d9..649c583c0fa 100644
--- a/target/hexagon/cpu_helper.c
+++ b/target/hexagon/cpu_helper.c
@@ -36,7 +36,7 @@ uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env)
uint64_t total = 0;
CPUState *cs;
- g_assert(bql_locked());
+ BQL_LOCK_GUARD();
CPU_FOREACH(cs) {
CPUHexagonState *thread_env = cpu_env(cs);
total += thread_env->t_cycle_count;
@@ -54,11 +54,15 @@ uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env)
return (uint32_t)(hexagon_get_sys_pcycle_count(env));
}
+/*
+ * Every function in this family takes the BQL itself, so the guard below
+ * holds it across the read-modify-write. Nested guards are no-ops.
+ */
void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t val)
{
uint64_t old;
- g_assert(bql_locked());
+ BQL_LOCK_GUARD();
old = hexagon_get_sys_pcycle_count(env);
old = deposit64(old, 32, 32, val);
hexagon_set_sys_pcycle_count(env, old);
@@ -68,7 +72,7 @@ void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t val)
{
uint64_t old;
- g_assert(bql_locked());
+ BQL_LOCK_GUARD();
old = hexagon_get_sys_pcycle_count(env);
old = deposit64(old, 0, 32, val);
hexagon_set_sys_pcycle_count(env, old);
@@ -81,7 +85,7 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t val)
int num_threads;
int64_t delta, per_thread, remainder;
- g_assert(bql_locked());
+ BQL_LOCK_GUARD();
total = hexagon_get_sys_pcycle_count(env);
/* Count active threads */
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (4 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 05/11] target/hexagon: take BQL when reading the system pcycle count Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-02 21:16 ` Philippe Mathieu-Daudé
2026-09-01 17:33 ` [PATCH v2 07/11] target/hexagon: read UTIMERLO/UTIMERHI from the global timer Brian Cain
` (4 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
GPCYCLELO/GPCYCLEHI read as zero unless the cycle counter is enabled
(SSR:CE), matching the hardware.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/cpu.c | 7 +++++--
target/hexagon/op_helper.c | 9 ++-------
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 0bbefc2fb87..0a677840bcb 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -809,14 +809,17 @@ static void hexagon_cpu_class_init(ObjectClass *c, const void *data)
#ifndef CONFIG_USER_ONLY
uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg)
{
+ uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+ int ssr_ce = GET_SSR_FIELD(SSR_CE, ssr);
+
if (reg <= HEX_GREG_G3) {
return env->greg[reg];
}
switch (reg) {
case HEX_GREG_GPCYCLELO:
- return hexagon_get_sys_pcycle_count_low(env);
+ return ssr_ce ? hexagon_get_sys_pcycle_count_low(env) : 0;
case HEX_GREG_GPCYCLEHI:
- return hexagon_get_sys_pcycle_count_high(env);
+ return ssr_ce ? hexagon_get_sys_pcycle_count_high(env) : 0;
default:
qemu_log_mask(LOG_UNIMP, "reading greg %" PRId32
" not yet supported.\n", reg);
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 23894ff3d28..2cea1927263 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -1912,13 +1912,8 @@ uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg)
return (uint64_t)(env->greg[reg]) |
(((uint64_t)(env->greg[reg + 1])) << 32);
}
- switch (reg) {
- case HEX_GREG_GPCYCLELO:
- return hexagon_get_sys_pcycle_count(env);
- default:
- return (uint64_t)hexagon_greg_read(env, reg) |
- ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
- }
+ return (uint64_t)hexagon_greg_read(env, reg) |
+ ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
}
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE
2026-09-01 17:33 ` [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE Brian Cain
@ 2026-09-02 21:16 ` Philippe Mathieu-Daudé
2026-09-03 19:36 ` Brian Cain
0 siblings, 1 reply; 20+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-02 21:16 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller
Hi Brian,
On 1/9/26 19:33, Brian Cain wrote:
> GPCYCLELO/GPCYCLEHI read as zero unless the cycle counter is enabled
> (SSR:CE), matching the hardware.
>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> target/hexagon/cpu.c | 7 +++++--
> target/hexagon/op_helper.c | 9 ++-------
> 2 files changed, 7 insertions(+), 9 deletions(-)
> diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
> index 23894ff3d28..2cea1927263 100644
> --- a/target/hexagon/op_helper.c
> +++ b/target/hexagon/op_helper.c
> @@ -1912,13 +1912,8 @@ uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg)
> return (uint64_t)(env->greg[reg]) |
> (((uint64_t)(env->greg[reg + 1])) << 32);
> }
> - switch (reg) {
> - case HEX_GREG_GPCYCLELO:
> - return hexagon_get_sys_pcycle_count(env);
> - default:
> - return (uint64_t)hexagon_greg_read(env, reg) |
> - ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
> - }
> + return (uint64_t)hexagon_greg_read(env, reg) |
> + ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
> }
Just curious, does greg_read_pair() work with impair %reg?
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE
2026-09-02 21:16 ` Philippe Mathieu-Daudé
@ 2026-09-03 19:36 ` Brian Cain
2026-09-04 13:19 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-03 19:36 UTC (permalink / raw)
To: Philippe Mathieu-Daudé, qemu-devel
Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller
On 9/2/2026 4:16 PM, Philippe Mathieu-Daudé wrote:
> Hi Brian,
>
> On 1/9/26 19:33, Brian Cain wrote:
>> GPCYCLELO/GPCYCLEHI read as zero unless the cycle counter is enabled
>> (SSR:CE), matching the hardware.
>>
>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>> ---
>> target/hexagon/cpu.c | 7 +++++--
>> target/hexagon/op_helper.c | 9 ++-------
>> 2 files changed, 7 insertions(+), 9 deletions(-)
>
>
>> diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
>> index 23894ff3d28..2cea1927263 100644
>> --- a/target/hexagon/op_helper.c
>> +++ b/target/hexagon/op_helper.c
>> @@ -1912,13 +1912,8 @@ uint64_t
>> HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg)
>> return (uint64_t)(env->greg[reg]) |
>> (((uint64_t)(env->greg[reg + 1])) << 32);
>> }
>> - switch (reg) {
>> - case HEX_GREG_GPCYCLELO:
>> - return hexagon_get_sys_pcycle_count(env);
>> - default:
>> - return (uint64_t)hexagon_greg_read(env, reg) |
>> - ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
>> - }
>> + return (uint64_t)hexagon_greg_read(env, reg) |
>> + ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
>> }
>
> Just curious, does greg_read_pair() work with impair %reg?
Not sure if I understand "impair %reg"?
But - if this refers to the odd-value pairs, then no - they don't. The
guest reg encoding only has the "g{odd}:{even}" designated by the even
reg's value. Sent a follow up patch to assert this in greg_read_pair().
-Brian
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE
2026-09-03 19:36 ` Brian Cain
@ 2026-09-04 13:19 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 20+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-04 13:19 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller
On 3/9/26 21:36, Brian Cain wrote:
>
> On 9/2/2026 4:16 PM, Philippe Mathieu-Daudé wrote:
>> Hi Brian,
>>
>> On 1/9/26 19:33, Brian Cain wrote:
>>> GPCYCLELO/GPCYCLEHI read as zero unless the cycle counter is enabled
>>> (SSR:CE), matching the hardware.
>>>
>>> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
>>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>>> ---
>>> target/hexagon/cpu.c | 7 +++++--
>>> target/hexagon/op_helper.c | 9 ++-------
>>> 2 files changed, 7 insertions(+), 9 deletions(-)
>>
>>
>>> diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
>>> index 23894ff3d28..2cea1927263 100644
>>> --- a/target/hexagon/op_helper.c
>>> +++ b/target/hexagon/op_helper.c
>>> @@ -1912,13 +1912,8 @@ uint64_t HELPER(greg_read_pair)
>>> (CPUHexagonState *env, uint32_t reg)
>>> return (uint64_t)(env->greg[reg]) |
>>> (((uint64_t)(env->greg[reg + 1])) << 32);
>>> }
>>> - switch (reg) {
>>> - case HEX_GREG_GPCYCLELO:
>>> - return hexagon_get_sys_pcycle_count(env);
>>> - default:
>>> - return (uint64_t)hexagon_greg_read(env, reg) |
>>> - ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
>>> - }
>>> + return (uint64_t)hexagon_greg_read(env, reg) |
>>> + ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
>>> }
>>
>> Just curious, does greg_read_pair() work with impair %reg?
>
>
> Not sure if I understand "impair %reg"?
>
> But - if this refers to the odd-value pairs,
Yes I meant 'odd' ;)
> then no - they don't. The
> guest reg encoding only has the "g{odd}:{even}" designated by the even
> reg's value. Sent a follow up patch to assert this in greg_read_pair().
>
> -Brian
>
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 07/11] target/hexagon: read UTIMERLO/UTIMERHI from the global timer
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (5 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 06/11] target/hexagon: gate GPCYCLE guest register reads on SSR:CE Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-02 21:21 ` Philippe Mathieu-Daudé
2026-09-01 17:33 ` [PATCH v2 08/11] target/hexagon: raise imprecise exception on multi-TLB match Brian Cain
` (3 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
In system mode, route accesses to the qtimer-backed global
TIMERLO/TIMERHI so guest reads see a live, monotonically increasing
timer.
In user mode we derive it from QEMU_CLOCK_VIRTUAL.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/helper.h | 4 ++++
target/hexagon/genptr.c | 29 +++++++++++++++++++++++++++++
target/hexagon/op_helper.c | 21 +++++++++++++++++++++
tests/tcg/hexagon/reg_mut.c | 11 ++++++++---
4 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h
index 78dc28ca9e5..e39afd623b5 100644
--- a/target/hexagon/helper.h
+++ b/target/hexagon/helper.h
@@ -113,6 +113,10 @@ DEF_HELPER_FLAGS_4(gvec_sabsdiff_w, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_uabsdiff_b, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
DEF_HELPER_FLAGS_4(gvec_uabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+#if defined(CONFIG_USER_ONLY)
+DEF_HELPER_FLAGS_0(utimer, TCG_CALL_NO_RWG, i64)
+#endif
+
#if !defined(CONFIG_USER_ONLY)
DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32)
DEF_HELPER_2(swi, void, env, i32)
diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
index 2a98b13b714..282a697d190 100644
--- a/target/hexagon/genptr.c
+++ b/target/hexagon/genptr.c
@@ -411,6 +411,23 @@ static inline void gen_read_ctrl_reg(DisasContext *ctx, const int reg_num,
} else if (reg_num == HEX_REG_QEMU_HVX_CNT) {
tcg_gen_addi_tl(dest, hex_gpr[HEX_REG_QEMU_HVX_CNT],
ctx->num_hvx_insns);
+#ifndef CONFIG_USER_ONLY
+ } else if (reg_num == HEX_REG_UTIMERLO) {
+ gen_helper_sreg_read(dest, tcg_env,
+ tcg_constant_i32(HEX_SREG_TIMERLO));
+ } else if (reg_num == HEX_REG_UTIMERHI) {
+ gen_helper_sreg_read(dest, tcg_env,
+ tcg_constant_i32(HEX_SREG_TIMERHI));
+#else
+ } else if (reg_num == HEX_REG_UTIMERLO) {
+ TCGv_i64 utimer = tcg_temp_new_i64();
+ gen_helper_utimer(utimer);
+ tcg_gen_extrl_i64_i32(dest, utimer);
+ } else if (reg_num == HEX_REG_UTIMERHI) {
+ TCGv_i64 utimer = tcg_temp_new_i64();
+ gen_helper_utimer(utimer);
+ tcg_gen_extrh_i64_i32(dest, utimer);
+#endif
} else {
tcg_gen_mov_tl(dest, hex_gpr[reg_num]);
}
@@ -439,6 +456,18 @@ static inline void gen_read_ctrl_reg_pair(DisasContext *ctx, const int reg_num,
tcg_gen_addi_tl(hvx_cnt, hex_gpr[HEX_REG_QEMU_HVX_CNT],
ctx->num_hvx_insns);
tcg_gen_concat_i32_i64(dest, hvx_cnt, hex_gpr[reg_num + 1]);
+#ifndef CONFIG_USER_ONLY
+ } else if (reg_num == HEX_REG_UTIMERLO) {
+ TCGv lo = tcg_temp_new();
+ TCGv hi = tcg_temp_new();
+ gen_helper_sreg_read(lo, tcg_env, tcg_constant_i32(HEX_SREG_TIMERLO));
+ gen_helper_sreg_read(hi, tcg_env, tcg_constant_i32(HEX_SREG_TIMERHI));
+ tcg_gen_concat_i32_i64(dest, lo, hi);
+#else
+ } else if (reg_num == HEX_REG_UTIMERLO) {
+ /* One helper call, so the pair is a coherent 64-bit snapshot. */
+ gen_helper_utimer(dest);
+#endif
} else {
tcg_gen_concat_i32_i64(dest,
hex_gpr[reg_num],
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 2cea1927263..df31ed2488a 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -37,6 +37,9 @@
#include "cpu_helper.h"
#include "tcg/tcg-gvec-desc.h"
#include "translate.h"
+#ifdef CONFIG_USER_ONLY
+#include "qemu/timer.h"
+#endif
#ifndef CONFIG_USER_ONLY
#include "hw/hexagon/hexagon_globalreg.h"
#include "hex_mmu.h"
@@ -46,6 +49,24 @@
#include "hexswi.h"
#endif
+#ifdef CONFIG_USER_ONLY
+/*
+ * User mode has no qtimer device backing TIMERLO/TIMERHI, so derive the
+ * user timer directly from the virtual clock -- the same clock the qtimer
+ * counts -- at the qtimer's default 19.2MHz tick rate, masked to the
+ * qtimer's counter width.
+ */
+#define HEX_UTIMER_FREQ_HZ 19200000ULL
+#define HEX_UTIMER_CNT_MASK 0x00ffffffffffffffULL
+
+uint64_t HELPER(utimer)(void)
+{
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ HEX_UTIMER_FREQ_HZ, NANOSECONDS_PER_SECOND) &
+ HEX_UTIMER_CNT_MASK;
+}
+#endif
+
#define SF_BIAS 127
#define SF_MANTBITS 23
diff --git a/tests/tcg/hexagon/reg_mut.c b/tests/tcg/hexagon/reg_mut.c
index c5a39e55100..9ce18f3ebe5 100644
--- a/tests/tcg/hexagon/reg_mut.c
+++ b/tests/tcg/hexagon/reg_mut.c
@@ -77,10 +77,10 @@ static inline void write_control_registers(void)
check32(result, 0x00000000);
WRITE_REG_NOCLOBBER(result, "utimerlo", 0xffffffff);
- check32(result, 0x00000000);
+ check32_ne(result, 0xffffffff);
WRITE_REG_NOCLOBBER(result, "utimerhi", 0xffffffff);
- check32(result, 0x00000000);
+ check32_ne(result, 0xffffffff);
/*
* PC is special. Setting it to these values
@@ -106,8 +106,13 @@ static inline void write_control_register_pairs(void)
WRITE_REG_NOCLOBBER(result, "c15:14", 0xffffffffffffffff);
check64(result, 0x0000000000000000);
+ /*
+ * c31:30 is UTIMERHI:UTIMERLO, a read-only free-running counter. The
+ * write must be discarded; the read-back is whatever the timer says,
+ * so only check that the written value did not stick.
+ */
WRITE_REG_NOCLOBBER(result, "c31:30", 0xffffffffffffffff);
- check64(result, 0x0000000000000000);
+ check64_ne(result, 0xffffffffffffffff);
WRITE_REG_PAIR_ENCODED(result, "c9:8", (uint64_t) 0x0000000000000000,
C9_8_EQ_R1_0);
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 07/11] target/hexagon: read UTIMERLO/UTIMERHI from the global timer
2026-09-01 17:33 ` [PATCH v2 07/11] target/hexagon: read UTIMERLO/UTIMERHI from the global timer Brian Cain
@ 2026-09-02 21:21 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 20+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-02 21:21 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Pierrick Bouvier, Laurent Vivier, Helge Deller
On 1/9/26 19:33, Brian Cain wrote:
> In system mode, route accesses to the qtimer-backed global
> TIMERLO/TIMERHI so guest reads see a live, monotonically increasing
> timer.
>
> In user mode we derive it from QEMU_CLOCK_VIRTUAL.
>
> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> target/hexagon/helper.h | 4 ++++
> target/hexagon/genptr.c | 29 +++++++++++++++++++++++++++++
> target/hexagon/op_helper.c | 21 +++++++++++++++++++++
> tests/tcg/hexagon/reg_mut.c | 11 ++++++++---
> 4 files changed, 62 insertions(+), 3 deletions(-)
> diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
> index 2cea1927263..df31ed2488a 100644
> --- a/target/hexagon/op_helper.c
> +++ b/target/hexagon/op_helper.c
> @@ -37,6 +37,9 @@
> #include "cpu_helper.h"
> #include "tcg/tcg-gvec-desc.h"
> #include "translate.h"
> +#ifdef CONFIG_USER_ONLY
> +#include "qemu/timer.h"
> +#endif
> #ifndef CONFIG_USER_ONLY
> #include "hw/hexagon/hexagon_globalreg.h"
> #include "hex_mmu.h"
> @@ -46,6 +49,24 @@
> #include "hexswi.h"
> #endif
>
> +#ifdef CONFIG_USER_ONLY
> +/*
> + * User mode has no qtimer device backing TIMERLO/TIMERHI, so derive the
> + * user timer directly from the virtual clock -- the same clock the qtimer
> + * counts -- at the qtimer's default 19.2MHz tick rate, masked to the
> + * qtimer's counter width.
> + */
> +#define HEX_UTIMER_FREQ_HZ 19200000ULL
> +#define HEX_UTIMER_CNT_MASK 0x00ffffffffffffffULL
> +
> +uint64_t HELPER(utimer)(void)
> +{
> + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
> + HEX_UTIMER_FREQ_HZ, NANOSECONDS_PER_SECOND) &
> + HEX_UTIMER_CNT_MASK;
> +}
> +#endif
Long term it could be easier to maintain a new user-specific file,
adding it to hexagon_user_ss[] then target_user_arch (less #ifdefs).
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 08/11] target/hexagon: raise imprecise exception on multi-TLB match
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (6 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 07/11] target/hexagon: read UTIMERLO/UTIMERHI from the global timer Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 17:33 ` [PATCH v2 09/11] target/hexagon: implement direct-to-guest interrupt delivery Brian Cain
` (2 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
tlbp now records a pending imprecise exception (via env->imprecise_exception)
when the lookup matches multiple entries, and the translator raises it after
the tlbp packet. Implement the HEX_EVENT_IMPRECISE delivery path so the
guest sees HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH instead of the exception
being silently dropped.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hexagon_tlb.h | 3 ++-
target/hexagon/cpu.h | 1 +
hw/hexagon/hexagon_tlb.c | 5 ++++-
target/hexagon/hex_mmu.c | 5 ++++-
target/hexagon/hexswi.c | 37 +++++++++++++++++++++++++++++---
target/hexagon/machine.c | 5 +++--
target/hexagon/translate.c | 29 +++++++++++++++++++++++++
7 files changed, 77 insertions(+), 8 deletions(-)
diff --git a/include/hw/hexagon/hexagon_tlb.h b/include/hw/hexagon/hexagon_tlb.h
index 760dc1ea811..67c0a56b79e 100644
--- a/include/hw/hexagon/hexagon_tlb.h
+++ b/include/hw/hexagon/hexagon_tlb.h
@@ -32,7 +32,8 @@ bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
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);
+ uint32_t VA, uint32_t *imprecise_exception,
+ int *cause_code);
int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
uint64_t index);
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index c50fbb3f72a..ed5671abe5c 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -147,6 +147,7 @@ typedef struct CPUArchState {
uint64_t t_cycle_count;
#endif
uint32_t next_PC;
+ uint32_t imprecise_exception;
target_ulong new_value_usr;
MemLog mem_log_stores[STORES_MAX];
diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c
index b6d4aff389e..6539458f25f 100644
--- a/hw/hexagon/hexagon_tlb.c
+++ b/hw/hexagon/hexagon_tlb.c
@@ -319,15 +319,18 @@ bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
}
uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
- uint32_t VA, int *cause_code)
+ uint32_t VA, uint32_t *imprecise_exception,
+ int *cause_code)
{
uint32_t not_found = 0x80000000;
uint32_t idx = not_found;
+ *imprecise_exception = 0;
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) {
+ *imprecise_exception = HEX_EVENT_IMPRECISE;
*cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
break;
}
diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
index 81d64a6146c..de108f709c0 100644
--- a/target/hexagon/hex_mmu.c
+++ b/target/hexagon/hex_mmu.c
@@ -86,9 +86,12 @@ 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);
+ uint32_t imprecise_exception = 0;
int cause_code = 0;
- uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA, &cause_code);
+ uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA,
+ &imprecise_exception, &cause_code);
+ env->imprecise_exception = imprecise_exception;
if (cause_code) {
env->cause_code = cause_code;
}
diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c
index 43c373ea2ee..d29c12e6481 100644
--- a/target/hexagon/hexswi.c
+++ b/target/hexagon/hexswi.c
@@ -69,6 +69,7 @@ void hexagon_cpu_do_interrupt(CPUState *cs)
{
CPUHexagonState *env = cpu_env(cs);
+ HexagonCPU *cpu = HEXAGON_CPU(cs);
uint32_t ssr;
BQL_LOCK_GUARD();
@@ -83,7 +84,6 @@ void hexagon_cpu_do_interrupt(CPUState *cs)
ssr = env->t_sreg[HEX_SREG_SSR];
if (GET_SSR_FIELD(SSR_EX, ssr) == 1) {
- HexagonCPU *cpu = env_archcpu(env);
if (cpu->globalregs) {
hexagon_globalreg_write(cpu->globalregs, HEX_SREG_DIAG,
env->cause_code, env->threadId);
@@ -243,8 +243,39 @@ void hexagon_cpu_do_interrupt(CPUState *cs)
break;
case HEX_EVENT_IMPRECISE:
- qemu_log_mask(LOG_UNIMP,
- "Imprecise exception: this case is not yet handled");
+ if (get_exe_mode(env) == HEX_EXE_MODE_WAIT) {
+ env->gpr[HEX_REG_PC] = env->wait_next_pc - 4;
+ clear_wait_mode(env);
+ }
+ switch (env->cause_code) {
+ case HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH:
+ hexagon_ssr_set_cause(env, env->cause_code);
+ set_addresses(env, 4, cs->exception_index);
+ if (cpu->globalregs) {
+ hexagon_globalreg_write(cpu->globalregs, HEX_SREG_DIAG,
+ (0x4 << 4) | (env->t_sreg[HEX_SREG_HTID] & 0xF),
+ env->threadId);
+ }
+ break;
+
+ case HEX_CAUSE_IMPRECISE_NMI:
+ hexagon_ssr_set_cause(env, env->cause_code);
+ set_addresses(env, 4, cs->exception_index);
+ if (cpu->globalregs) {
+ hexagon_globalreg_write(cpu->globalregs, HEX_SREG_DIAG,
+ (0x3 << 4) | (env->t_sreg[HEX_SREG_HTID] & 0xF),
+ env->threadId);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Imprecise exception with unhandled cause 0x%x\n",
+ env->cause_code);
+ hexagon_ssr_set_cause(env, env->cause_code);
+ set_addresses(env, 4, cs->exception_index);
+ break;
+ }
break;
default:
diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c
index 2dd95466e7d..bf4646f4a8b 100644
--- a/target/hexagon/machine.c
+++ b/target/hexagon/machine.c
@@ -10,8 +10,8 @@
const VMStateDescription vmstate_hexagon_cpu = {
.name = "cpu",
- .version_id = 1,
- .minimum_version_id = 1,
+ .version_id = 2,
+ .minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(env.gpr, HexagonCPU, TOTAL_PER_THREAD_REGS),
VMSTATE_UINT32_ARRAY(env.pred, HexagonCPU, NUM_PREGS),
@@ -26,6 +26,7 @@ const VMStateDescription vmstate_hexagon_cpu = {
VMSTATE_UINT32(env.cause_code, HexagonCPU),
VMSTATE_UINT32(env.wait_next_pc, HexagonCPU),
VMSTATE_UINT64(env.t_cycle_count, HexagonCPU),
+ VMSTATE_UINT32(env.imprecise_exception, HexagonCPU),
VMSTATE_END_OF_LIST()
},
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index 5cfa60ca302..1d28f2db0fc 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -65,6 +65,7 @@ TCGv hex_llsc_val;
TCGv_i64 hex_llsc_val_i64;
#ifndef CONFIG_USER_ONLY
TCGv_i64 hex_cycle_count;
+TCGv hex_imprecise_exception;
#endif
TCGv hex_vstore_addr[VSTORES_MAX];
TCGv hex_vstore_size[VSTORES_MAX];
@@ -1051,6 +1052,28 @@ static void update_exec_counters(DisasContext *ctx)
ctx->num_cycles += PCYCLES_PER_PACKET;
}
+#ifndef CONFIG_USER_ONLY
+/*
+ * A tlbp instruction may detect multiple TLB matches and set a pending
+ * imprecise exception. Raise it after the packet that ran the tlbp.
+ */
+static void check_imprecise_exception(Packet *pkt)
+{
+ for (int i = 0; i < pkt->num_insns; i++) {
+ if (pkt->insn[i].opcode == Y2_tlbp) {
+ TCGv PC = tcg_constant_tl(pkt->pc);
+ TCGLabel *label = gen_new_label();
+ tcg_gen_brcondi_tl(TCG_COND_EQ, hex_imprecise_exception,
+ 0, label);
+ gen_helper_raise_exception(tcg_env,
+ hex_imprecise_exception, PC);
+ gen_set_label(label);
+ return;
+ }
+ }
+}
+#endif
+
static void gen_commit_packet(DisasContext *ctx)
{
/*
@@ -1150,6 +1173,10 @@ static void gen_commit_packet(DisasContext *ctx)
ctx->pkt.vhist_insn->generate(ctx);
}
+#ifndef CONFIG_USER_ONLY
+ check_imprecise_exception(&ctx->pkt);
+#endif
+
if (ctx->pkt_ends_tb || ctx->base.is_jmp == DISAS_NORETURN) {
gen_end_tb(ctx);
}
@@ -1357,6 +1384,8 @@ void hexagon_translate_init(void)
#ifndef CONFIG_USER_ONLY
hex_cycle_count = tcg_global_mem_new_i64(tcg_env,
offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count");
+ hex_imprecise_exception = tcg_global_mem_new(tcg_env,
+ offsetof(CPUHexagonState, imprecise_exception), "imprecise_exception");
#endif
for (i = 0; i < STORES_MAX; i++) {
snprintf(store_addr_names[i], NAME_LEN, "store_addr_%d", i);
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 09/11] target/hexagon: implement direct-to-guest interrupt delivery
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (7 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 08/11] target/hexagon: raise imprecise exception on multi-TLB match Brian Cain
@ 2026-09-01 17:33 ` Brian Cain
2026-09-01 18:26 ` Pierrick Bouvier
2026-09-01 17:33 ` [PATCH v2 10/11] target/hexagon: fix iassign{r, w} to cover all threads Brian Cain via qemu development
2026-09-01 17:33 ` [PATCH v2 11/11] tests/functional/hexagon: update to v0.2.12, +test_{interrupts, sys_regs} Brian Cain via qemu development
10 siblings, 1 reply; 20+ messages in thread
From: Brian Cain @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
Interrupts 3 through 5 are routed to guest mode when CCR:GIE and the
matching CCR:VV bit are set. Enter through GEVB rather than EVB,
record the pre-entry state in GSR and the return address in GELR, and
read the vector ID from the l2vic.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/reg_fields_def.h.inc | 7 +++
target/hexagon/cpu.c | 5 ++
target/hexagon/hex_interrupts.c | 91 ++++++++++++++++++++++++++---
3 files changed, 96 insertions(+), 7 deletions(-)
diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc
index d2c706d56b5..29497fbcc4d 100644
--- a/target/hexagon/reg_fields_def.h.inc
+++ b/target/hexagon/reg_fields_def.h.inc
@@ -136,6 +136,13 @@ DEF_REG_FIELD(CCR_VV1, 29, 1)
DEF_REG_FIELD(CCR_VV2, 30, 1)
DEF_REG_FIELD(CCR_VV3, 31, 1)
+/* GSR fields */
+DEF_REG_FIELD(GSR_CAUSE, 0, 16)
+DEF_REG_FIELD(GSR_CFI, 28, 1)
+DEF_REG_FIELD(GSR_SS, 29, 1)
+DEF_REG_FIELD(GSR_IE, 30, 1)
+DEF_REG_FIELD(GSR_UM, 31, 1)
+
/* ISDB ST fields */
DEF_REG_FIELD(ISDBST_WAITRUN, 24, 8)
DEF_REG_FIELD(ISDBST_ONOFF, 16, 8)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 0a677840bcb..efaf569c6f8 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -475,6 +475,11 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp)
error_setg(errp, "hexagon cpu requires 'tlb' link property to be set");
return;
}
+ if (!HEXAGON_CPU(dev)->l2vic) {
+ error_setg(errp,
+ "hexagon cpu requires 'l2vic' link property to be set");
+ return;
+ }
#endif
qemu_init_vcpu(cs);
diff --git a/target/hexagon/hex_interrupts.c b/target/hexagon/hex_interrupts.c
index 3534481da24..7dde1294b2e 100644
--- a/target/hexagon/hex_interrupts.c
+++ b/target/hexagon/hex_interrupts.c
@@ -11,6 +11,7 @@
#include "cpu_helper.h"
#include "exec/cpu-interrupt.h"
#include "hex_interrupts.h"
+#include "hw/intc/hex-l2vic.h"
#include "macros.h"
#include "sys_macros.h"
#include "system/cpus.h"
@@ -215,19 +216,79 @@ static void restore_state(CPUHexagonState *env, bool int_accepted)
}
}
+/*
+ * Direct-to-guest interrupts bypass the need for monitor-mode
+ * forwarding of interrupts into the guest OS.
+ */
+static bool int_should_dtg(CPUHexagonState *env, int int_num)
+{
+ uint32_t ccr = env->t_sreg[HEX_SREG_CCR];
+
+ switch (int_num) {
+ case 3:
+ if (!GET_FIELD(CCR_VV1, ccr)) {
+ return false;
+ }
+ break;
+ case 4:
+ if (!GET_FIELD(CCR_VV2, ccr)) {
+ return false;
+ }
+ break;
+ case 5:
+ if (!GET_FIELD(CCR_VV3, ccr)) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ return GET_FIELD(CCR_GIE, ccr);
+}
+
+static void guest_interrupt_entry(CPUHexagonState *env, uint32_t cause,
+ uint32_t event_pc)
+{
+ uint32_t old_ssr = env->t_sreg[HEX_SREG_SSR];
+ uint32_t new_ssr = old_ssr;
+ uint32_t ccr = env->t_sreg[HEX_SREG_CCR];
+ uint32_t gsr = 0;
+
+ gsr = deposit32(gsr, reg_field_info[GSR_CAUSE].offset,
+ reg_field_info[GSR_CAUSE].width, cause);
+ gsr = deposit32(gsr, reg_field_info[GSR_SS].offset,
+ reg_field_info[GSR_SS].width,
+ GET_SSR_FIELD(SSR_SS, old_ssr));
+ gsr = deposit32(gsr, reg_field_info[GSR_UM].offset,
+ reg_field_info[GSR_UM].width,
+ !GET_SSR_FIELD(SSR_GM, old_ssr));
+ gsr = deposit32(gsr, reg_field_info[GSR_IE].offset,
+ reg_field_info[GSR_IE].width,
+ GET_FIELD(CCR_GIE, ccr));
+ env->greg[HEX_GREG_GSR] = gsr;
+
+ fSET_FIELD(new_ssr, SSR_SS, 0);
+ fSET_FIELD(new_ssr, SSR_GM, 1);
+ env->t_sreg[HEX_SREG_SSR] = new_ssr;
+ hexagon_modify_ssr(env, new_ssr, old_ssr);
+
+ SET_SYSTEM_FIELD(env, HEX_SREG_CCR, CCR_GIE, 0);
+ env->greg[HEX_GREG_GELR] = event_pc;
+ env->gpr[HEX_REG_PC] = env->t_sreg[HEX_SREG_GEVB] |
+ (HEX_EVENT_INT0 << 2);
+}
+
static void hex_accept_int(CPUHexagonState *env, int int_num)
{
CPUState *cs = env_cpu(env);
HexagonCPU *cpu = env_archcpu(env);
- uint32_t evb =
- hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB,
- env->threadId);
const int exe_mode = get_exe_mode(env);
const bool in_wait_mode = exe_mode == HEX_EXE_MODE_WAIT;
+ uint32_t elr;
set_ipend_bit(env, int_num, 0);
set_iad_bit(env, int_num, 1);
- set_ssr_ex_cause(env, 1, HEX_CAUSE_INT0 | int_num);
cs->exception_index = HEX_EVENT_INT0 + int_num;
env->cause_code = HEX_EVENT_INT0 + int_num;
clear_pending_locks(env);
@@ -235,15 +296,31 @@ static void hex_accept_int(CPUHexagonState *env, int int_num)
qemu_log_mask(CPU_LOG_INT,
"%s: thread " TARGET_FMT_ld " resuming, exiting WAIT mode\n",
__func__, env->threadId);
- set_elr(env, env->wait_next_pc);
+ elr = env->wait_next_pc;
clear_wait_mode(env);
cs->halted = false;
} else if (env->k0_lock_state == HEX_LOCK_WAITING) {
g_assert_not_reached();
} else {
- set_elr(env, env->gpr[HEX_REG_PC]);
+ elr = env->gpr[HEX_REG_PC];
+ }
+
+ if (int_should_dtg(env, int_num)) {
+ int vic_group = int_num - 2;
+ uint32_t vid_packed = l2vic_read_vid(cpu->l2vic, vic_group / 2);
+ uint32_t vid = extract32(vid_packed,
+ (vic_group & 1) ? 16 : 0, 16);
+
+ guest_interrupt_entry(env, vid, elr);
+ } else {
+ uint32_t evb =
+ hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB,
+ env->threadId);
+
+ set_ssr_ex_cause(env, 1, HEX_CAUSE_INT0 | int_num);
+ set_elr(env, elr);
+ env->gpr[HEX_REG_PC] = evb | (cs->exception_index << 2);
}
- env->gpr[HEX_REG_PC] = evb | (cs->exception_index << 2);
if (get_ipend(env) == 0) {
restore_state(env, true);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 09/11] target/hexagon: implement direct-to-guest interrupt delivery
2026-09-01 17:33 ` [PATCH v2 09/11] target/hexagon: implement direct-to-guest interrupt delivery Brian Cain
@ 2026-09-01 18:26 ` Pierrick Bouvier
0 siblings, 0 replies; 20+ messages in thread
From: Pierrick Bouvier @ 2026-09-01 18:26 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Laurent Vivier, Helge Deller
On 9/1/2026 10:33 AM, Brian Cain wrote:
> Interrupts 3 through 5 are routed to guest mode when CCR:GIE and the
> matching CCR:VV bit are set. Enter through GEVB rather than EVB,
> record the pre-entry state in GSR and the return address in GELR, and
> read the vector ID from the l2vic.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> target/hexagon/reg_fields_def.h.inc | 7 +++
> target/hexagon/cpu.c | 5 ++
> target/hexagon/hex_interrupts.c | 91 ++++++++++++++++++++++++++---
> 3 files changed, 96 insertions(+), 7 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 10/11] target/hexagon: fix iassign{r, w} to cover all threads
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (8 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 09/11] target/hexagon: implement direct-to-guest interrupt delivery Brian Cain
@ 2026-09-01 17:33 ` Brian Cain via qemu development
2026-09-01 17:33 ` [PATCH v2 11/11] tests/functional/hexagon: update to v0.2.12, +test_{interrupts, sys_regs} Brian Cain via qemu development
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain via qemu development @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
iassignw/iassignr were filtering CPU_FOREACH by MODECTL_E, the
thread-enabled mask. That mask reflects whether a thread has been
started, not whether it exists, so IMASK writes/reads never reached
threads that hadn't been started yet, leaving their IMASK stale or
unreadable. Drop the MODECTL_E filtering and operate on every thread.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
target/hexagon/op_helper.c | 42 +++++++++-----------------------------
1 file changed, 10 insertions(+), 32 deletions(-)
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index df31ed2488a..e19c3c91085 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -1637,61 +1637,39 @@ void HELPER(cswi)(CPUHexagonState *env, uint32_t mask)
void HELPER(iassignw)(CPUHexagonState *env, uint32_t src)
{
- uint32_t modectl;
- uint32_t thread_enabled_mask;
CPUState *cpu;
- HexagonCPU *hex_cpu;
BQL_LOCK_GUARD();
- hex_cpu = env_archcpu(env);
- modectl = hex_cpu->globalregs ?
- hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL,
- env->threadId) : 0;
- thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
CPU_FOREACH(cpu) {
CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env);
- uint32_t thread_id_mask = 0x1 << thread_env->threadId;
- if (thread_enabled_mask & thread_id_mask) {
- uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
- uint32_t intbitpos = (src >> 16) & 0xF;
- uint32_t val = (src >> thread_env->threadId) & 0x1;
- imask = deposit32(imask, intbitpos, 1, val);
- thread_env->t_sreg[HEX_SREG_IMASK] = imask;
+ uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
+ uint32_t intbitpos = (src >> 16) & 0xF;
+ uint32_t val = (src >> thread_env->threadId) & 0x1;
+ imask = deposit32(imask, intbitpos, 1, val);
+ thread_env->t_sreg[HEX_SREG_IMASK] = imask;
- qemu_log_mask(CPU_LOG_INT, "%s: thread " TARGET_FMT_ld
- ", new imask 0x%" PRIx32 "\n", __func__,
- thread_env->threadId, imask);
- }
+ qemu_log_mask(CPU_LOG_INT, "%s: thread " TARGET_FMT_ld
+ ", new imask 0x%" PRIx32 "\n", __func__,
+ thread_env->threadId, imask);
}
hex_interrupt_update(env);
}
uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src)
{
- uint32_t modectl;
- uint32_t thread_enabled_mask;
uint32_t intbitpos;
uint32_t dest_reg;
CPUState *cpu;
- HexagonCPU *hex_cpu;
BQL_LOCK_GUARD();
- hex_cpu = env_archcpu(env);
- modectl = hex_cpu->globalregs ?
- hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL,
- env->threadId) : 0;
- thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
/* src fields are in same position as modectl, but mean different things */
intbitpos = GET_FIELD(MODECTL_W, src);
dest_reg = 0;
CPU_FOREACH(cpu) {
CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env);
- uint32_t thread_id_mask = 0x1 << thread_env->threadId;
- if (thread_enabled_mask & thread_id_mask) {
- uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
- dest_reg |= ((imask >> intbitpos) & 0x1) << thread_env->threadId;
- }
+ uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
+ dest_reg |= ((imask >> intbitpos) & 0x1) << thread_env->threadId;
}
return dest_reg;
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 11/11] tests/functional/hexagon: update to v0.2.12, +test_{interrupts, sys_regs}
2026-09-01 17:33 [PATCH v2 00/11] Hexagon: exception, interrupt, system reg fixes Brian Cain
` (9 preceding siblings ...)
2026-09-01 17:33 ` [PATCH v2 10/11] target/hexagon: fix iassign{r, w} to cover all threads Brian Cain via qemu development
@ 2026-09-01 17:33 ` Brian Cain via qemu development
10 siblings, 0 replies; 20+ messages in thread
From: Brian Cain via qemu development @ 2026-09-01 17:33 UTC (permalink / raw)
To: qemu-devel; +Cc: Pierrick Bouvier, Brian Cain, Laurent Vivier, Helge Deller
test_interrupts also now passes in full following the iassign{r,w}
fix, so enable it alongside the other arch tests.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
tests/functional/hexagon/test_arch_tests.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
index 0834398c3b1..8e71386c186 100755
--- a/tests/functional/hexagon/test_arch_tests.py
+++ b/tests/functional/hexagon/test_arch_tests.py
@@ -21,8 +21,8 @@ class ArchTestsUart(QemuSystemTest):
ASSET_TARBALL = Asset(
"https://github.com/qualcomm/qemu-hexagon-testing/releases/"
- "download/v0.2.5/arch_tests_uart.tar.gz",
- "edb4f37b877a3a72a0e10920477458a43b40045d34398fee8cb763fefd342f4f",
+ "download/v0.2.12/arch_tests_uart.tar.gz",
+ "871a339bf78cac0ebaf1b2509bfcd5b249ad8190be33e0cf848283b2f6915323",
)
def run_uart_test(self, test_name: str,
@@ -58,6 +58,10 @@ def test_int_steering(self) -> None:
"""
self.run_uart_test("test_int_steering")
+ def test_interrupts(self) -> None:
+ """Tests interrupt delivery."""
+ self.run_uart_test("test_interrupts")
+
def test_cache(self) -> None:
"""Tests cache operations: dckill/ickill, l2kill, dczeroa,
dccleaninva, cache disable/enable, barriers, and dcinva/dccleana.
@@ -70,6 +74,10 @@ def test_l2vic(self) -> None:
"""
self.run_uart_test("test_l2vic")
+ def test_sys_regs(self) -> None:
+ """Tests system registers."""
+ self.run_uart_test("test_sys_regs")
+
def test_threads(self) -> None:
"""Tests hardware thread management: start/stop, MODECTL state,
per-thread HTID, shared memory, wait/resume, STID priority, and
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread