* [PATCH v4 00/18] Add hexagon l2vic, qtimer devices
@ 2026-07-30 1:28 Brian Cain
2026-07-30 1:28 ` [PATCH v4 01/18] hw/hexagon: add hex-subsys Brian Cain
` (17 more replies)
0 siblings, 18 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
The previously independent threads for l2vic and qtimer are now combined,
which seems to make the most sense because qtimer testing effectively
depends on l2vic.
* split the hex-subsys refactor into 5 patches, one per field moved.
* add virtio_mmio[VIRTIO_DEV_COUNT] to HexagonVirtMachineState and record
each transport
l2vic changes in v4:
* INT_STATUSn and SOFT_INTn get L2VIC_OP_NONE; SOFT_INTn no longer
writes int_enable as a side effect.
* SOFT_INTn now walks every set bit instead of only the lowest one.
* get_vid_group() rejects an out-of-range vid with LOG_GUEST_ERROR.
* fastl2vic region gains a read handler returning 0 (was write-only).
* Comments rewritten to describe the INT_GRPn interleave and VID packing.
qtimer changes in v4:
* hex_timer_rearm() uses muldiv64_round_up() instead of muldiv64()+1 ns.
* AC CNTFRQ write of 0 is rejected with LOG_GUEST_ERROR.
* realize() validates freq_hz != 0 and frame_stride a nonzero power of 2.
* Frame decode drops the redundant offset mask now that stride is
guaranteed to be a power of two.
* Reset clears cntpl0acr and deasserts the frame IRQ.
* vmstate adds freq_hz and secure.
wiring changes in v4:
* hex_subsys_add_cpu() splits CPU parenting/link wiring from realize;
machines now realize the cluster before the CPUs.
* qtimer AC region was unintentionally unmapped, now mapped at m_cfg->csr_base.
test changes in v4:
* l2vic-test: adds VID_GRP_0..3 readback and an l2vic_set_vid_group()
helper; VID steering now checked per output line rather than by
writing INT_GRPn_1..3 directly.
* l2vic-test: multiple-pending case drains in a loop instead of a single
clear, and restores INT_GRPn_0/INT_TYPEn between blocks.
* qct-qtimer-test: adds test_qtimer_ac_region() covering CNTFRQ (incl.
the ignored 0 write), CNTSR, CNTTID_0 and CNTACR frame 0.
Lacking R-b:
- hw/hexagon: add hex-subsys
- hw/hexagon: move VTCM to the common machine state
- hw/hexagon: move global registers to hex-subsys
- hw/hexagon: move the TLB to hex-subsys
- hw/hexagon: group the CPUs in a cluster
- hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes
- hw/timer: Add QCT QTimer device model
- hw/hexagon: connect qtimer device
- tests/qtest: add qct-qtimer qtest
- tests/functional/hexagon: enable more arch_tests cases
l2vic v3: https://lore.kernel.org/qemu-devel/20260724183648.190531-1-brian.cain@oss.qualcomm.com/
l2vic v2: https://lore.kernel.org/qemu-devel/20260715185701.1287176-1-brian.cain@oss.qualcomm.com/
l2vic v1: https://lore.kernel.org/qemu-devel/20260714155126.3509544-1-brian.cain@oss.qualcomm.com/
qtimer v1: https://lore.kernel.org/qemu-devel/20260718013241.1550124-1-brian.cain@oss.qualcomm.com/
Brian Cain (17):
hw/hexagon: add hex-subsys
hw/hexagon: move VTCM to the common machine state
hw/hexagon: move global registers to hex-subsys
hw/hexagon: move the TLB to hex-subsys
hw/hexagon: group the CPUs in a cluster
bitops.h: Add find_first_bit32()
hw/hexagon: extract get_reg_value/set_reg_value stubs in globalreg
hw/hexagon: connect l2vic device
hw/hexagon/virt: instantiate virtio-mmio transports
hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT
nodes
hw/hexagon/virt: connect pl011 UART interrupt
tests/qtest: add L2VIC qtest
tests/functional/hexagon: add arch_tests functional test
hw/timer: Add QCT QTimer device model
hw/hexagon: connect qtimer device
tests/qtest: add qct-qtimer qtest
tests/functional/hexagon: enable more arch_tests cases
Sid Manning (1):
hw/intc: Add l2vic interrupt controller
MAINTAINERS | 7 +
docs/devel/hexagon-l2vic.rst | 55 ++
docs/devel/index-internals.rst | 1 +
include/hw/hexagon/hex-subsys.h | 32 +
include/hw/hexagon/hexagon.h | 6 +
include/hw/hexagon/hexagon_globalreg.h | 8 +
include/hw/hexagon/virt.h | 4 +-
include/hw/intc/hex-l2vic.h | 61 ++
include/hw/timer/qct-qtimer.h | 43 ++
include/qemu/bitops.h | 30 +-
target/hexagon/cpu.h | 2 +
hw/hexagon/hex-subsys.c | 176 +++++
hw/hexagon/hexagon_dsp.c | 45 +-
hw/hexagon/hexagon_globalreg.c | 65 +-
hw/hexagon/virt.c | 130 ++--
hw/intc/hex-l2vic.c | 556 ++++++++++++++++
hw/timer/qct-qtimer.c | 681 ++++++++++++++++++++
target/hexagon/cpu.c | 2 +
target/hexagon/op_helper.c | 16 +-
tests/qtest/l2vic-test.c | 249 +++++++
tests/qtest/qct-qtimer-test.c | 269 ++++++++
hw/hexagon/Kconfig | 3 +
hw/hexagon/meson.build | 1 +
hw/intc/Kconfig | 3 +
hw/intc/meson.build | 2 +
hw/intc/trace-events | 4 +
hw/timer/Kconfig | 3 +
hw/timer/meson.build | 2 +
hw/timer/trace-events | 5 +
tests/functional/hexagon/test_arch_tests.py | 38 ++
tests/qtest/meson.build | 2 +-
31 files changed, 2411 insertions(+), 90 deletions(-)
create mode 100644 docs/devel/hexagon-l2vic.rst
create mode 100644 include/hw/hexagon/hex-subsys.h
create mode 100644 include/hw/intc/hex-l2vic.h
create mode 100644 include/hw/timer/qct-qtimer.h
create mode 100644 hw/hexagon/hex-subsys.c
create mode 100644 hw/intc/hex-l2vic.c
create mode 100644 hw/timer/qct-qtimer.c
create mode 100644 tests/qtest/l2vic-test.c
create mode 100644 tests/qtest/qct-qtimer-test.c
--
2.34.1
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH v4 01/18] hw/hexagon: add hex-subsys
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:28 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state Brian Cain
` (16 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
The virt and DSP machine models build the same core subsystem, let's
abstract out that part. Start with the DDR and config table ROM setup.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hex-subsys.h | 17 +++++++++++++++++
hw/hexagon/hex-subsys.c | 32 ++++++++++++++++++++++++++++++++
hw/hexagon/hexagon_dsp.c | 17 ++---------------
hw/hexagon/virt.c | 14 ++------------
hw/hexagon/meson.build | 1 +
5 files changed, 54 insertions(+), 27 deletions(-)
create mode 100644 include/hw/hexagon/hex-subsys.h
create mode 100644 hw/hexagon/hex-subsys.c
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h
new file mode 100644
index 00000000000..6bcde303f2e
--- /dev/null
+++ b/include/hw/hexagon/hex-subsys.h
@@ -0,0 +1,17 @@
+/*
+ * Hexagon subsystem helpers shared between the machine models.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_HEX_SUBSYS_H
+#define HW_HEXAGON_HEX_SUBSYS_H
+
+#include "hw/hexagon/hexagon.h"
+
+/* Create the subsystem shared by every Hexagon machine. */
+void hex_subsys_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg);
+
+#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
new file mode 100644
index 00000000000..d146ee0c075
--- /dev/null
+++ b/hw/hexagon/hex-subsys.c
@@ -0,0 +1,32 @@
+/*
+ * Hexagon subsystem helpers shared between the machine models.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/hexagon/hex-subsys.h"
+#include "hw/core/loader.h"
+#include "system/address-spaces.h"
+
+void hex_subsys_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg)
+{
+ MachineState *machine = MACHINE(hms);
+ MemoryRegion *sysmem = get_system_memory();
+
+ /* Main DDR at the reset vector. */
+ memory_region_init_ram(&hms->ram, NULL, "ddr.ram", machine->ram_size,
+ &error_fatal);
+ memory_region_add_subregion(sysmem, 0x0, &hms->ram);
+
+ /* Config-table ROM and the blob that backs it. */
+ memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom",
+ sizeof(m_cfg->cfgtable), &error_fatal);
+ memory_region_add_subregion(sysmem, m_cfg->cfgbase, &hms->cfgtable_rom);
+ rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
+ sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
+ &address_space_memory);
+}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index aa493993229..f94f7fd4e11 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -14,6 +14,7 @@
#include "hw/core/boards.h"
#include "hw/core/qdev-properties.h"
#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/hex-subsys.h"
#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
@@ -108,7 +109,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
{
HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine);
HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine);
- MemoryRegion *address_space;
DeviceState *glob_regs_dev;
DeviceState *tlb_dev;
@@ -120,16 +120,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
machine->enable_graphics = 0;
- address_space = get_system_memory();
-
- memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom",
- sizeof(m_cfg->cfgtable), &error_fatal);
- memory_region_add_subregion(address_space, m_cfg->cfgbase,
- &hms->cfgtable_rom);
-
- memory_region_init_ram(&hms->ram, NULL, "ddr.ram",
- machine->ram_size, &error_fatal);
- memory_region_add_subregion(address_space, 0x0, &hms->ram);
+ hex_subsys_create(hms, m_cfg);
glob_regs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
object_property_add_child(OBJECT(machine), "global-regs",
@@ -162,10 +153,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
OBJECT(tlb_dev), &error_fatal);
qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
}
-
- rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
- sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
- &address_space_memory);
}
static void init_mc(MachineClass *mc)
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index b7504725026..ad0bc4f1329 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -13,6 +13,7 @@
#include "hw/core/clock.h"
#include "hw/core/sysbus-fdt.h"
#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/hex-subsys.h"
#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
@@ -244,9 +245,7 @@ static void virt_init(MachineState *ms)
vms->apb_clk = clock_new(OBJECT(ms), "apb-pclk");
clock_set_hz(vms->apb_clk, 24000000);
- memory_region_init_ram(&vms->parent_obj.ram, NULL, "ddr.ram",
- ms->ram_size, &error_fatal);
- memory_region_add_subregion(vms->sys, 0x0, &vms->parent_obj.ram);
+ hex_subsys_create(&vms->parent_obj, m_cfg);
if (m_cfg->l2tcm_size) {
memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size,
@@ -255,11 +254,6 @@ static void virt_init(MachineState *ms)
&vms->tcm);
}
- memory_region_init_rom(&vms->parent_obj.cfgtable_rom, NULL,
- "config_table.rom", sizeof(m_cfg->cfgtable),
- &error_fatal);
- memory_region_add_subregion(vms->sys, m_cfg->cfgbase,
- &vms->parent_obj.cfgtable_rom);
fdt_add_hvx(vms, m_cfg);
gsregs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
@@ -302,10 +296,6 @@ static void virt_init(MachineState *ms)
clk_phandle = fdt_add_clocks(vms);
fdt_add_uart(vms, VIRT_UART0, clk_phandle);
- rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
- sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
- &address_space_memory);
-
hexagon_load_fdt(vms);
}
diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build
index bade3a32921..720a5d54dcc 100644
--- a/hw/hexagon/meson.build
+++ b/hw/hexagon/meson.build
@@ -1,6 +1,7 @@
hexagon_ss = ss.source_set()
hexagon_ss.add(files('hexagon_tlb.c'))
hexagon_ss.add(files('hexagon_globalreg.c'))
+hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hex-subsys.c'))
hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c'))
hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c'))
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
2026-07-30 1:28 ` [PATCH v4 01/18] hw/hexagon: add hex-subsys Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:29 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys Brian Cain
` (15 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
The VTCM is described by the config table, so every machine can set it
up the same way.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hexagon.h | 1 +
include/hw/hexagon/virt.h | 1 -
hw/hexagon/hex-subsys.c | 8 ++++++++
hw/hexagon/virt.c | 5 -----
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 1034b09c2ac..9c73cadb16d 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -156,6 +156,7 @@ struct HexagonCommonMachineState {
MemoryRegion ram;
MemoryRegion cfgtable_rom;
+ MemoryRegion vtcm;
};
#endif
diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h
index fcb47762194..cf0cd2f8f22 100644
--- a/include/hw/hexagon/virt.h
+++ b/include/hw/hexagon/virt.h
@@ -17,7 +17,6 @@ struct HexagonVirtMachineState {
int fdt_size;
MemoryRegion *sys;
MemoryRegion tcm;
- MemoryRegion vtcm;
MemoryRegion bios;
Clock *apb_clk;
};
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index d146ee0c075..0c20cf02e7d 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -29,4 +29,12 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
&address_space_memory);
+
+ if (m_cfg->cfgtable.vtcm_size_kb > 0) {
+ memory_region_init_ram(&hms->vtcm, NULL, "vtcm.ram",
+ m_cfg->cfgtable.vtcm_size_kb * 1024,
+ &error_fatal);
+ memory_region_add_subregion(sysmem, m_cfg->cfgtable.vtcm_base << 16,
+ &hms->vtcm);
+ }
}
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index ad0bc4f1329..53eaafaf85a 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -75,11 +75,6 @@ static void fdt_add_hvx(HexagonVirtMachineState *vms,
const MachineState *ms = MACHINE(vms);
uint32_t vtcm_size_bytes = m_cfg->cfgtable.vtcm_size_kb * 1024;
if (vtcm_size_bytes > 0) {
- memory_region_init_ram(&vms->vtcm, NULL, "vtcm.ram", vtcm_size_bytes,
- &error_fatal);
- memory_region_add_subregion(vms->sys, m_cfg->cfgtable.vtcm_base << 16,
- &vms->vtcm);
-
qemu_fdt_add_subnode(ms->fdt, "/soc/vtcm");
qemu_fdt_setprop_string(ms->fdt, "/soc/vtcm", "compatible",
"qcom,hexagon_vtcm");
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
2026-07-30 1:28 ` [PATCH v4 01/18] hw/hexagon: add hex-subsys Brian Cain
2026-07-30 1:28 ` [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:29 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 04/18] hw/hexagon: move the TLB " Brian Cain
` (14 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Both machines create the global register device the same way, so let
hex-subsys own it and link it to each CPU as it is realized.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hex-subsys.h | 6 +++++-
include/hw/hexagon/hexagon.h | 1 +
hw/hexagon/hex-subsys.c | 29 ++++++++++++++++++++++++++++-
hw/hexagon/hexagon_dsp.c | 15 ++-------------
hw/hexagon/virt.c | 23 +++++------------------
5 files changed, 41 insertions(+), 33 deletions(-)
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h
index 6bcde303f2e..087c105cfa4 100644
--- a/include/hw/hexagon/hex-subsys.h
+++ b/include/hw/hexagon/hex-subsys.h
@@ -9,9 +9,13 @@
#define HW_HEXAGON_HEX_SUBSYS_H
#include "hw/hexagon/hexagon.h"
+#include "hw/core/qdev.h"
/* Create the subsystem shared by every Hexagon machine. */
void hex_subsys_create(HexagonCommonMachineState *hms,
- const struct hexagon_machine_config *m_cfg);
+ const struct hexagon_machine_config *m_cfg, Rev_t rev);
+
+/* Realize a CPU into the subsystem. */
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 9c73cadb16d..ef5700082cc 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -157,6 +157,7 @@ struct HexagonCommonMachineState {
MemoryRegion ram;
MemoryRegion cfgtable_rom;
MemoryRegion vtcm;
+ DeviceState *glob_regs;
};
#endif
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index 0c20cf02e7d..6789b6bb23d 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -8,11 +8,29 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/hexagon/hex-subsys.h"
+#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/core/loader.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
#include "system/address-spaces.h"
+static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg,
+ Rev_t rev)
+{
+ DeviceState *glob_regs = qdev_new(TYPE_HEXAGON_GLOBALREG);
+
+ object_property_add_child(OBJECT(hms), "global-regs", OBJECT(glob_regs));
+ qdev_prop_set_uint64(glob_regs, "config-table-addr", m_cfg->cfgbase);
+ qdev_prop_set_uint32(glob_regs, "dsp-rev", rev);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs), &error_fatal);
+
+ return glob_regs;
+}
+
void hex_subsys_create(HexagonCommonMachineState *hms,
- const struct hexagon_machine_config *m_cfg)
+ const struct hexagon_machine_config *m_cfg, Rev_t rev)
{
MachineState *machine = MACHINE(hms);
MemoryRegion *sysmem = get_system_memory();
@@ -37,4 +55,13 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
memory_region_add_subregion(sysmem, m_cfg->cfgtable.vtcm_base << 16,
&hms->vtcm);
}
+
+ hms->glob_regs = globalreg_create(hms, m_cfg, rev);
+}
+
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
+{
+ object_property_set_link(OBJECT(cpu), "global-regs",
+ OBJECT(hms->glob_regs), &error_fatal);
+ qdev_realize_and_unref(cpu, NULL, &error_fatal);
}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index f94f7fd4e11..8599f5883bf 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -15,7 +15,6 @@
#include "hw/core/qdev-properties.h"
#include "hw/hexagon/hexagon.h"
#include "hw/hexagon/hex-subsys.h"
-#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
#include "qapi/error.h"
@@ -109,7 +108,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
{
HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine);
HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine);
- DeviceState *glob_regs_dev;
DeviceState *tlb_dev;
memset(&hexagon_binfo, 0, sizeof(hexagon_binfo));
@@ -120,14 +118,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
machine->enable_graphics = 0;
- hex_subsys_create(hms, m_cfg);
-
- glob_regs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
- object_property_add_child(OBJECT(machine), "global-regs",
- OBJECT(glob_regs_dev));
- qdev_prop_set_uint64(glob_regs_dev, "config-table-addr", m_cfg->cfgbase);
- qdev_prop_set_uint32(glob_regs_dev, "dsp-rev", rev);
- sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs_dev), &error_fatal);
+ hex_subsys_create(hms, m_cfg, rev);
tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
object_property_add_child(OBJECT(machine), "tlb", OBJECT(tlb_dev));
@@ -147,11 +138,9 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
if (i == 0) {
hexagon_init_bootstrap(dms, cpu);
}
- object_property_set_link(OBJECT(cpu), "global-regs",
- OBJECT(glob_regs_dev), &error_fatal);
object_property_set_link(OBJECT(cpu), "tlb",
OBJECT(tlb_dev), &error_fatal);
- qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
+ hex_subsys_realize_cpu(hms, DEVICE(cpu));
}
}
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index 53eaafaf85a..290783d511f 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -14,7 +14,6 @@
#include "hw/core/sysbus-fdt.h"
#include "hw/hexagon/hexagon.h"
#include "hw/hexagon/hex-subsys.h"
-#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
@@ -226,9 +225,7 @@ static void virt_init(MachineState *ms)
{
HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms);
const struct hexagon_machine_config *m_cfg = &v68n_1024;
- DeviceState *gsregs_dev;
DeviceState *tlb_dev;
- DeviceState *cpu0;
int32_t clk_phandle;
create_fdt(vms);
@@ -240,7 +237,7 @@ static void virt_init(MachineState *ms)
vms->apb_clk = clock_new(OBJECT(ms), "apb-pclk");
clock_set_hz(vms->apb_clk, 24000000);
- hex_subsys_create(&vms->parent_obj, m_cfg);
+ hex_subsys_create(&vms->parent_obj, m_cfg, v68_rev);
if (m_cfg->l2tcm_size) {
memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size,
@@ -251,42 +248,32 @@ static void virt_init(MachineState *ms)
fdt_add_hvx(vms, m_cfg);
- gsregs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
- object_property_add_child(OBJECT(ms), "global-regs", OBJECT(gsregs_dev));
- qdev_prop_set_uint64(gsregs_dev, "config-table-addr", m_cfg->cfgbase);
- qdev_prop_set_uint32(gsregs_dev, "dsp-rev", v68_rev);
- sysbus_realize_and_unref(SYS_BUS_DEVICE(gsregs_dev), &error_fatal);
-
tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
object_property_add_child(OBJECT(ms), "tlb", OBJECT(tlb_dev));
qdev_prop_set_uint32(tlb_dev, "num-entries",
m_cfg->cfgtable.jtlb_size_entries);
sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal);
- cpu0 = NULL;
for (int i = 0; i < ms->smp.cpus; i++) {
HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type));
qemu_register_reset(do_cpu_reset, cpu);
if (i == 0) {
- cpu0 = DEVICE(cpu);
if (ms->kernel_filename) {
uint64_t entry = load_kernel(vms);
- qdev_prop_set_uint32(cpu0, "exec-start-addr", entry);
+ qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry);
} else if (ms->firmware) {
uint64_t entry = load_bios(vms);
- qdev_prop_set_uint32(cpu0, "exec-start-addr", entry);
+ qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry);
}
}
qdev_prop_set_uint32(DEVICE(cpu), "htid", i);
qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0));
- object_property_set_link(OBJECT(cpu), "global-regs",
- OBJECT(gsregs_dev), &error_fatal);
object_property_set_link(OBJECT(cpu), "tlb",
OBJECT(tlb_dev), &error_fatal);
-
- qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
+ hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpu));
}
+
fdt_add_cpu_nodes(vms);
clk_phandle = fdt_add_clocks(vms);
fdt_add_uart(vms, VIRT_UART0, clk_phandle);
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 04/18] hw/hexagon: move the TLB to hex-subsys
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (2 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:30 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster Brian Cain
` (13 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
The TLB device is sized from the config table, so both machines create
it identically.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hexagon.h | 1 +
hw/hexagon/hex-subsys.c | 16 ++++++++++++++++
hw/hexagon/hexagon_dsp.c | 10 ----------
hw/hexagon/virt.c | 10 ----------
4 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index ef5700082cc..6edd93f478a 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -158,6 +158,7 @@ struct HexagonCommonMachineState {
MemoryRegion cfgtable_rom;
MemoryRegion vtcm;
DeviceState *glob_regs;
+ DeviceState *tlb;
};
#endif
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index 6789b6bb23d..ac7e29604eb 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -9,6 +9,7 @@
#include "qapi/error.h"
#include "hw/hexagon/hex-subsys.h"
#include "hw/hexagon/hexagon_globalreg.h"
+#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev.h"
@@ -29,6 +30,18 @@ static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
return glob_regs;
}
+static DeviceState *tlb_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg)
+{
+ DeviceState *tlb = qdev_new(TYPE_HEXAGON_TLB);
+
+ object_property_add_child(OBJECT(hms), "tlb", OBJECT(tlb));
+ qdev_prop_set_uint32(tlb, "num-entries", m_cfg->cfgtable.jtlb_size_entries);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb), &error_fatal);
+
+ return tlb;
+}
+
void hex_subsys_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg, Rev_t rev)
{
@@ -57,11 +70,14 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
}
hms->glob_regs = globalreg_create(hms, m_cfg, rev);
+ hms->tlb = tlb_create(hms, m_cfg);
}
void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
{
object_property_set_link(OBJECT(cpu), "global-regs",
OBJECT(hms->glob_regs), &error_fatal);
+ object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb),
+ &error_fatal);
qdev_realize_and_unref(cpu, NULL, &error_fatal);
}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index 8599f5883bf..25e8550fba2 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -15,7 +15,6 @@
#include "hw/core/qdev-properties.h"
#include "hw/hexagon/hexagon.h"
#include "hw/hexagon/hex-subsys.h"
-#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
@@ -108,7 +107,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
{
HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine);
HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine);
- DeviceState *tlb_dev;
memset(&hexagon_binfo, 0, sizeof(hexagon_binfo));
if (machine->kernel_filename) {
@@ -120,12 +118,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
hex_subsys_create(hms, m_cfg, rev);
- tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
- object_property_add_child(OBJECT(machine), "tlb", OBJECT(tlb_dev));
- qdev_prop_set_uint32(tlb_dev, "num-entries",
- m_cfg->cfgtable.jtlb_size_entries);
- sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal);
-
for (int i = 0; i < machine->smp.cpus; i++) {
HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type));
qemu_register_reset(do_cpu_reset, cpu);
@@ -138,8 +130,6 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
if (i == 0) {
hexagon_init_bootstrap(dms, cpu);
}
- object_property_set_link(OBJECT(cpu), "tlb",
- OBJECT(tlb_dev), &error_fatal);
hex_subsys_realize_cpu(hms, DEVICE(cpu));
}
}
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index 290783d511f..b9e0bbb704c 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -14,7 +14,6 @@
#include "hw/core/sysbus-fdt.h"
#include "hw/hexagon/hexagon.h"
#include "hw/hexagon/hex-subsys.h"
-#include "hw/hexagon/hexagon_tlb.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-clock.h"
@@ -225,7 +224,6 @@ static void virt_init(MachineState *ms)
{
HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms);
const struct hexagon_machine_config *m_cfg = &v68n_1024;
- DeviceState *tlb_dev;
int32_t clk_phandle;
create_fdt(vms);
@@ -248,12 +246,6 @@ static void virt_init(MachineState *ms)
fdt_add_hvx(vms, m_cfg);
- tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
- object_property_add_child(OBJECT(ms), "tlb", OBJECT(tlb_dev));
- qdev_prop_set_uint32(tlb_dev, "num-entries",
- m_cfg->cfgtable.jtlb_size_entries);
- sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal);
-
for (int i = 0; i < ms->smp.cpus; i++) {
HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type));
qemu_register_reset(do_cpu_reset, cpu);
@@ -269,8 +261,6 @@ static void virt_init(MachineState *ms)
}
qdev_prop_set_uint32(DEVICE(cpu), "htid", i);
qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0));
- object_property_set_link(OBJECT(cpu), "tlb",
- OBJECT(tlb_dev), &error_fatal);
hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpu));
}
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (3 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 04/18] hw/hexagon: move the TLB " Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:34 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 06/18] bitops.h: Add find_first_bit32() Brian Cain
` (12 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
The CPUs are now grouped in a TYPE_CPU_CLUSTER.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hex-subsys.h | 12 +++++++++++-
include/hw/hexagon/hexagon.h | 1 +
hw/hexagon/hex-subsys.c | 30 +++++++++++++++++++++++++++++-
hw/hexagon/hexagon_dsp.c | 11 ++++++++++-
hw/hexagon/virt.c | 11 ++++++++++-
hw/hexagon/Kconfig | 1 +
6 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h
index 087c105cfa4..6d0d5e1da86 100644
--- a/include/hw/hexagon/hex-subsys.h
+++ b/include/hw/hexagon/hex-subsys.h
@@ -15,7 +15,17 @@
void hex_subsys_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg, Rev_t rev);
-/* Realize a CPU into the subsystem. */
+/*
+ * Parent a CPU into the subsystem's cluster and wire its links. Call for
+ * every CPU before hex_subsys_realize_cluster(), then realize each CPU with
+ * hex_subsys_realize_cpu().
+ */
+void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
+
+/* Realize the CPU cluster, once all CPUs have been parented into it. */
+void hex_subsys_realize_cluster(HexagonCommonMachineState *hms);
+
+/* Realize a CPU previously parented via hex_subsys_add_cpu(). */
void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 6edd93f478a..31669a829f8 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -157,6 +157,7 @@ struct HexagonCommonMachineState {
MemoryRegion ram;
MemoryRegion cfgtable_rom;
MemoryRegion vtcm;
+ DeviceState *cluster;
DeviceState *glob_regs;
DeviceState *tlb;
};
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index ac7e29604eb..6fad6fe0a4d 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -10,6 +10,7 @@
#include "hw/hexagon/hex-subsys.h"
#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/cpu/cluster.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev.h"
@@ -42,6 +43,16 @@ static DeviceState *tlb_create(HexagonCommonMachineState *hms,
return tlb;
}
+static DeviceState *cluster_create(HexagonCommonMachineState *hms)
+{
+ DeviceState *cluster = qdev_new(TYPE_CPU_CLUSTER);
+
+ object_property_add_child(OBJECT(hms), "cluster", OBJECT(cluster));
+ qdev_prop_set_uint32(cluster, "cluster-id", 0);
+
+ return cluster;
+}
+
void hex_subsys_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg, Rev_t rev)
{
@@ -69,15 +80,32 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
&hms->vtcm);
}
+ hms->cluster = cluster_create(hms);
hms->glob_regs = globalreg_create(hms, m_cfg, rev);
hms->tlb = tlb_create(hms, m_cfg);
}
-void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
+void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
{
+ object_property_add_child(OBJECT(hms->cluster), "cpu[*]", OBJECT(cpu));
object_property_set_link(OBJECT(cpu), "global-regs",
OBJECT(hms->glob_regs), &error_fatal);
object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb),
&error_fatal);
+}
+
+void hex_subsys_realize_cluster(HexagonCommonMachineState *hms)
+{
+ /*
+ * The cluster must be realized after its CPUs have been parented into it
+ * (see hex_subsys_add_cpu()) but before any CPU is itself realized, since
+ * qdev_realize_and_unref() on a CPU latches cluster_index into the TCG
+ * cflags at that point.
+ */
+ qdev_realize_and_unref(hms->cluster, NULL, &error_fatal);
+}
+
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
+{
qdev_realize_and_unref(cpu, NULL, &error_fatal);
}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index 25e8550fba2..1db5d681665 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -118,6 +118,8 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
hex_subsys_create(hms, m_cfg, rev);
+ g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, machine->smp.cpus);
+
for (int i = 0; i < machine->smp.cpus; i++) {
HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type));
qemu_register_reset(do_cpu_reset, cpu);
@@ -130,7 +132,14 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
if (i == 0) {
hexagon_init_bootstrap(dms, cpu);
}
- hex_subsys_realize_cpu(hms, DEVICE(cpu));
+ hex_subsys_add_cpu(hms, DEVICE(cpu));
+ cpus[i] = cpu;
+ }
+
+ hex_subsys_realize_cluster(hms);
+
+ for (int i = 0; i < machine->smp.cpus; i++) {
+ hex_subsys_realize_cpu(hms, DEVICE(cpus[i]));
}
}
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index b9e0bbb704c..dc1d5b6aa24 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -246,6 +246,8 @@ static void virt_init(MachineState *ms)
fdt_add_hvx(vms, m_cfg);
+ g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, ms->smp.cpus);
+
for (int i = 0; i < ms->smp.cpus; i++) {
HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type));
qemu_register_reset(do_cpu_reset, cpu);
@@ -261,7 +263,14 @@ static void virt_init(MachineState *ms)
}
qdev_prop_set_uint32(DEVICE(cpu), "htid", i);
qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0));
- hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpu));
+ hex_subsys_add_cpu(&vms->parent_obj, DEVICE(cpu));
+ cpus[i] = cpu;
+ }
+
+ hex_subsys_realize_cluster(&vms->parent_obj);
+
+ for (int i = 0; i < ms->smp.cpus; i++) {
+ hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpus[i]));
}
fdt_add_cpu_nodes(vms);
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index 52065ab3b22..121c548bbb9 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -2,6 +2,7 @@ config HEX_DSP
bool
default y
depends on HEXAGON
+ select CPU_CLUSTER
config HEX_VIRT
bool
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 06/18] bitops.h: Add find_first_bit32()
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (4 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 07/18] hw/intc: Add l2vic interrupt controller Brian Cain
` (11 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
set_bit32()/test_bit32()/etc already let devices operate on
guest-visible uint32_t register arrays without depending on the host's
'unsigned long' size. Add find_first_bit32() so callers need not cast
a uint32_t array to 'unsigned long *'.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/qemu/bitops.h | 30 ++++++++++++++++++++++++++----
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h
index c7b838a6283..f7363a5479f 100644
--- a/include/qemu/bitops.h
+++ b/include/qemu/bitops.h
@@ -43,10 +43,9 @@
* be some guest-visible register view of the bit array.
*
* We do not currently implement uint32_t versions of find_last_bit(),
- * find_next_bit(), find_next_zero_bit(), find_first_bit() or
- * find_first_zero_bit(), because we haven't yet needed them. If you
- * need them you should implement them similarly to the 'unsigned long'
- * versions.
+ * find_next_bit(), find_next_zero_bit() or find_first_zero_bit(),
+ * because we haven't yet needed them. If you need them you should
+ * implement them similarly to the 'unsigned long' versions.
*
* You can declare a bitmap to be used with these functions via the
* DECLARE_BITMAP and DECLARE_BITMAP32 macros in bitmap.h.
@@ -382,6 +381,29 @@ static inline int test_bit32(long nr, const uint32_t *addr)
return 1U & (addr[BIT32_WORD(nr)] >> (nr & 31));
}
+/**
+ * find_first_bit32 - find the first set bit in a memory region
+ * @addr: The address to start the search at
+ * @size: The maximum size to search
+ *
+ * Returns the bit number of the first set bit,
+ * or @size if there is no set bit in the bitmap.
+ */
+static inline uint32_t find_first_bit32(const uint32_t *addr, uint32_t size)
+{
+ uint32_t result;
+
+ for (result = 0; result < size; result += 32) {
+ uint32_t tmp = *addr++;
+ if (tmp) {
+ result += ctz32(tmp);
+ return result < size ? result : size;
+ }
+ }
+ /* Not found */
+ return size;
+}
+
/**
* DOC: Miscellaneous bit operations on single values
*
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 07/18] hw/intc: Add l2vic interrupt controller
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (5 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 06/18] bitops.h: Add find_first_bit32() Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 08/18] hw/hexagon: extract get_reg_value/set_reg_value stubs in globalreg Brian Cain
` (10 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino, Sid Manning,
Matheus Tavares Bernardino, Damien Hedde
From: Sid Manning <sidneym@quicinc.com>
The Hexagon DSP requires an L2VIC to route up to 1024 external
interrupt sources through 4 VID output groups into the core's 8
interrupt inputs. Add a device model for it.
Co-authored-by: Matheus Tavares Bernardino <quic_mathbern@quicinc.com>
Co-authored-by: Damien Hedde <damien.hedde@dahe.fr>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 3 +
docs/devel/hexagon-l2vic.rst | 55 ++++
docs/devel/index-internals.rst | 1 +
include/hw/intc/hex-l2vic.h | 61 ++++
target/hexagon/cpu.h | 2 +
hw/intc/hex-l2vic.c | 556 +++++++++++++++++++++++++++++++++
target/hexagon/cpu.c | 2 +
target/hexagon/op_helper.c | 16 +-
hw/hexagon/Kconfig | 1 +
hw/intc/Kconfig | 3 +
hw/intc/meson.build | 2 +
hw/intc/trace-events | 4 +
12 files changed, 705 insertions(+), 1 deletion(-)
create mode 100644 docs/devel/hexagon-l2vic.rst
create mode 100644 include/hw/intc/hex-l2vic.h
create mode 100644 hw/intc/hex-l2vic.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a28935c8986..a76366d6da5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -250,6 +250,8 @@ M: Brian Cain <brian.cain@oss.qualcomm.com>
R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
S: Supported
F: target/hexagon/
+F: hw/intc/hex-l2vic.c
+F: include/hw/intc/hex-l2vic.h
X: target/hexagon/idef-parser/
X: target/hexagon/gen_idef_parser_funcs.py
F: linux-user/hexagon/
@@ -262,6 +264,7 @@ F: gdbstub/gdb-xml/hexagon*.xml
F: docs/system/target-hexagon.rst
F: docs/system/hexagon/
F: docs/devel/hexagon-sys.rst
+F: docs/devel/hexagon-l2vic.rst
T: git https://github.com/qualcomm/qemu.git hex-next
Hexagon idef-parser
diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst
new file mode 100644
index 00000000000..9cb2a86871e
--- /dev/null
+++ b/docs/devel/hexagon-l2vic.rst
@@ -0,0 +1,55 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Hexagon L2 Vectored Interrupt Controller
+========================================
+
+
+.. code-block:: none
+
+ +-------------+ +----------------------+
+ | l2vic | | hexagon core |
+ | | | |
+ IRQ in ---->| | | |
+ IRQ in ---->| VID0 -|----------------->| irq2 |
+ ... ---->| | | | |
+ IRQ in ---->| | | v |
+ | ... | | <int steering> |
+ | | | / | | \ |
+ IRQ in ---->| | | t0 t1 t2 t3 ...|
+ IRQ in ---->| VIDN -| | |
+ ... ---->| | | |
+ IRQ in ---->| | | Global SREG File |
+ | | | |
+ | State | | |
+ | [ ] <--|==================|==> [ VID ] |
+ | [ ] <--|==================|==> [ VID1 ] |
+ | | | |
+ +-------------+ +----------------------+
+
+L2VIC/Core Integration
+----------------------
+
+* hexagon core supports 8 external interrupt sources
+* l2vic supports 1024 input interrupts mapped among 4 output interrupts
+* l2vic has four output signals: { VID0, VID1, VID2, VID3 }
+* l2vic device has a bank of registers per-VID that can be used to query
+ the status or assert new interrupts.
+* Interrupts are 'steered' to threads based on { thread priority, 'EX' state,
+ thread interrupt mask, thread interrupt enable, global interrupt enable,
+ etc. }.
+* Any hardware thread could conceivably handle any input interrupt, dependent
+ on state.
+* The system register transfer instruction can read the VID0-VID3 values from
+ the l2vic when reading from hexagon core system registers "VID" and "VID1".
+* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output
+ IRQ and stores the IRQ number for the VID0 register field. Only after this
+ interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide
+ the next interrupt number on the VID0 register.
+* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the
+ core interrupt. If some/an l2vic VID0 interrupt is pending when this occurs,
+ the next interrupt should fire and any subsequent reads of the VID register
+ should reflect the newly raised interrupt.
+* In QEMU, on an external interrupt or an unmasked-pending interrupt,
+ all vCPUs are triggered (has_work==true) and each will grab the IO lock
+ while considering the steering logic to determine whether they're the thread
+ that must handle the interrupt.
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index a8f5e310df3..763cda1e76f 100644
--- a/docs/devel/index-internals.rst
+++ b/docs/devel/index-internals.rst
@@ -15,6 +15,7 @@ Details about QEMU's various subsystems including how to add features to them.
clocks
ebpf_rss
hexagon-sys
+ hexagon-l2vic
migration/index
multi-process
reset
diff --git a/include/hw/intc/hex-l2vic.h b/include/hw/intc/hex-l2vic.h
new file mode 100644
index 00000000000..edc278266cb
--- /dev/null
+++ b/include/hw/intc/hex-l2vic.h
@@ -0,0 +1,61 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_INTC_HEX_L2VIC_H
+#define HW_INTC_HEX_L2VIC_H
+
+#include "qom/object.h"
+
+#define TYPE_HEX_L2VIC "hex-l2vic"
+/*
+ * L2VIC Interface for CPU/GlobalReg interaction
+ */
+#define TYPE_HEX_L2VIC_INTERFACE "hex-l2vic-if"
+
+typedef struct HexL2VicInterface HexL2VicInterface;
+
+typedef struct HexL2VicInterfaceClass {
+ InterfaceClass parent_class;
+
+ uint32_t (*read_vid)(HexL2VicInterface *l2vic, uint32_t group);
+
+ /*
+ * Write the VID: unpack the fields into per-group VIDs. This does
+ * not deliver or clear any interrupt; a pending interrupt stays
+ * gated until ciad.
+ */
+ void (*update_vid)(HexL2VicInterface *l2vic, uint32_t group,
+ uint32_t value);
+
+ /* Clear interrupt using CIAD instruction */
+ void (*clear_interrupt)(HexL2VicInterface *l2vic);
+} HexL2VicInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(HexL2VicInterface, HexL2VicInterfaceClass,
+ HEX_L2VIC_INTERFACE, TYPE_HEX_L2VIC_INTERFACE);
+
+static inline uint32_t l2vic_read_vid(HexL2VicInterface *l2vic,
+ uint32_t group)
+{
+ HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+ return k->read_vid(l2vic, group);
+}
+
+static inline void l2vic_update_vid(HexL2VicInterface *l2vic, uint32_t group,
+ uint32_t value)
+{
+ HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+ k->update_vid(l2vic, group, value);
+}
+
+static inline void l2vic_clear_interrupt(HexL2VicInterface *l2vic)
+{
+ HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+ k->clear_interrupt(l2vic);
+}
+
+#endif /* HW_INTC_HEX_L2VIC_H */
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 7694fd91fa8..f6c3c639325 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -39,6 +39,7 @@ typedef struct HexagonGlobalRegState HexagonGlobalRegState;
#include "qemu/bitmap.h"
#include "target/hexagon/reg_fields.h"
+#include "hw/intc/hex-l2vic.h"
#define NUM_PREGS 4
#define TOTAL_PER_THREAD_REGS 64
@@ -198,6 +199,7 @@ struct ArchCPU {
uint32_t boot_addr;
HexagonGlobalRegState *globalregs;
uint32_t htid;
+ HexL2VicInterface *l2vic;
#endif
};
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
new file mode 100644
index 00000000000..f07ec850d49
--- /dev/null
+++ b/hw/intc/hex-l2vic.c
@@ -0,0 +1,556 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitmap.h"
+#include "qemu/bitops.h"
+#include "hw/intc/hex-l2vic.h"
+#include "trace.h"
+
+#define L2VIC_VID_GRP_0 0x0 /* Read */
+#define L2VIC_VID_GRP_1 0x4 /* Read */
+#define L2VIC_VID_GRP_2 0x8 /* Read */
+#define L2VIC_VID_GRP_3 0xC /* Read */
+#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
+#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
+#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
+#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
+#define L2VIC_INT_STATUSn 0x380 /* Read */
+#define L2VIC_INT_CLEARn 0x400 /* Write */
+#define L2VIC_SOFT_INTn 0x480 /* Write */
+#define L2VIC_INT_PENDINGn 0x500 /* Read */
+#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
+#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
+#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
+#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
+
+#define L2VIC_INTERRUPT_MAX 1024
+/*
+ * Note about l2vic groups:
+ * Each interrupt to L2VIC can be configured to associate with one of
+ * four groups.
+ * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default)
+ * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3)
+ * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4)
+ * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5)
+ */
+
+static void bitmap32_write_word(uint32_t *bitmap, int word_offset, uint32_t val)
+{
+ bitmap[word_offset] = val;
+}
+
+static void bitmap32_clear_word(uint32_t *bitmap, int word_offset,
+ uint32_t mask)
+{
+ bitmap[word_offset] &= ~mask;
+}
+
+static void bitmap32_set_word(uint32_t *bitmap, int word_offset, uint32_t mask)
+{
+ bitmap[word_offset] |= mask;
+}
+
+static uint32_t bitmap32_read_word(uint32_t *bitmap, int word_offset)
+{
+ return bitmap[word_offset];
+}
+
+OBJECT_DECLARE_SIMPLE_TYPE(HexL2VICState, HEX_L2VIC)
+
+#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32)
+#define L2VIC_REG_RANGE_SIZE 0x80
+
+typedef struct HexL2VICState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegion fast_iomem;
+ /*
+ * vid_group[i] is readable at L2VIC_VID_GRP_i (offset i*4): the irq
+ * last delivered through VID group i, 0-1023 so only 10 bits are used.
+ */
+ uint32_t vid_group[4];
+ /*
+ * Last irq delivered on any VID group; not specific to group 0.
+ * Used by the ciad path to clear the most-recently-delivered
+ * interrupt from int_status.
+ */
+ uint32_t vid;
+ DECLARE_BITMAP32(int_enable, L2VIC_INTERRUPT_MAX);
+ /* Asserted interrupts awaiting delivery once no VID is active */
+ DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX);
+ /* Which enabled interrupt is active */
+ DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX);
+ /* Edge or Level interrupt */
+ DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX);
+ DECLARE_BITMAP32(int_group_n[4], L2VIC_INTERRUPT_MAX);
+ qemu_irq irq[8];
+} HexL2VICState;
+
+typedef enum {
+ L2VIC_OP_WRITE,
+ L2VIC_OP_CLEAR,
+ L2VIC_OP_SET,
+ L2VIC_OP_NONE,
+} L2VicWriteOp;
+
+typedef struct {
+ hwaddr base;
+ size_t state_offset;
+ L2VicWriteOp write_op;
+ bool write_only;
+} L2VicRegRange;
+
+static const L2VicRegRange l2vic_reg_ranges[] = {
+ { L2VIC_INT_ENABLEn, offsetof(HexL2VICState, int_enable),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_ENABLE_CLEARn, offsetof(HexL2VICState, int_enable),
+ L2VIC_OP_CLEAR, true },
+ { L2VIC_INT_ENABLE_SETn, offsetof(HexL2VICState, int_enable),
+ L2VIC_OP_SET, true },
+ { L2VIC_INT_TYPEn, offsetof(HexL2VICState, int_type),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_STATUSn, offsetof(HexL2VICState, int_status),
+ L2VIC_OP_NONE, false },
+ { L2VIC_INT_CLEARn, offsetof(HexL2VICState, int_status),
+ L2VIC_OP_CLEAR, true },
+ { L2VIC_SOFT_INTn, offsetof(HexL2VICState, int_pending),
+ L2VIC_OP_NONE, true },
+ { L2VIC_INT_PENDINGn, offsetof(HexL2VICState, int_pending),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_0, offsetof(HexL2VICState, int_group_n[0]),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_1, offsetof(HexL2VICState, int_group_n[1]),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_2, offsetof(HexL2VICState, int_group_n[2]),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_3, offsetof(HexL2VICState, int_group_n[3]),
+ L2VIC_OP_WRITE, false },
+};
+
+static uint32_t *l2vic_state_bitmap(HexL2VICState *s, size_t state_offset)
+{
+ return (uint32_t *)((char *)s + state_offset);
+}
+
+static bool l2vic_reg_read_range(HexL2VICState *s, hwaddr offset,
+ uint64_t *value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+ const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+ if (offset >= r->base &&
+ offset < r->base + L2VIC_REG_RANGE_SIZE) {
+ if (r->write_only) {
+ *value = 0;
+ } else {
+ uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+ *value = bitmap32_read_word(bitmap,
+ (offset - r->base) >> 2);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool l2vic_reg_write_range(HexL2VICState *s, hwaddr offset,
+ uint32_t val)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+ const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+ if (offset >= r->base &&
+ offset < r->base + L2VIC_REG_RANGE_SIZE) {
+ uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+ int word = (offset - r->base) >> 2;
+
+ switch (r->write_op) {
+ case L2VIC_OP_WRITE:
+ bitmap32_write_word(bitmap, word, val);
+ break;
+ case L2VIC_OP_CLEAR:
+ bitmap32_clear_word(bitmap, word, val);
+ break;
+ case L2VIC_OP_SET:
+ bitmap32_set_word(bitmap, word, val);
+ break;
+ case L2VIC_OP_NONE:
+ /* Read-only or handled elsewhere; ignore the write. */
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * The four INT_GRPn_* register arrays are interleaved across irqs in
+ * blocks of 8: irq 0-7 live in group_n[0], irq 8-15 in group_n[1], irq
+ * 16-23 in group_n[2], irq 24-31 in group_n[3], irq 32-39 back in
+ * group_n[0], and so on.
+ */
+static uint32_t *get_int_group(HexL2VICState *s, int irq)
+{
+ return s->int_group_n[extract32(irq, 3, 2)];
+}
+
+static int find_slice(int irq)
+{
+ return irq / 32;
+}
+
+static int get_vid(HexL2VICState *s, int irq)
+{
+ uint32_t *group = get_int_group(s, irq);
+ uint32_t slice = group[find_slice(irq)];
+ uint32_t vid;
+ /*
+ * Each irq occupies a 4-bit field: bit 3 is the group-enable bit,
+ * bits 0-2 select the VID group. Shift down to this irq's field.
+ */
+ uint32_t val = slice >> ((irq & 0x7) * 4);
+
+ if (!(val & 0x8)) {
+ return 0;
+ }
+ vid = val & 0x7;
+ if (vid >= ARRAY_SIZE(s->vid_group)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "L2VIC: irq %d requests invalid vid group %u\n",
+ irq, vid);
+ return 0;
+ }
+ return vid;
+}
+
+static inline bool vid_active(HexL2VICState *s)
+{
+ const uint32_t size = L2VIC_INTERRUPT_MAX;
+ const uint32_t active_irq = find_first_bit32(s->int_status, size);
+ return active_irq != size;
+}
+
+static bool l2vic_update(HexL2VICState *s, int irq)
+{
+ bool pending;
+ bool enable;
+
+ if (vid_active(s)) {
+ return true;
+ }
+
+ pending = test_bit32(irq, s->int_pending);
+ enable = test_bit32(irq, s->int_enable);
+ if (pending && enable) {
+ int vid = get_vid(s, irq);
+ set_bit32(irq, s->int_status);
+ clear_bit32(irq, s->int_pending);
+ /*
+ * Only auto-disable for edge-triggered interrupts (type=1).
+ * Level-triggered interrupts (type=0, the default) keep their
+ * enable bit set across deliveries -- the firmware enables once
+ * and expects the interrupt to remain enabled.
+ */
+ if (test_bit32(irq, s->int_type)) {
+ clear_bit32(irq, s->int_enable);
+ }
+ s->vid = irq;
+ s->vid_group[vid] = irq;
+
+ qemu_irq_pulse(s->irq[vid + 2]);
+ trace_hex_l2vic_delivered(irq, vid);
+ return true;
+ }
+ return false;
+}
+
+static void l2vic_update_all(HexL2VICState *s)
+{
+ for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) {
+ if (l2vic_update(s, i)) {
+ /* once vid is active, no-one else can set it until ciad */
+ return;
+ }
+ }
+}
+
+static void l2vic_set_irq(void *opaque, int irq, int level)
+{
+ HexL2VICState *s = (HexL2VICState *)opaque;
+
+ if (level) {
+ set_bit32(irq, s->int_pending);
+ }
+ l2vic_update(s, irq);
+}
+
+static void l2vic_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ HexL2VICState *s = (HexL2VICState *)opaque;
+
+ trace_hex_l2vic_reg_write((unsigned)offset, (uint32_t)val);
+
+ if (!l2vic_reg_write_range(s, offset, val)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: offset 0x%" HWADDR_PRIx " unimplemented\n",
+ __func__, offset);
+ }
+
+ /* SOFT_INT also sets pending for edge-triggered interrupts */
+ if (offset >= L2VIC_SOFT_INTn &&
+ offset < L2VIC_SOFT_INTn + L2VIC_REG_RANGE_SIZE && val) {
+ int base_irq = ((offset - L2VIC_SOFT_INTn) >> 2) * 32;
+ uint32_t bits = val;
+ int bit;
+
+ while ((bit = ctz32(bits)) < 32) {
+ int irq = base_irq + bit;
+
+ if (test_bit32(irq, s->int_type)) {
+ set_bit32(irq, s->int_pending);
+ }
+ bits &= ~(1u << bit);
+ }
+ }
+
+ l2vic_update_all(s);
+}
+
+static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint64_t value;
+ HexL2VICState *s = (HexL2VICState *)opaque;
+
+ if (offset <= L2VIC_VID_GRP_3) {
+ value = s->vid_group[offset >> 2];
+ } else if (!l2vic_reg_read_range(s, offset, &value)) {
+ value = 0;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "L2VIC: %s: offset 0x%" HWADDR_PRIx "\n", __func__,
+ offset);
+ }
+
+ trace_hex_l2vic_reg_read((unsigned)offset, (uint32_t)value);
+ return value;
+}
+
+static const MemoryRegionOps l2vic_ops = {
+ .read = l2vic_read,
+ .write = l2vic_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+#define FASTL2VIC_ENABLE 0x0
+#define FASTL2VIC_DISABLE 0x1
+#define FASTL2VIC_INT 0x2
+
+static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ if (offset == 0) {
+ uint32_t cmd = (val >> 16) & 0x3;
+ uint32_t irq = val & 0x3ff;
+ uint32_t slice = (irq / 32) * 4;
+ val = 1 << (irq % 32);
+
+ if (cmd == FASTL2VIC_ENABLE) {
+ l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size);
+ } else if (cmd == FASTL2VIC_DISABLE) {
+ l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size);
+ } else if (cmd == FASTL2VIC_INT) {
+ l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write cmd %" PRId32 "\n",
+ __func__, cmd);
+ }
+ return;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx
+ "\n", __func__, offset);
+}
+
+static uint64_t fastl2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+ return 0;
+}
+
+static const MemoryRegionOps fastl2vic_ops = {
+ .read = fastl2vic_read,
+ .write = fastl2vic_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static uint32_t l2vic_interface_read_vid_impl(HexL2VicInterface *iface,
+ uint32_t group)
+{
+ HexL2VICState *s = HEX_L2VIC(iface);
+ uint32_t result = 0;
+
+ if (group == 0) {
+ /* VID register combines vid_group[0] (VID0) and vid_group[1] (VID1) */
+ result = deposit32(result, 0, 16, s->vid_group[0]);
+ result = deposit32(result, 16, 16, s->vid_group[1]);
+ } else if (group == 1) {
+ /* VID1 register combines vid_group[2] (VID2) and vid_group[3] (VID3) */
+ result = deposit32(result, 0, 16, s->vid_group[2]);
+ result = deposit32(result, 16, 16, s->vid_group[3]);
+ }
+ return result;
+}
+
+static void l2vic_interface_update_vid_impl(HexL2VicInterface *iface,
+ uint32_t group, uint32_t value)
+{
+ HexL2VICState *s = HEX_L2VIC(iface);
+
+ if (group == 0) {
+ s->vid_group[0] = extract32(value, 0, 16);
+ s->vid_group[1] = extract32(value, 16, 16);
+ } else if (group == 1) {
+ s->vid_group[2] = extract32(value, 0, 16);
+ s->vid_group[3] = extract32(value, 16, 16);
+ }
+
+ l2vic_update_all(s);
+}
+
+static void l2vic_interface_clear_interrupt_impl(HexL2VicInterface *iface)
+{
+ HexL2VICState *s = HEX_L2VIC(iface);
+
+ if (s->vid < L2VIC_INTERRUPT_MAX) {
+ clear_bit32(s->vid, s->int_status);
+ }
+ l2vic_update_all(s);
+}
+
+static void l2vic_reset_hold(Object *obj, ResetType type G_GNUC_UNUSED)
+{
+ HexL2VICState *s = HEX_L2VIC(obj);
+
+ memset(s->int_enable, 0, sizeof(s->int_enable));
+ memset(s->int_pending, 0, sizeof(s->int_pending));
+ memset(s->int_status, 0, sizeof(s->int_status));
+ memset(s->int_type, 0, sizeof(s->int_type));
+ memset(s->int_group_n, 0, sizeof(s->int_group_n));
+ memset(s->vid_group, 0, sizeof(s->vid_group));
+ s->vid = 0;
+
+ l2vic_update_all(s);
+}
+
+static void reset_irq_handler(void *opaque, int irq, int level)
+{
+ Object *obj = OBJECT(opaque);
+
+ if (level) {
+ l2vic_reset_hold(obj, RESET_TYPE_COLD);
+ }
+}
+
+static void l2vic_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ HexL2VICState *s = HEX_L2VIC(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ int i;
+
+ memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast",
+ 0x10000);
+ sysbus_init_mmio(sbd, &s->fast_iomem);
+
+ qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX);
+ qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1);
+ for (i = 0; i < 8; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ }
+}
+
+static const VMStateDescription vmstate_l2vic = {
+ .name = "l2vic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields =
+ (VMStateField[]){
+ VMSTATE_UINT32_ARRAY(vid_group, HexL2VICState, 4),
+ VMSTATE_UINT32(vid, HexL2VICState),
+ VMSTATE_UINT32_ARRAY(int_enable, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_type, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_status, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_pending, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_2DARRAY(int_group_n, HexL2VICState, 4, SLICE_MAX),
+ VMSTATE_END_OF_LIST() }
+};
+
+static void l2vic_interface_class_init(ObjectClass *klass, const void *data)
+{
+ HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_CLASS(klass);
+
+ k->read_vid = l2vic_interface_read_vid_impl;
+ k->update_vid = l2vic_interface_update_vid_impl;
+ k->clear_interrupt = l2vic_interface_clear_interrupt_impl;
+}
+
+static void l2vic_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->vmsd = &vmstate_l2vic;
+ rc->phases.hold = l2vic_reset_hold;
+}
+
+static const TypeInfo l2vic_interface_info = {
+ .name = TYPE_HEX_L2VIC_INTERFACE,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(HexL2VicInterfaceClass),
+ .class_init = l2vic_interface_class_init,
+};
+
+static const TypeInfo l2vic_info = {
+ .name = TYPE_HEX_L2VIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(HexL2VICState),
+ .instance_init = l2vic_init,
+ .class_init = l2vic_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HEX_L2VIC_INTERFACE },
+ { }
+ },
+};
+
+static const TypeInfo l2vic_types[] = {
+ l2vic_interface_info,
+ l2vic_info,
+};
+
+DEFINE_TYPES(l2vic_types)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 12d27ce1381..75cae450299 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -65,6 +65,8 @@ static const Property hexagon_cpu_properties[] = {
DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
HexagonTLBState *),
DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff),
+ DEFINE_PROP_LINK("l2vic", HexagonCPU, l2vic,
+ TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
DEFINE_PROP_LINK("global-regs", HexagonCPU, globalregs,
TYPE_HEXAGON_GLOBALREG, HexagonGlobalRegState *),
DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0),
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 3ce223caba3..8fb3d43c33b 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -40,6 +40,7 @@
#include "hw/hexagon/hexagon_globalreg.h"
#include "hex_mmu.h"
#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/intc/hex-l2vic.h"
#include "hex_interrupts.h"
#include "hexswi.h"
#endif
@@ -1564,7 +1565,20 @@ void HELPER(raise_stack_overflow)(CPUHexagonState *env, uint32_t slot,
void HELPER(ciad)(CPUHexagonState *env, uint32_t mask)
{
- g_assert_not_reached();
+ uint32_t ipendad;
+ uint32_t iad;
+ HexagonCPU *cpu;
+
+ BQL_LOCK_GUARD();
+ cpu = env_archcpu(env);
+ ipendad = hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+ env->threadId);
+ iad = fGET_FIELD(ipendad, IPENDAD_IAD);
+ fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask));
+ hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+ ipendad, env->threadId);
+ l2vic_clear_interrupt(cpu->l2vic);
+ hex_interrupt_update(env);
}
void HELPER(siad)(CPUHexagonState *env, uint32_t mask)
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index 121c548bbb9..c75090c44a1 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -3,6 +3,7 @@ config HEX_DSP
default y
depends on HEXAGON
select CPU_CLUSTER
+ select HEX_L2VIC
config HEX_VIRT
bool
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 636d00b7e88..097de4efc5d 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -8,6 +8,9 @@ config I8259
config PL190
bool
+config HEX_L2VIC
+ bool
+
config IOAPIC
bool
select I8259
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index fac2d228f9b..9077a3f3297 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -74,6 +74,8 @@ specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('xics_spapr.c', 'spapr_xi
specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c'))
specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
if_true: files('spapr_xive_kvm.c'))
+
+specific_ss.add(when: 'CONFIG_HEX_L2VIC', if_true: files('hex-l2vic.c'))
specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c'))
specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index e7d6c304487..7512c6e6004 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -337,6 +337,10 @@ sh_intc_register(const char *s, int id, unsigned short v, int c, int m) "%s %u -
sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx"
sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx"
sh_intc_set(int id, int enable) "setting interrupt group %d to %d"
+# hex-l2vic.c
+hex_l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)"
# loongson_ipi.c
loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 08/18] hw/hexagon: extract get_reg_value/set_reg_value stubs in globalreg
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (6 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 07/18] hw/intc: Add l2vic interrupt controller Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 09/18] hw/hexagon: connect l2vic device Brian Cain
` (9 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Route the globalreg read/write accessors through new
get_reg_value()/set_reg_value() helpers instead of touching
s->regs[reg] directly.
This will be exploited by a subsequent patch that redirects VID/VID1
accesses to the L2VIC.
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
hw/hexagon/hexagon_globalreg.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c
index b5e5913507e..105475219ab 100644
--- a/hw/hexagon/hexagon_globalreg.c
+++ b/hw/hexagon/hexagon_globalreg.c
@@ -135,6 +135,17 @@ static inline uint32_t apply_write_mask(uint32_t new_val, uint32_t cur_val,
return new_val;
}
+static uint32_t get_reg_value(HexagonGlobalRegState *s, uint32_t reg)
+{
+ return s->regs[reg];
+}
+
+static void set_reg_value(HexagonGlobalRegState *s, uint32_t reg,
+ uint32_t value)
+{
+ s->regs[reg] = value;
+}
+
uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
uint32_t htid)
{
@@ -146,7 +157,7 @@ uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
g_assert(reg < NUM_SREGS);
g_assert(reg >= HEX_SREG_GLB_START);
- value = s->regs[reg];
+ value = get_reg_value(s, reg);
trace_hexagon_globalreg_read(htid, get_sreg_name(reg), value);
return value;
@@ -160,7 +171,7 @@ void hexagon_globalreg_write(HexagonGlobalRegState *s, uint32_t reg,
}
g_assert(reg < NUM_SREGS);
g_assert(reg >= HEX_SREG_GLB_START);
- s->regs[reg] = value;
+ set_reg_value(s, reg, value);
trace_hexagon_globalreg_write(htid, get_sreg_name(reg), value);
}
@@ -168,6 +179,7 @@ uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg,
uint32_t value)
{
uint32_t reg_mask;
+ uint32_t cur_val;
if (!s) {
return value;
@@ -175,9 +187,10 @@ uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg,
g_assert(reg < NUM_SREGS);
g_assert(reg >= HEX_SREG_GLB_START);
reg_mask = global_sreg_immut_masks[reg];
+ cur_val = get_reg_value(s, reg);
return reg_mask == IMMUTABLE ?
- s->regs[reg] :
- apply_write_mask(value, s->regs[reg], reg_mask);
+ cur_val :
+ apply_write_mask(value, cur_val, reg_mask);
}
void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg,
@@ -186,7 +199,7 @@ void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg,
if (!s) {
return;
}
- s->regs[reg] = hexagon_globalreg_masked_value(s, reg, value);
+ set_reg_value(s, reg, hexagon_globalreg_masked_value(s, reg, value));
}
uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s)
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 09/18] hw/hexagon: connect l2vic device
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (7 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 08/18] hw/hexagon: extract get_reg_value/set_reg_value stubs in globalreg Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 10/18] hw/hexagon/virt: instantiate virtio-mmio transports Brian Cain
` (8 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add the l2vic to the shared hex-subsys so both machine models pick it
up. Map its register banks, wire the interrupt lines to CPU[0]
and link each vCPU, globalregs.
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hex-subsys.h | 5 ++--
include/hw/hexagon/hexagon.h | 1 +
include/hw/hexagon/hexagon_globalreg.h | 4 +++
hw/hexagon/hex-subsys.c | 38 +++++++++++++++++++++++++-
hw/hexagon/hexagon_dsp.c | 2 +-
hw/hexagon/hexagon_globalreg.c | 25 +++++++++++++++++
hw/hexagon/virt.c | 2 +-
7 files changed, 72 insertions(+), 5 deletions(-)
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h
index 6d0d5e1da86..5792f7b5af3 100644
--- a/include/hw/hexagon/hex-subsys.h
+++ b/include/hw/hexagon/hex-subsys.h
@@ -18,7 +18,7 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
/*
* Parent a CPU into the subsystem's cluster and wire its links. Call for
* every CPU before hex_subsys_realize_cluster(), then realize each CPU with
- * hex_subsys_realize_cpu().
+ * hex_subsys_realize_cpu(). CPU[0] receives the L2VIC outputs.
*/
void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
@@ -26,6 +26,7 @@ void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
void hex_subsys_realize_cluster(HexagonCommonMachineState *hms);
/* Realize a CPU previously parented via hex_subsys_add_cpu(). */
-void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu,
+ bool boot_cpu);
#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 31669a829f8..ec1578807d8 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -158,6 +158,7 @@ struct HexagonCommonMachineState {
MemoryRegion cfgtable_rom;
MemoryRegion vtcm;
DeviceState *cluster;
+ DeviceState *l2vic;
DeviceState *glob_regs;
DeviceState *tlb;
};
diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h
index 950099808fd..397dc6854c9 100644
--- a/include/hw/hexagon/hexagon_globalreg.h
+++ b/include/hw/hexagon/hexagon_globalreg.h
@@ -10,6 +10,7 @@
#include "hw/core/qdev.h"
#include "hw/core/sysbus.h"
+#include "hw/intc/hex-l2vic.h"
#include "qom/object.h"
#include "target/hexagon/cpu.h"
@@ -22,6 +23,9 @@ struct HexagonGlobalRegState {
/* Array of system registers */
uint32_t regs[NUM_SREGS];
+ /* L2VIC interface used to back the VID/VID1 registers */
+ HexL2VicInterface *l2vic;
+
/* Global performance cycle counter base */
uint64_t g_pcycle_base;
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index 6fad6fe0a4d..25c028f6579 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -10,6 +10,7 @@
#include "hw/hexagon/hex-subsys.h"
#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/intc/hex-l2vic.h"
#include "hw/cpu/cluster.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
@@ -17,6 +18,31 @@
#include "hw/core/sysbus.h"
#include "system/address-spaces.h"
+#define HEX_L2VIC_CPU_IRQS 8
+
+static DeviceState *l2vic_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg)
+{
+ DeviceState *l2vic = qdev_new(TYPE_HEX_L2VIC);
+
+ object_property_add_child(OBJECT(hms), "l2vic", OBJECT(l2vic));
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(l2vic), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 0, m_cfg->l2vic_base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 1,
+ m_cfg->cfgtable.fastl2vic_base << 16);
+
+ return l2vic;
+}
+
+static void l2vic_connect_cpu(DeviceState *l2vic, DeviceState *cpu)
+{
+ int i;
+
+ for (i = 0; i < HEX_L2VIC_CPU_IRQS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(l2vic), i, qdev_get_gpio_in(cpu, i));
+ }
+}
+
static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg,
Rev_t rev)
@@ -26,6 +52,8 @@ static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
object_property_add_child(OBJECT(hms), "global-regs", OBJECT(glob_regs));
qdev_prop_set_uint64(glob_regs, "config-table-addr", m_cfg->cfgbase);
qdev_prop_set_uint32(glob_regs, "dsp-rev", rev);
+ object_property_set_link(OBJECT(glob_regs), "l2vic", OBJECT(hms->l2vic),
+ &error_fatal);
sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs), &error_fatal);
return glob_regs;
@@ -81,6 +109,7 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
}
hms->cluster = cluster_create(hms);
+ hms->l2vic = l2vic_create(hms, m_cfg);
hms->glob_regs = globalreg_create(hms, m_cfg, rev);
hms->tlb = tlb_create(hms, m_cfg);
}
@@ -92,6 +121,8 @@ void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
OBJECT(hms->glob_regs), &error_fatal);
object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb),
&error_fatal);
+ object_property_set_link(OBJECT(cpu), "l2vic", OBJECT(hms->l2vic),
+ &error_fatal);
}
void hex_subsys_realize_cluster(HexagonCommonMachineState *hms)
@@ -105,7 +136,12 @@ void hex_subsys_realize_cluster(HexagonCommonMachineState *hms)
qdev_realize_and_unref(hms->cluster, NULL, &error_fatal);
}
-void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu,
+ bool boot_cpu)
{
qdev_realize_and_unref(cpu, NULL, &error_fatal);
+
+ if (boot_cpu) {
+ l2vic_connect_cpu(hms->l2vic, cpu);
+ }
}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index 1db5d681665..20306c28e72 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -139,7 +139,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev,
hex_subsys_realize_cluster(hms);
for (int i = 0; i < machine->smp.cpus; i++) {
- hex_subsys_realize_cpu(hms, DEVICE(cpus[i]));
+ hex_subsys_realize_cpu(hms, DEVICE(cpus[i]), (i == 0));
}
}
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c
index 105475219ab..285cb48c44b 100644
--- a/hw/hexagon/hexagon_globalreg.c
+++ b/hw/hexagon/hexagon_globalreg.c
@@ -11,6 +11,7 @@
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "hw/core/resettable.h"
+#include "hw/intc/hex-l2vic.h"
#include "migration/vmstate.h"
#include "qom/object.h"
#include "target/hexagon/cpu.h"
@@ -135,8 +136,16 @@ static inline uint32_t apply_write_mask(uint32_t new_val, uint32_t cur_val,
return new_val;
}
+static inline bool is_vid_reg(uint32_t reg)
+{
+ return reg == HEX_SREG_VID || reg == HEX_SREG_VID1;
+}
+
static uint32_t get_reg_value(HexagonGlobalRegState *s, uint32_t reg)
{
+ if (is_vid_reg(reg)) {
+ return l2vic_read_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1);
+ }
return s->regs[reg];
}
@@ -144,6 +153,9 @@ static void set_reg_value(HexagonGlobalRegState *s, uint32_t reg,
uint32_t value)
{
s->regs[reg] = value;
+ if (is_vid_reg(reg)) {
+ l2vic_update_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1, value);
+ }
}
uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
@@ -269,6 +281,16 @@ static void hexagon_globalreg_reset_hold(Object *obj, ResetType type)
do_hexagon_globalreg_reset(s);
}
+static void hexagon_globalreg_realize(DeviceState *dev, Error **errp)
+{
+ HexagonGlobalRegState *s = HEXAGON_GLOBALREG(dev);
+
+ if (!s->l2vic) {
+ error_setg(errp, "hexagon_globalreg: 'l2vic' link property not set");
+ return;
+ }
+}
+
static const VMStateDescription vmstate_hexagon_globalreg = {
.name = "hexagon_globalreg",
.version_id = 1,
@@ -288,6 +310,8 @@ static const VMStateDescription vmstate_hexagon_globalreg = {
};
static const Property hexagon_globalreg_properties[] = {
+ DEFINE_PROP_LINK("l2vic", HexagonGlobalRegState, l2vic,
+ TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0),
DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState,
config_table_addr, 0xffffffffULL),
@@ -308,6 +332,7 @@ static void hexagon_globalreg_class_init(ObjectClass *klass, const void *data)
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = hexagon_globalreg_reset_hold;
+ dc->realize = hexagon_globalreg_realize;
dc->vmsd = &vmstate_hexagon_globalreg;
dc->user_creatable = false;
device_class_set_props(dc, hexagon_globalreg_properties);
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index dc1d5b6aa24..9a680852c3c 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -270,7 +270,7 @@ static void virt_init(MachineState *ms)
hex_subsys_realize_cluster(&vms->parent_obj);
for (int i = 0; i < ms->smp.cpus; i++) {
- hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpus[i]));
+ hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpus[i]), (i == 0));
}
fdt_add_cpu_nodes(vms);
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 10/18] hw/hexagon/virt: instantiate virtio-mmio transports
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (8 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 09/18] hw/hexagon: connect l2vic device Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes Brian Cain
` (7 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add a VIRT_MMIO region and virtio-mmio transports, wired into l2vic.
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/virt.h | 3 +++
hw/hexagon/virt.c | 28 ++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h
index cf0cd2f8f22..4ca2f1ef859 100644
--- a/include/hw/hexagon/virt.h
+++ b/include/hw/hexagon/virt.h
@@ -11,6 +11,8 @@
#include "hw/hexagon/hexagon.h"
#include "target/hexagon/cpu.h"
+#define VIRTIO_DEV_COUNT 8
+
struct HexagonVirtMachineState {
HexagonCommonMachineState parent_obj;
@@ -19,6 +21,7 @@ struct HexagonVirtMachineState {
MemoryRegion tcm;
MemoryRegion bios;
Clock *apb_clk;
+ DeviceState *virtio_mmio[VIRTIO_DEV_COUNT];
};
void hexagon_load_fdt(const struct HexagonVirtMachineState *vms);
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index 9a680852c3c..ff0770cfc46 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -30,11 +30,19 @@
enum {
VIRT_UART0,
+ VIRT_MMIO,
VIRT_FDT,
};
+/*
+ * Virtio IRQs run from VIRTIO_IRQ_BASE to
+ * VIRTIO_IRQ_BASE + VIRTIO_DEV_COUNT - 1
+ */
+static const int VIRTIO_IRQ_BASE = 16;
+
static const MemMapEntry base_memmap[] = {
[VIRT_UART0] = { 0x10000000, 0x00000200 },
+ [VIRT_MMIO] = { 0x11000000, 0x00001000 },
[VIRT_FDT] = { 0x99800000, 0x00400000 },
};
@@ -167,7 +175,25 @@ static void fdt_add_cpu_nodes(const HexagonVirtMachineState *vms)
}
}
+static void create_virtio_devices(HexagonVirtMachineState *vms)
+{
+ hwaddr size = base_memmap[VIRT_MMIO].size;
+ for (int i = 0; i < VIRTIO_DEV_COUNT; i++) {
+ int irq = VIRTIO_IRQ_BASE + i;
+ hwaddr base = base_memmap[VIRT_MMIO].base + i * size;
+ DeviceState *dev = qdev_new("virtio-mmio");
+ SysBusDevice *s = SYS_BUS_DEVICE(dev);
+
+ object_property_add_child(OBJECT(MACHINE(vms)), "virtio-mmio[*]",
+ OBJECT(dev));
+ sysbus_realize_and_unref(s, &error_fatal);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0,
+ qdev_get_gpio_in(vms->parent_obj.l2vic, irq));
+ vms->virtio_mmio[i] = dev;
+ }
+}
void hexagon_load_fdt(const HexagonVirtMachineState *vms)
{
@@ -246,6 +272,8 @@ static void virt_init(MachineState *ms)
fdt_add_hvx(vms, m_cfg);
+ create_virtio_devices(vms);
+
g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, ms->smp.cpus);
for (int i = 0; i < ms->smp.cpus; i++) {
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (9 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 10/18] hw/hexagon/virt: instantiate virtio-mmio transports Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:35 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 12/18] hw/hexagon/virt: connect pl011 UART interrupt Brian Cain
` (6 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Expose l2vic as a device-tree interrupt-controller node and reference
it via interrupt-parent, so guest kernels can discover the virtio-mmio
transports.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
hw/hexagon/virt.c | 43 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index ff0770cfc46..ea20ea3c50a 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -75,6 +75,30 @@ static void create_fdt(HexagonVirtMachineState *vms)
qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
}
+static int32_t fdt_add_l2vic(HexagonVirtMachineState *vms,
+ const struct hexagon_machine_config *m_cfg)
+{
+ MachineState *ms = MACHINE(vms);
+ int32_t l2vic_phandle = qemu_fdt_alloc_phandle(ms->fdt);
+ char *nodename = g_strdup_printf("/soc/interrupt-controller@%x",
+ m_cfg->l2vic_base);
+ const char compat[] = "qcom,h2-pic\0hvm-pic";
+
+ qemu_fdt_setprop_cell(ms->fdt, "/soc", "interrupt-parent", l2vic_phandle);
+
+ qemu_fdt_add_subnode(ms->fdt, nodename);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x0);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 0x1);
+ qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat));
+ qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0,
+ m_cfg->l2vic_base, m_cfg->l2vic_size);
+ qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", l2vic_phandle);
+
+ g_free(nodename);
+ return l2vic_phandle;
+}
+
static void fdt_add_hvx(HexagonVirtMachineState *vms,
const struct hexagon_machine_config *m_cfg)
{
@@ -175,13 +199,16 @@ static void fdt_add_cpu_nodes(const HexagonVirtMachineState *vms)
}
}
-static void create_virtio_devices(HexagonVirtMachineState *vms)
+static void create_virtio_devices(HexagonVirtMachineState *vms,
+ int32_t l2vic_phandle)
{
+ MachineState *ms = MACHINE(vms);
hwaddr size = base_memmap[VIRT_MMIO].size;
for (int i = 0; i < VIRTIO_DEV_COUNT; i++) {
int irq = VIRTIO_IRQ_BASE + i;
hwaddr base = base_memmap[VIRT_MMIO].base + i * size;
+ char *nodename = g_strdup_printf("/soc/virtio_mmio@%" PRIx64, base);
DeviceState *dev = qdev_new("virtio-mmio");
SysBusDevice *s = SYS_BUS_DEVICE(dev);
@@ -192,6 +219,16 @@ static void create_virtio_devices(HexagonVirtMachineState *vms)
sysbus_connect_irq(s, 0,
qdev_get_gpio_in(vms->parent_obj.l2vic, irq));
vms->virtio_mmio[i] = dev;
+
+ qemu_fdt_add_subnode(ms->fdt, nodename);
+ qemu_fdt_setprop_string(ms->fdt, nodename, "compatible",
+ "virtio,mmio");
+ qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", irq);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent",
+ l2vic_phandle);
+
+ g_free(nodename);
}
}
@@ -251,6 +288,7 @@ static void virt_init(MachineState *ms)
HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms);
const struct hexagon_machine_config *m_cfg = &v68n_1024;
int32_t clk_phandle;
+ int32_t l2vic_phandle;
create_fdt(vms);
qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline);
@@ -272,7 +310,8 @@ static void virt_init(MachineState *ms)
fdt_add_hvx(vms, m_cfg);
- create_virtio_devices(vms);
+ l2vic_phandle = fdt_add_l2vic(vms, m_cfg);
+ create_virtio_devices(vms, l2vic_phandle);
g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, ms->smp.cpus);
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 12/18] hw/hexagon/virt: connect pl011 UART interrupt
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (10 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 13/18] tests/qtest: add L2VIC qtest Brian Cain
` (5 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Wire pl011's sysbus IRQ into l2vic at IRQ 15.
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
hw/hexagon/virt.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index ea20ea3c50a..64d8366d27d 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -39,6 +39,7 @@ enum {
* VIRTIO_IRQ_BASE + VIRTIO_DEV_COUNT - 1
*/
static const int VIRTIO_IRQ_BASE = 16;
+static const int VIRT_UART0_IRQ = 15;
static const MemMapEntry base_memmap[] = {
[VIRT_UART0] = { 0x10000000, 0x00000200 },
@@ -143,7 +144,7 @@ static int32_t fdt_add_clocks(const HexagonVirtMachineState *vms)
}
static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart,
- int32_t clk_phandle)
+ int32_t clk_phandle, int32_t l2vic_phandle)
{
char *nodename;
hwaddr base = base_memmap[uart].base;
@@ -161,6 +162,8 @@ static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart,
qdev_connect_clock_in(dev, "clk", vms->apb_clk);
sysbus_realize_and_unref(s, &error_fatal);
sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0,
+ qdev_get_gpio_in(vms->parent_obj.l2vic, VIRT_UART0_IRQ));
nodename = g_strdup_printf("/pl011@%" PRIx64, base);
qemu_fdt_add_subnode(ms->fdt, nodename);
@@ -168,6 +171,9 @@ static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart,
/* Note that we can't use setprop_string because of the embedded NUL */
qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat));
qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", VIRT_UART0_IRQ);
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent",
+ l2vic_phandle);
qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", clk_phandle,
clk_phandle);
qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames,
@@ -342,7 +348,7 @@ static void virt_init(MachineState *ms)
fdt_add_cpu_nodes(vms);
clk_phandle = fdt_add_clocks(vms);
- fdt_add_uart(vms, VIRT_UART0, clk_phandle);
+ fdt_add_uart(vms, VIRT_UART0, clk_phandle, l2vic_phandle);
hexagon_load_fdt(vms);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 13/18] tests/qtest: add L2VIC qtest
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (11 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 12/18] hw/hexagon/virt: connect pl011 UART interrupt Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 14/18] tests/functional/hexagon: add arch_tests functional test Brian Cain
` (4 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add a qtest exercising L2VIC register access and interrupt
enable/disable.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 1 +
tests/qtest/l2vic-test.c | 249 +++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +-
3 files changed, 251 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/l2vic-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a76366d6da5..114bbec45ad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -252,6 +252,7 @@ S: Supported
F: target/hexagon/
F: hw/intc/hex-l2vic.c
F: include/hw/intc/hex-l2vic.h
+F: tests/qtest/l2vic-test.c
X: target/hexagon/idef-parser/
X: target/hexagon/gen_idef_parser_funcs.py
F: linux-user/hexagon/
diff --git a/tests/qtest/l2vic-test.c b/tests/qtest/l2vic-test.c
new file mode 100644
index 00000000000..adb4dc7281e
--- /dev/null
+++ b/tests/qtest/l2vic-test.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTest testcase for the L2VIC Interrupt Controller
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "hw/hexagon/hexagon.h"
+
+#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
+#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
+
+/* L2VIC register offsets exercised by this test */
+#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
+#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
+#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
+#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
+#define L2VIC_INT_STATUSn 0x380 /* Read */
+#define L2VIC_INT_CLEARn 0x400 /* Write */
+#define L2VIC_SOFT_INTn 0x480 /* Write */
+#define L2VIC_INT_PENDINGn 0x500 /* Read */
+#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
+#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
+#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
+#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
+
+/*
+ * VID group readback: records which irq last fired through each VID
+ * group. Outputs themselves are momentary pulses (see l2vic_update()),
+ * so these registers -- not the qtest IRQ level snapshot -- are how the
+ * test observes VID steering.
+ */
+#define L2VIC_VID_GRP_0 0x0
+#define L2VIC_VID_GRP_1 0x4
+#define L2VIC_VID_GRP_2 0x8
+#define L2VIC_VID_GRP_3 0xC
+
+typedef struct {
+ const char *machine;
+ const struct hexagon_machine_config *cfg;
+} L2VICMachineCfg;
+
+static const L2VICMachineCfg l2vic_machines[] = {
+ { "virt", &v68n_1024 },
+ { "V66G_1024", &v66g_1024 },
+};
+
+static uint32_t l2vic_read32(uint64_t base, uint32_t offset)
+{
+ return readl(base + offset);
+}
+
+static void l2vic_write32(uint64_t base, uint32_t offset, uint32_t value)
+{
+ writel(base + offset, value);
+}
+
+static void test_l2vic_register_access(uint64_t base)
+{
+ uint32_t val;
+
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
+ val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+ g_assert_cmpuint(val & 0x1, ==, 0x1);
+
+ l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
+ val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+ g_assert_cmpuint(val & 0x1, ==, 0x0);
+}
+
+static void test_l2vic_interrupt_enable(uint64_t base)
+{
+ uint32_t val;
+
+ val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+ g_assert_cmpuint(val, ==, 0);
+
+ /* Enable IRQ 0 and 2 */
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x5);
+ val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+ g_assert_cmpuint(val & 0x5, ==, 0x5);
+
+ /* Disable IRQ 0, leaving IRQ 2 enabled */
+ l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
+ val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+ g_assert_cmpuint(val & 0x1, ==, 0x0);
+ g_assert_cmpuint(val & 0x4, ==, 0x4);
+}
+
+static void test_l2vic_basic_functionality(uint64_t base)
+{
+ l2vic_read32(base, L2VIC_INT_ENABLEn);
+ l2vic_read32(base, L2VIC_INT_PENDINGn);
+ l2vic_read32(base, L2VIC_INT_STATUSn);
+ l2vic_read32(base, L2VIC_INT_TYPEn);
+
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0);
+ l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0);
+}
+
+/*
+ * IRQs 0-7 pack their group-enable/VID-select nibbles into
+ * L2VIC_INT_GRPn_0 (int_group_n[0]), 4 bits per irq: bit 3 enables
+ * VID steering, bits 0-2 select the VID group (0-3), which pulses
+ * output line vid+2.
+ */
+static void l2vic_set_vid_group(uint64_t base, int irq, int vid)
+{
+ uint32_t val = l2vic_read32(base, L2VIC_INT_GRPn_0);
+ uint32_t nibble = 0x8 | (vid & 0x7);
+
+ val &= ~(0xFu << (irq * 4));
+ val |= nibble << (irq * 4);
+ l2vic_write32(base, L2VIC_INT_GRPn_0, val);
+}
+
+static void test_l2vic_irq_outputs(uint64_t base)
+{
+ uint32_t val;
+
+ l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0);
+ l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+
+ /* Group 0 / IRQ2: soft interrupts require edge-triggered config */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x1);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x1);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x1, ==, 0x1);
+ /* Default VID group (0) records the delivering irq, output line 2 */
+ g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_0), ==, 0);
+
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0x1);
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x1, ==, 0x0);
+
+ /* IRQ1 steered to VID group 1 -> output line 3 */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x2);
+ l2vic_set_vid_group(base, 1, 1);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x2);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x2);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x2, ==, 0x2);
+ g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_1), ==, 1);
+
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0x2);
+
+ /* IRQ4 steered to VID group 2 -> output line 4 */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x10);
+ l2vic_set_vid_group(base, 4, 2);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x10);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x10);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x10, ==, 0x10);
+ g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_2), ==, 4);
+
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0x10);
+
+ /* IRQ5 steered to VID group 3 -> output line 5 */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
+ l2vic_set_vid_group(base, 5, 3);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x20, ==, 0x20);
+ g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_3), ==, 5);
+
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0x20);
+
+ /* Restore defaults; the block below reuses IRQ 3-5 without VID steering */
+ l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0);
+
+ /* Multiple pending: at most one active at a time */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0xF);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0xF);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0xF);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0xF, !=, 0x0);
+
+ /*
+ * Only one irq becomes active per delivery; each clear unblocks the
+ * next pending one, so drain until all four have been delivered.
+ */
+ while ((val = l2vic_read32(base, L2VIC_INT_STATUSn)) & 0xF) {
+ l2vic_write32(base, L2VIC_INT_CLEARn, val & 0xF);
+ }
+
+ /* Level-triggered sources ignore soft interrupts */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x0);
+ l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x20, ==, 0x0);
+
+ /* Same source, now edge-triggered, does fire */
+ l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
+ l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+ val = l2vic_read32(base, L2VIC_INT_STATUSn);
+ g_assert_cmpuint(val & 0x20, ==, 0x20);
+
+ l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+ l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+ l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+ l2vic_write32(base, L2VIC_INT_GRPn_1, 0);
+ l2vic_write32(base, L2VIC_INT_GRPn_2, 0);
+ l2vic_write32(base, L2VIC_INT_GRPn_3, 0);
+}
+
+static void test_l2vic_on_machine(gconstpointer data)
+{
+ const L2VICMachineCfg *mc = data;
+ g_autofree char *args = g_strdup_printf("-machine %s", mc->machine);
+ uint64_t base = mc->cfg->l2vic_base;
+
+ qtest_start(args);
+
+ test_l2vic_register_access(base);
+ test_l2vic_interrupt_enable(base);
+ test_l2vic_basic_functionality(base);
+ test_l2vic_irq_outputs(base);
+
+ qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+ size_t i;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; i < ARRAY_SIZE(l2vic_machines); i++) {
+ g_autofree char *path = g_strdup_printf("/l2vic/%s/all-tests",
+ l2vic_machines[i].machine);
+ qtest_add_data_func(path, &l2vic_machines[i], test_l2vic_on_machine);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e216..a76863c7dd6 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -299,7 +299,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
['iommu-riscv-test'] : []) + \
(config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
-qtests_hexagon = ['boot-serial-test']
+qtests_hexagon = ['boot-serial-test', 'l2vic-test']
qos_test_ss = ss.source_set()
qos_test_ss.add(
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 14/18] tests/functional/hexagon: add arch_tests functional test
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (12 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 13/18] tests/qtest: add L2VIC qtest Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 15/18] hw/timer: Add QCT QTimer device model Brian Cain
` (3 subsequent siblings)
17 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add the test_int_steering case from hexagon-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 | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
index 54a1412c597..2bb34f9b8dc 100755
--- a/tests/functional/hexagon/test_arch_tests.py
+++ b/tests/functional/hexagon/test_arch_tests.py
@@ -52,6 +52,12 @@ def test_guest_mode(self) -> None:
"""
self.run_uart_test("test_guest_mode")
+ def test_int_steering(self) -> None:
+ """Tests interrupt steering via priority-based routing to
+ specific threads using STID priority and iassignw.
+ """
+ self.run_uart_test("test_int_steering")
+
if __name__ == "__main__":
QemuSystemTest.main()
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 15/18] hw/timer: Add QCT QTimer device model
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (13 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 14/18] tests/functional/hexagon: add arch_tests functional test Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:59 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 16/18] hw/hexagon: connect qtimer device Brian Cain
` (2 subsequent siblings)
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino, Damien Hedde,
Tobias Röhmel, Sid Manning, Thomas Marceron, Mahmoud Kamel
Implement the QCT QTimer generic timer device used by Hexagon DSP
systems.
Co-authored-by: Damien Hedde <damien.hedde@greensocs.com>
Co-authored-by: Tobias Röhmel <quic_trohmel@quicinc.com>
Co-authored-by: Sid Manning <sidneym@quicinc.com>
Co-authored-by: Thomas Marceron <tmarcero@qti.qualcomm.com>
Co-authored-by: Mahmoud Kamel <mkamel@qti.qualcomm.com>
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 2 +
include/hw/timer/qct-qtimer.h | 43 +++
hw/timer/qct-qtimer.c | 681 ++++++++++++++++++++++++++++++++++
hw/timer/Kconfig | 3 +
hw/timer/meson.build | 2 +
hw/timer/trace-events | 5 +
6 files changed, 736 insertions(+)
create mode 100644 include/hw/timer/qct-qtimer.h
create mode 100644 hw/timer/qct-qtimer.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 114bbec45ad..de106416b64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -253,6 +253,8 @@ F: target/hexagon/
F: hw/intc/hex-l2vic.c
F: include/hw/intc/hex-l2vic.h
F: tests/qtest/l2vic-test.c
+F: hw/timer/qct-qtimer.c
+F: include/hw/timer/qct-qtimer.h
X: target/hexagon/idef-parser/
X: target/hexagon/gen_idef_parser_funcs.py
F: linux-user/hexagon/
diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h
new file mode 100644
index 00000000000..53d8291472d
--- /dev/null
+++ b/include/hw/timer/qct-qtimer.h
@@ -0,0 +1,43 @@
+/*
+ * Qualcomm QCT QTimer
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_TIMER_QCT_QTIMER_H
+#define HW_TIMER_QCT_QTIMER_H
+
+#include "qom/object.h"
+
+#define TYPE_QCT_QTIMER "qct-qtimer"
+
+/* QTimer interface for external access from hexagon_globalreg */
+#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if"
+
+typedef struct QctQtimerInterface QctQtimerInterface;
+
+typedef struct QctQtimerInterfaceClass {
+ InterfaceClass parent_class;
+
+ /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */
+ uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer);
+ uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer);
+} QctQtimerInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass,
+ QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE);
+
+static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer)
+{
+ QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
+ return k->get_timer_lo(qtimer);
+}
+
+static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer)
+{
+ QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
+ return k->get_timer_hi(qtimer);
+}
+
+#endif /* HW_TIMER_QCT_QTIMER_H */
diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c
new file mode 100644
index 00000000000..82c711000eb
--- /dev/null
+++ b/hw/timer/qct-qtimer.c
@@ -0,0 +1,681 @@
+/*
+ * Qualcomm QCT QTimer
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "hw/timer/qct-qtimer.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#define QTIMER_MEM_SIZE_BYTES 0x1000
+#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
+
+#define QCT_QTIMER_TIMER_FRAME_ELTS (16)
+#define QCT_QTIMER_TIMER_VIEW_ELTS (2)
+
+#define QCT_QTIMER_AC_CNTFRQ (0x000)
+#define QCT_QTIMER_AC_CNTSR (0x004)
+#define QCT_QTIMER_AC_CNTTID_0 (0x08)
+#define QCT_QTIMER_AC_CNTACR_START (0x40)
+#define QCT_QTIMER_AC_CNTACR_END (0x5c)
+#define QCT_QTIMER_AC_CNTTID_1 (0x108)
+#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
+#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */
+#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */
+#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
+#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */
+#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
+#define QCT_QTIMER_VERSION (0x0fd0)
+
+#define QCT_QTIMER_CNTPCT_LO (0x000)
+#define QCT_QTIMER_CNTPCT_HI (0x004)
+#define QCT_QTIMER_CNT_FREQ (0x010)
+#define QCT_QTIMER_CNTPL0ACR (0x014)
+#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9)
+#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8)
+#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1)
+#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0)
+#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
+#define QCT_QTIMER_CNTP_CVAL_HI (0x024)
+#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL
+#define QCT_QTIMER_CNT_HI_BITS 24
+#define QCT_QTIMER_CNTP_TVAL (0x028)
+#define QCT_QTIMER_CNTP_CTL (0x02c)
+#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
+#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1)
+#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2)
+
+OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER)
+
+typedef struct QCTHextimerState {
+ QCTQtimerState *qtimer;
+ QEMUTimer *timer; /* one-shot deadline timer */
+ int64_t offset_ns; /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */
+ uint64_t cntval; /* 64-bit physical timer compare value */
+ uint32_t control;
+ uint32_t cnt_ctrl;
+ uint32_t cntpl0acr;
+ uint32_t int_level;
+ qemu_irq irq;
+} QCTHextimerState;
+
+struct QCTQtimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegion view_iomem;
+ uint32_t secure;
+ QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS];
+ uint32_t freq_hz;
+ uint32_t nr_frames;
+ uint32_t nr_views;
+ uint32_t frame_stride;
+ uint32_t cnttid_0;
+ uint32_t cnttid_1;
+ uint32_t freq_scale;
+};
+
+/*
+ * QTimer version register:
+ *
+ * 3 2 1
+ * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Major | Minor | Step |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+#define QCT_QTIMER_VERSION_VALUE 0x20020000
+
+/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */
+static uint64_t hex_timer_now(QCTHextimerState *s)
+{
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ uint32_t scale;
+ uint64_t scaled_elapsed;
+
+ if (now <= s->offset_ns) {
+ return 0;
+ }
+ scale = MAX(s->qtimer->freq_scale, 1u);
+ scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale;
+ return muldiv64(scaled_elapsed, s->qtimer->freq_hz,
+ NANOSECONDS_PER_SECOND) &
+ QCT_QTIMER_CNT_MASK;
+}
+
+/* Arm (or disarm) the one-shot deadline timer. */
+static void hex_timer_rearm(QCTHextimerState *s)
+{
+ uint32_t scale;
+ uint64_t base_ns;
+ int64_t deadline_ns;
+
+ if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) {
+ timer_del(s->timer);
+ return;
+ }
+
+ scale = MAX(s->qtimer->freq_scale, 1u);
+ /*
+ * Round the ticks-to-ns conversion up so that hex_timer_now(), which
+ * truncates when it divides elapsed ns by scale, is guaranteed to
+ * report >= cntval once this deadline fires. A truncating conversion
+ * here could re-arm at the same deadline forever when scale > 1.
+ */
+ base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND,
+ s->qtimer->freq_hz);
+ if (base_ns >
+ ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) {
+ timer_del(s->timer);
+ return;
+ }
+ deadline_ns = s->offset_ns + (int64_t)(base_ns * scale);
+ timer_mod(s->timer, deadline_ns);
+}
+
+static void hex_timer_update(QCTHextimerState *s)
+{
+ int level = s->int_level &&
+ (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) &&
+ !(s->control & QCT_QTIMER_CNTP_CTL_INTEN);
+
+ trace_qtimer_interrupt();
+ qemu_set_irq(s->irq, level);
+}
+
+/*
+ * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/
+ * CNTTID/CNTACR per frame plus the shared VERSION register.
+ */
+static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size)
+{
+ QCTQtimerState *s = opaque;
+ uint32_t frame;
+
+ switch (offset) {
+ case QCT_QTIMER_AC_CNTFRQ:
+ return s->freq_hz;
+ case QCT_QTIMER_AC_CNTSR:
+ return s->secure;
+ case QCT_QTIMER_AC_CNTTID_0:
+ return s->cnttid_0;
+ case QCT_QTIMER_AC_CNTTID_1:
+ return s->cnttid_1;
+ case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
+ frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
+ if (frame >= s->nr_frames) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
+ __func__, (int)offset);
+ return 0;
+ }
+ return s->timer[frame].cnt_ctrl;
+ case QCT_QTIMER_VERSION:
+ return QCT_QTIMER_VERSION_VALUE;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+ (int)offset);
+ return 0;
+ }
+}
+
+static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ QCTQtimerState *s = opaque;
+ uint32_t frame;
+
+ switch (offset) {
+ case QCT_QTIMER_AC_CNTFRQ:
+ if (value == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n",
+ __func__);
+ return;
+ }
+ s->freq_hz = value;
+ return;
+ case QCT_QTIMER_AC_CNTSR:
+ if (value > 0xff) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n",
+ __func__, (int)value);
+ return;
+ }
+ s->secure = value;
+ return;
+ case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
+ frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
+ if (frame >= s->nr_frames) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
+ __func__, (int)offset);
+ return;
+ }
+ s->timer[frame].cnt_ctrl = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+ (int)offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps qct_qtimer_ac_ops = {
+ .read = qct_qtimer_ac_read,
+ .write = qct_qtimer_ac_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * View region: a flat array of (frame, view) slots, each frame_stride
+ * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL
+ * register set.
+ */
+static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset,
+ uint32_t *reg_offset,
+ uint32_t *view)
+{
+ uint32_t stride = s->frame_stride;
+ uint32_t stride_shift = ctz32(stride);
+ uint32_t slot_nr = offset >> stride_shift;
+ uint32_t frame = slot_nr / s->nr_views;
+
+ *reg_offset = offset & (stride - 1);
+ *view = slot_nr % s->nr_views;
+ if (frame >= s->nr_frames) {
+ return NULL;
+ }
+ return &s->timer[frame];
+}
+
+/* Frames 8+ share cnttid_1; each frame's second view is gated by a bit. */
+static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame,
+ uint32_t view)
+{
+ uint32_t cnttid = frame < 8 ? s->cnttid_0 : s->cnttid_1;
+ uint32_t frame_idx = frame < 8 ? frame : frame - 8;
+
+ return !view || (cnttid & (0x4 << (frame_idx * 4)));
+}
+
+static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data,
+ unsigned size, MemTxAttrs attrs)
+{
+ QCTQtimerState *qs = opaque;
+ uint32_t reg_offset;
+ uint32_t view;
+ QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
+ uint32_t frame;
+
+ if (!s) {
+ *data = 0;
+ return MEMTX_ACCESS_ERROR;
+ }
+ frame = s - qs->timer;
+
+ trace_qtimer_read(offset);
+
+ if (!qct_qtimer_view_visible(qs, frame, view)) {
+ *data = 0;
+ return MEMTX_OK;
+ }
+
+ switch (reg_offset) {
+ case QCT_QTIMER_CNT_FREQ:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RFRQ)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & (QCT_QTIMER_CNTPL0ACR_PL0PCTEN |
+ QCT_QTIMER_CNTPL0ACR_PL0VCTEN))) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ *data = s->qtimer->freq_hz;
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTP_CVAL_LO:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ *data = extract64(s->cntval, 0, 32);
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTP_CVAL_HI:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
+ *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS);
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTPCT_LO:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ *data = extract64(hex_timer_now(s), 0, 32);
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTPCT_HI:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS);
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTP_TVAL:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s));
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTP_CTL:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ /*
+ * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt
+ * pending). ISTAT tracks int_level and is read-only.
+ */
+ *data = s->control | ((s->int_level & 0x1) << 2);
+ return MEMTX_OK;
+ case QCT_QTIMER_CNTPL0ACR:
+ *data = view ? 0 : s->cntpl0acr;
+ return MEMTX_OK;
+ case QCT_QTIMER_VERSION:
+ *data = QCT_QTIMER_VERSION_VALUE;
+ return MEMTX_OK;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+ (int)offset);
+ *data = 0;
+ return MEMTX_ACCESS_ERROR;
+ }
+}
+
+static MemTxResult hex_timer_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ QCTQtimerState *qs = opaque;
+ uint32_t reg_offset;
+ uint32_t view;
+ QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
+ uint32_t frame;
+
+ if (!s) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ frame = s - qs->timer;
+
+ trace_qtimer_write(offset, value);
+
+ if (!qct_qtimer_view_visible(qs, frame, view)) {
+ return MEMTX_OK;
+ }
+
+ switch (reg_offset) {
+ case QCT_QTIMER_CNTP_CVAL_LO:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ s->int_level = 0;
+ s->cntval = deposit64(s->cntval, 0, 32, value);
+ hex_timer_rearm(s);
+ break;
+ case QCT_QTIMER_CNTP_CVAL_HI:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ s->int_level = 0;
+ /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
+ s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) &
+ QCT_QTIMER_CNT_MASK;
+ hex_timer_rearm(s);
+ break;
+ case QCT_QTIMER_CNTP_CTL:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */
+ s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT;
+ hex_timer_rearm(s);
+ break;
+ case QCT_QTIMER_CNTP_TVAL:
+ if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */
+ s->int_level = 0;
+ s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) &
+ QCT_QTIMER_CNT_MASK;
+ hex_timer_rearm(s);
+ break;
+ case QCT_QTIMER_CNTPL0ACR:
+ if (!view) {
+ s->cntpl0acr = value;
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+ (int)offset);
+ return MEMTX_ACCESS_ERROR;
+ }
+ hex_timer_update(s);
+ return MEMTX_OK;
+}
+
+static void hex_timer_tick(void *opaque)
+{
+ QCTHextimerState *s = opaque;
+ uint64_t now = hex_timer_now(s);
+ uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK;
+ int64_t signed_diff = (int64_t)(diff56 << 8) >> 8;
+
+ if (signed_diff >= 0) {
+ s->int_level = 1;
+ hex_timer_update(s);
+ } else {
+ hex_timer_rearm(s);
+ }
+}
+
+static const MemoryRegionOps hex_timer_ops = {
+ .read_with_attrs = hex_timer_read,
+ .write_with_attrs = hex_timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ .unaligned = false,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_qct_hextimer = {
+ .name = "qct-hextimer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(control, QCTHextimerState),
+ VMSTATE_UINT32(cnt_ctrl, QCTHextimerState),
+ VMSTATE_INT64(offset_ns, QCTHextimerState),
+ VMSTATE_UINT64(cntval, QCTHextimerState),
+ VMSTATE_UINT32(cntpl0acr, QCTHextimerState),
+ VMSTATE_UINT32(int_level, QCTHextimerState),
+ VMSTATE_TIMER_PTR(timer, QCTHextimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_qct_qtimer = {
+ .name = "qct-qtimer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(freq_hz, QCTQtimerState),
+ VMSTATE_UINT32(secure, QCTQtimerState),
+ VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames,
+ 1, vmstate_qct_hextimer, QCTHextimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void qct_qtimer_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ QCTQtimerState *s = QCT_QTIMER(dev);
+ unsigned int i;
+
+ if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) {
+ error_setg(errp, "nr_frames too high");
+ return;
+ }
+ if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) {
+ error_setg(errp, "nr_views too high");
+ return;
+ }
+ if (s->freq_hz == 0) {
+ error_setg(errp, "freq-hz must be nonzero");
+ return;
+ }
+ if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) {
+ error_setg(errp, "frame_stride must be a nonzero power of two");
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s,
+ "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s,
+ "qct-qtimer-view",
+ (uint64_t)s->frame_stride * s->nr_frames *
+ s->nr_views);
+ sysbus_init_mmio(sbd, &s->view_iomem);
+
+ for (i = 0; i < s->nr_frames; i++) {
+ QCTHextimerState *t = &s->timer[i];
+
+ t->qtimer = s;
+ s->secure |= (1 << i);
+
+ sysbus_init_irq(sbd, &t->irq);
+ t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t);
+ }
+}
+
+static void qct_qtimer_unrealize(DeviceState *dev)
+{
+ QCTQtimerState *s = QCT_QTIMER(dev);
+ unsigned int i;
+
+ for (i = 0; i < s->nr_frames; i++) {
+ QCTHextimerState *t = &s->timer[i];
+
+ if (t->timer) {
+ timer_free(t->timer);
+ t->timer = NULL;
+ }
+ }
+}
+
+static void qct_qtimer_reset_hold(Object *obj, ResetType type)
+{
+ QCTQtimerState *s = QCT_QTIMER(obj);
+ unsigned int i;
+
+ for (i = 0; i < s->nr_frames; i++) {
+ QCTHextimerState *t = &s->timer[i];
+
+ /*
+ * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that
+ * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is
+ * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a
+ * spurious fire before SW programs the compare value.
+ */
+ t->control = 0;
+ t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT |
+ QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ |
+ QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT;
+ t->cntval = 0;
+ t->cntpl0acr = 0;
+ t->int_level = 0;
+ t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ timer_del(t->timer);
+ qemu_set_irq(t->irq, 0);
+ }
+}
+
+static const Property qct_qtimer_properties[] = {
+ DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz,
+ QTIMER_DEFAULT_FREQ_HZ),
+ DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1),
+ DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2),
+ DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1),
+ DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000),
+ DEFINE_PROP_UINT32("cnttid_0", QCTQtimerState, cnttid_0, 0x11),
+ DEFINE_PROP_UINT32("cnttid_1", QCTQtimerState, cnttid_1, 0x0),
+};
+
+static void qct_qtimer_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ device_class_set_props(dc, qct_qtimer_properties);
+ dc->realize = qct_qtimer_realize;
+ dc->unrealize = qct_qtimer_unrealize;
+ dc->vmsd = &vmstate_qct_qtimer;
+ rc->phases.hold = qct_qtimer_reset_hold;
+}
+
+/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */
+static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj)
+{
+ QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
+
+ return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32)
+ : 0;
+}
+
+static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj)
+{
+ QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
+
+ return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32)
+ : 0;
+}
+
+static void qct_qtimer_interface_class_init(ObjectClass *klass,
+ const void *data)
+{
+ QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass);
+
+ k->get_timer_lo = qct_qtimer_get_timer_lo_impl;
+ k->get_timer_hi = qct_qtimer_get_timer_hi_impl;
+}
+
+static const TypeInfo qct_qtimer_types[] = {
+ {
+ .name = TYPE_QCT_QTIMER_INTERFACE,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(QctQtimerInterfaceClass),
+ .class_init = qct_qtimer_interface_class_init,
+ },
+ {
+ .name = TYPE_QCT_QTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(QCTQtimerState),
+ .class_init = qct_qtimer_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_QCT_QTIMER_INTERFACE },
+ { }
+ },
+ },
+};
+
+DEFINE_TYPES(qct_qtimer_types)
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index b3d823ce2c3..e1b751a54a7 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -65,3 +65,6 @@ config STELLARIS_GPTM
config AVR_TIMER16
bool
+
+config HEX_QTIMER
+ bool
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 201b5d8316d..0e8326b0492 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -34,3 +34,5 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
+
+specific_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
diff --git a/hw/timer/trace-events b/hw/timer/trace-events
index 634ba1da27a..636310f8caa 100644
--- a/hw/timer/trace-events
+++ b/hw/timer/trace-events
@@ -128,3 +128,8 @@ imx_epit_get_freq(uint32_t freq) "ptimer frequency is %u"
imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x"
imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")"
imx_epit_cmp(uint32_t sr) "sr was %d"
+
+# qct-qtimer.c
+qtimer_interrupt(void) "qtimer interrupt line updated"
+qtimer_read(uint64_t offset) "offset 0x%" PRIx64
+qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 16/18] hw/hexagon: connect qtimer device
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (14 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 15/18] hw/timer: Add QCT QTimer device model Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:36 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest Brian Cain
2026-07-30 1:28 ` [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases Brian Cain
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add the QTimer to the shared hex-subsys so both machine models pick it
up. Map its view region, wire its interrupt lines into l2vic, and link
it to the globalreg device backing HEX_SREG_TIMERLO/TIMERHI.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
include/hw/hexagon/hexagon.h | 1 +
include/hw/hexagon/hexagon_globalreg.h | 4 ++++
hw/hexagon/hex-subsys.c | 29 ++++++++++++++++++++++++++
hw/hexagon/hexagon_globalreg.c | 17 +++++++++++++++
hw/hexagon/Kconfig | 1 +
5 files changed, 52 insertions(+)
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index ec1578807d8..3d7b3cb12dc 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -159,6 +159,7 @@ struct HexagonCommonMachineState {
MemoryRegion vtcm;
DeviceState *cluster;
DeviceState *l2vic;
+ DeviceState *qtimer;
DeviceState *glob_regs;
DeviceState *tlb;
};
diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h
index 397dc6854c9..07437dfabb1 100644
--- a/include/hw/hexagon/hexagon_globalreg.h
+++ b/include/hw/hexagon/hexagon_globalreg.h
@@ -11,6 +11,7 @@
#include "hw/core/qdev.h"
#include "hw/core/sysbus.h"
#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
#include "qom/object.h"
#include "target/hexagon/cpu.h"
@@ -26,6 +27,9 @@ struct HexagonGlobalRegState {
/* L2VIC interface used to back the VID/VID1 registers */
HexL2VicInterface *l2vic;
+ /* QTimer interface used to back the TIMERLO/TIMERHI registers */
+ QctQtimerInterface *qtimer;
+
/* Global performance cycle counter base */
uint64_t g_pcycle_base;
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index 25c028f6579..12d4ec72d38 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -11,6 +11,7 @@
#include "hw/hexagon/hexagon_globalreg.h"
#include "hw/hexagon/hexagon_tlb.h"
#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
#include "hw/cpu/cluster.h"
#include "hw/core/loader.h"
#include "hw/core/qdev-properties.h"
@@ -20,6 +21,13 @@
#define HEX_L2VIC_CPU_IRQS 8
+/* L2VIC input lines the QTimer's two frames are wired to. */
+#define HEX_QTIMER_L2VIC_IRQ0 3
+#define HEX_QTIMER_L2VIC_IRQ1 4
+
+/* Number of QTimer frames instantiated for every Hexagon machine. */
+#define HEX_QTIMER_NR_FRAMES 3
+
static DeviceState *l2vic_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg)
{
@@ -43,6 +51,24 @@ static void l2vic_connect_cpu(DeviceState *l2vic, DeviceState *cpu)
}
}
+static DeviceState *qtimer_create(HexagonCommonMachineState *hms,
+ const struct hexagon_machine_config *m_cfg)
+{
+ DeviceState *qtimer = qdev_new(TYPE_QCT_QTIMER);
+
+ object_property_add_child(OBJECT(hms), "qtimer", OBJECT(qtimer));
+ qdev_prop_set_uint32(qtimer, "nr_frames", HEX_QTIMER_NR_FRAMES);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(qtimer), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 0, m_cfg->csr_base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_region);
+ sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 0,
+ qdev_get_gpio_in(hms->l2vic, HEX_QTIMER_L2VIC_IRQ0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 1,
+ qdev_get_gpio_in(hms->l2vic, HEX_QTIMER_L2VIC_IRQ1));
+
+ return qtimer;
+}
+
static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
const struct hexagon_machine_config *m_cfg,
Rev_t rev)
@@ -54,6 +80,8 @@ static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
qdev_prop_set_uint32(glob_regs, "dsp-rev", rev);
object_property_set_link(OBJECT(glob_regs), "l2vic", OBJECT(hms->l2vic),
&error_fatal);
+ object_property_set_link(OBJECT(glob_regs), "qtimer", OBJECT(hms->qtimer),
+ &error_fatal);
sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs), &error_fatal);
return glob_regs;
@@ -110,6 +138,7 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
hms->cluster = cluster_create(hms);
hms->l2vic = l2vic_create(hms, m_cfg);
+ hms->qtimer = qtimer_create(hms, m_cfg);
hms->glob_regs = globalreg_create(hms, m_cfg, rev);
hms->tlb = tlb_create(hms, m_cfg);
}
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c
index 285cb48c44b..61621cf2b8f 100644
--- a/hw/hexagon/hexagon_globalreg.c
+++ b/hw/hexagon/hexagon_globalreg.c
@@ -12,6 +12,7 @@
#include "hw/core/sysbus.h"
#include "hw/core/resettable.h"
#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
#include "migration/vmstate.h"
#include "qom/object.h"
#include "target/hexagon/cpu.h"
@@ -141,11 +142,21 @@ static inline bool is_vid_reg(uint32_t reg)
return reg == HEX_SREG_VID || reg == HEX_SREG_VID1;
}
+static inline bool is_timer_reg(uint32_t reg)
+{
+ return reg == HEX_SREG_TIMERLO || reg == HEX_SREG_TIMERHI;
+}
+
static uint32_t get_reg_value(HexagonGlobalRegState *s, uint32_t reg)
{
if (is_vid_reg(reg)) {
return l2vic_read_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1);
}
+ if (is_timer_reg(reg)) {
+ return reg == HEX_SREG_TIMERLO ?
+ qct_qtimer_get_timer_lo(s->qtimer) :
+ qct_qtimer_get_timer_hi(s->qtimer);
+ }
return s->regs[reg];
}
@@ -289,6 +300,10 @@ static void hexagon_globalreg_realize(DeviceState *dev, Error **errp)
error_setg(errp, "hexagon_globalreg: 'l2vic' link property not set");
return;
}
+ if (!s->qtimer) {
+ error_setg(errp, "hexagon_globalreg: 'qtimer' link property not set");
+ return;
+ }
}
static const VMStateDescription vmstate_hexagon_globalreg = {
@@ -312,6 +327,8 @@ static const VMStateDescription vmstate_hexagon_globalreg = {
static const Property hexagon_globalreg_properties[] = {
DEFINE_PROP_LINK("l2vic", HexagonGlobalRegState, l2vic,
TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
+ DEFINE_PROP_LINK("qtimer", HexagonGlobalRegState, qtimer,
+ TYPE_QCT_QTIMER_INTERFACE, QctQtimerInterface *),
DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0),
DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState,
config_table_addr, 0xffffffffULL),
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index c75090c44a1..83b2763d1e3 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -4,6 +4,7 @@ config HEX_DSP
depends on HEXAGON
select CPU_CLUSTER
select HEX_L2VIC
+ select HEX_QTIMER
config HEX_VIRT
bool
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (15 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 16/18] hw/hexagon: connect qtimer device Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:38 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases Brian Cain
17 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add a qtest exercising the QCT QTimer's register access, view-region
frame addressing, and one-shot timer firing behavior.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 1 +
tests/qtest/qct-qtimer-test.c | 269 ++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +-
3 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/qct-qtimer-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index de106416b64..0e0b99084df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -255,6 +255,7 @@ F: include/hw/intc/hex-l2vic.h
F: tests/qtest/l2vic-test.c
F: hw/timer/qct-qtimer.c
F: include/hw/timer/qct-qtimer.h
+F: tests/qtest/qct-qtimer-test.c
X: target/hexagon/idef-parser/
X: target/hexagon/gen_idef_parser_funcs.py
F: linux-user/hexagon/
diff --git a/tests/qtest/qct-qtimer-test.c b/tests/qtest/qct-qtimer-test.c
new file mode 100644
index 00000000000..3ac0c96bb70
--- /dev/null
+++ b/tests/qtest/qct-qtimer-test.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTest testcase for the QCT QTimer
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "hw/hexagon/hexagon.h"
+#include "qemu/bitops.h"
+
+#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
+#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
+
+#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
+
+#define QCT_QTIMER_CNTPCT_LO (0x000)
+#define QCT_QTIMER_CNT_FREQ (0x010)
+#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
+#define QCT_QTIMER_CNTP_TVAL (0x028)
+#define QCT_QTIMER_CNTP_CTL (0x02c)
+
+#define QCT_QTIMER_AC_CNTFRQ (0x000)
+#define QCT_QTIMER_AC_CNTSR (0x004)
+#define QCT_QTIMER_AC_CNTTID_0 (0x08)
+#define QCT_QTIMER_AC_CNTACR_START (0x40)
+
+static uint64_t qtimer_view_base;
+static uint64_t qtimer_ac_base;
+
+#define TIMER_TEST_OFFSET 1000
+/* TIMER_TEST_OFFSET ticks expressed in nanoseconds of QEMU_CLOCK_VIRTUAL */
+#define TIMER_TEST_NS \
+ ((TIMER_TEST_OFFSET * 1000000000ULL) / QTIMER_DEFAULT_FREQ_HZ)
+
+static uint32_t qtimer_read32(uint64_t base, uint32_t offset)
+{
+ return readl(base + offset);
+}
+
+static void qtimer_write32(uint64_t base, uint32_t offset, uint32_t value)
+{
+ writel(base + offset, value);
+}
+
+static uint64_t qtimer_read64(uint64_t base, uint32_t offset)
+{
+ uint32_t lo = qtimer_read32(base, offset);
+ uint32_t hi = qtimer_read32(base, offset + 4);
+
+ return ((uint64_t)hi << 32) | lo;
+}
+
+static void qtimer_write64(uint64_t base, uint32_t offset, uint64_t value)
+{
+ qtimer_write32(base, offset, extract64(value, 0, 32));
+ qtimer_write32(base, offset + 4, extract64(value, 32, 32));
+}
+
+static void test_qtimer_basic_access(void)
+{
+ uint32_t val;
+
+ val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_multiple_frames(void)
+{
+ uint32_t val;
+ uint64_t frame0_base = qtimer_view_base;
+ uint64_t frame1_base = qtimer_view_base + 0x1000;
+
+ val = qtimer_read32(frame0_base, QCT_QTIMER_CNT_FREQ);
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+ val = qtimer_read32(frame1_base, QCT_QTIMER_CNT_FREQ);
+ g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_register_reads(void)
+{
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+ qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL);
+}
+
+static void test_qtimer_control_registers(void)
+{
+ uint32_t ctl_val;
+ uint64_t cval_before, cval_after;
+
+ cval_before = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 1000);
+
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
+ ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ g_assert_cmpuint(ctl_val & 1, ==, 1);
+
+ /* CVAL should be greater than before since we set TVAL */
+ cval_after = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+ g_assert_cmpuint(cval_after, >, cval_before);
+
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
+ ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ g_assert_cmpuint(ctl_val & 1, ==, 0);
+}
+
+static void test_qtimer_cval_access(void)
+{
+ uint64_t current_time, test_cval, read_cval;
+
+ current_time = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ test_cval = current_time + 10000;
+
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, test_cval);
+ read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+ g_assert_cmpuint(read_cval, ==, test_cval);
+}
+
+static void test_qtimer_counter_progression(void)
+{
+ uint32_t freq;
+ uint64_t count1, count2;
+
+ /*
+ * In qtest mode the virtual clock does not advance on its own, so
+ * reading the counter twice must give the same value.
+ */
+ count1 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ count2 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ g_assert_cmpuint(count2, ==, count1);
+
+ freq = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_timer_behavior(void)
+{
+ uint64_t current_count, target_count, read_cval, new_count;
+ uint64_t ctl_val, count_after_disable;
+
+ current_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+
+ target_count = current_count + TIMER_TEST_OFFSET;
+ qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, target_count);
+
+ read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+ g_assert_cmpuint(read_cval, ==, target_count);
+
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
+
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ /* EN set, IMASK clear, ISTAT not yet pending */
+ g_assert_cmpuint(ctl_val, ==, 0x1);
+
+ /* Step forward but not past the target */
+ qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
+ new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ g_assert_cmpuint(new_count, >=, current_count);
+
+ /* Step past the target */
+ qtest_clock_step(global_qtest, TIMER_TEST_NS);
+ new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+ g_assert_cmpuint(new_count, >=, target_count);
+
+ qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
+
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ /* EN cleared, ISTAT set since new_count >= target_count */
+ g_assert_cmpuint(ctl_val, ==, 0x4);
+
+ /*
+ * CNTPCT runs independently of CNTP_CTL.EN: only the compare/IRQ
+ * logic is gated by EN, so the counter must keep advancing.
+ */
+ qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
+ count_after_disable = qtimer_read64(qtimer_view_base,
+ QCT_QTIMER_CNTPCT_LO);
+ g_assert_cmpuint(count_after_disable, >, new_count);
+
+ /* ISTAT remains set while CNTPCT >= CVAL, even with EN=0 */
+ ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+ g_assert_cmpuint(ctl_val, ==, 0x4);
+}
+
+/* Test the access-control region: CNTFRQ, CNTSR, CNTTID_0, CNTACR frame 0 */
+static void test_qtimer_ac_region(void)
+{
+ uint32_t freq, sr, tid0, acr0;
+
+ freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+ /* A write of 0 to CNTFRQ must be ignored (freq-hz must stay nonzero). */
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ, 0);
+ freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
+ g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR, 0x3);
+ sr = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR);
+ g_assert_cmpuint(sr, ==, 0x3);
+
+ tid0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTTID_0);
+ g_assert_cmpuint(tid0, ==, 0x11);
+
+ /* CNTACR for frame 0 defaults to full read/write permissions. */
+ acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
+ g_assert_cmpuint(acr0, !=, 0);
+
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, 0);
+ acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
+ g_assert_cmpuint(acr0, ==, 0);
+
+ /* Restore full permissions so later view-region tests keep working. */
+ qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, 0x3f);
+}
+
+typedef struct {
+ const char *machine;
+ const struct hexagon_machine_config *cfg;
+} QtimerMachineCfg;
+
+static const QtimerMachineCfg qtimer_machines[] = {
+ { "virt", &v68n_1024 },
+ { "V66G_1024", &v66g_1024 },
+};
+
+static void test_qtimer_on_machine(gconstpointer data)
+{
+ const QtimerMachineCfg *mc = data;
+ g_autofree char *args = g_strdup_printf(
+ "-machine %s -global qct-qtimer.freq-scale=1", mc->machine);
+
+ qtimer_view_base = mc->cfg->qtmr_region;
+ qtimer_ac_base = mc->cfg->csr_base;
+
+ qtest_start(args);
+
+ test_qtimer_basic_access();
+ test_qtimer_multiple_frames();
+ test_qtimer_register_reads();
+ test_qtimer_control_registers();
+ test_qtimer_cval_access();
+ test_qtimer_counter_progression();
+ test_qtimer_timer_behavior();
+ test_qtimer_ac_region();
+
+ qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+ size_t i;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; i < ARRAY_SIZE(qtimer_machines); i++) {
+ g_autofree char *path = g_strdup_printf("/qct-qtimer/%s/all-tests",
+ qtimer_machines[i].machine);
+ qtest_add_data_func(path, &qtimer_machines[i], test_qtimer_on_machine);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index a76863c7dd6..651246b2ebb 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -299,7 +299,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
['iommu-riscv-test'] : []) + \
(config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
-qtests_hexagon = ['boot-serial-test', 'l2vic-test']
+qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test']
qos_test_ss = ss.source_set()
qos_test_ss.add(
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
` (16 preceding siblings ...)
2026-07-30 1:28 ` [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest Brian Cain
@ 2026-07-30 1:28 ` Brian Cain
2026-07-31 17:39 ` Pierrick Bouvier
2026-07-31 17:42 ` Pierrick Bouvier
17 siblings, 2 replies; 33+ messages in thread
From: Brian Cain @ 2026-07-30 1:28 UTC (permalink / raw)
To: qemu-devel
Cc: philmd, Pierrick Bouvier, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Brian Cain, matheus.bernardino
Add more tests from hexagon-arch-tests, enabled by QTimer device.
These exercise cache maintenance ops, l2vic, thread start/stop, tlb/mmu
operations, and user-mode transitions.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
tests/functional/hexagon/test_arch_tests.py | 32 +++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
index 2bb34f9b8dc..0834398c3b1 100755
--- a/tests/functional/hexagon/test_arch_tests.py
+++ b/tests/functional/hexagon/test_arch_tests.py
@@ -58,6 +58,38 @@ def test_int_steering(self) -> None:
"""
self.run_uart_test("test_int_steering")
+ def test_cache(self) -> None:
+ """Tests cache operations: dckill/ickill, l2kill, dczeroa,
+ dccleaninva, cache disable/enable, barriers, and dcinva/dccleana.
+ """
+ self.run_uart_test("test_cache")
+
+ def test_l2vic(self) -> None:
+ """Tests the L2VIC interrupt controller: enable readback,
+ interrupt type readback, VID capture, and the fast interface.
+ """
+ self.run_uart_test("test_l2vic")
+
+ def test_threads(self) -> None:
+ """Tests hardware thread management: start/stop, MODECTL state,
+ per-thread HTID, shared memory, wait/resume, STID priority, and
+ SCHEDCFG/BESTWAIT readback.
+ """
+ self.run_uart_test("test_threads")
+
+ def test_tlb_mmu(self) -> None:
+ """Tests TLB/MMU operations: write/read/probe/invalidate,
+ global entries, multiple entries, overwrite, ASID matching,
+ and permission checks.
+ """
+ self.run_uart_test("test_tlb_mmu")
+
+ def test_user_mode(self) -> None:
+ """Tests user mode / privilege transitions: supervisor mode,
+ SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler.
+ """
+ self.run_uart_test("test_user_mode")
+
if __name__ == "__main__":
QemuSystemTest.main()
--
2.34.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH v4 01/18] hw/hexagon: add hex-subsys
2026-07-30 1:28 ` [PATCH v4 01/18] hw/hexagon: add hex-subsys Brian Cain
@ 2026-07-31 17:28 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:28 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> The virt and DSP machine models build the same core subsystem, let's
> abstract out that part. Start with the DDR and config table ROM setup.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hex-subsys.h | 17 +++++++++++++++++
> hw/hexagon/hex-subsys.c | 32 ++++++++++++++++++++++++++++++++
> hw/hexagon/hexagon_dsp.c | 17 ++---------------
> hw/hexagon/virt.c | 14 ++------------
> hw/hexagon/meson.build | 1 +
> 5 files changed, 54 insertions(+), 27 deletions(-)
> create mode 100644 include/hw/hexagon/hex-subsys.h
> create mode 100644 hw/hexagon/hex-subsys.c
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state
2026-07-30 1:28 ` [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state Brian Cain
@ 2026-07-31 17:29 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:29 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> The VTCM is described by the config table, so every machine can set it
> up the same way.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hexagon.h | 1 +
> include/hw/hexagon/virt.h | 1 -
> hw/hexagon/hex-subsys.c | 8 ++++++++
> hw/hexagon/virt.c | 5 -----
> 4 files changed, 9 insertions(+), 6 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys
2026-07-30 1:28 ` [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys Brian Cain
@ 2026-07-31 17:29 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:29 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Both machines create the global register device the same way, so let
> hex-subsys own it and link it to each CPU as it is realized.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hex-subsys.h | 6 +++++-
> include/hw/hexagon/hexagon.h | 1 +
> hw/hexagon/hex-subsys.c | 29 ++++++++++++++++++++++++++++-
> hw/hexagon/hexagon_dsp.c | 15 ++-------------
> hw/hexagon/virt.c | 23 +++++------------------
> 5 files changed, 41 insertions(+), 33 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 04/18] hw/hexagon: move the TLB to hex-subsys
2026-07-30 1:28 ` [PATCH v4 04/18] hw/hexagon: move the TLB " Brian Cain
@ 2026-07-31 17:30 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:30 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> The TLB device is sized from the config table, so both machines create
> it identically.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hexagon.h | 1 +
> hw/hexagon/hex-subsys.c | 16 ++++++++++++++++
> hw/hexagon/hexagon_dsp.c | 10 ----------
> hw/hexagon/virt.c | 10 ----------
> 4 files changed, 17 insertions(+), 20 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster
2026-07-30 1:28 ` [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster Brian Cain
@ 2026-07-31 17:34 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:34 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> The CPUs are now grouped in a TYPE_CPU_CLUSTER.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hex-subsys.h | 12 +++++++++++-
> include/hw/hexagon/hexagon.h | 1 +
> hw/hexagon/hex-subsys.c | 30 +++++++++++++++++++++++++++++-
> hw/hexagon/hexagon_dsp.c | 11 ++++++++++-
> hw/hexagon/virt.c | 11 ++++++++++-
> hw/hexagon/Kconfig | 1 +
> 6 files changed, 62 insertions(+), 4 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes
2026-07-30 1:28 ` [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes Brian Cain
@ 2026-07-31 17:35 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:35 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Expose l2vic as a device-tree interrupt-controller node and reference
> it via interrupt-parent, so guest kernels can discover the virtio-mmio
> transports.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> hw/hexagon/virt.c | 43 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 41 insertions(+), 2 deletions(-)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 16/18] hw/hexagon: connect qtimer device
2026-07-30 1:28 ` [PATCH v4 16/18] hw/hexagon: connect qtimer device Brian Cain
@ 2026-07-31 17:36 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:36 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Add the QTimer to the shared hex-subsys so both machine models pick it
> up. Map its view region, wire its interrupt lines into l2vic, and link
> it to the globalreg device backing HEX_SREG_TIMERLO/TIMERHI.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> include/hw/hexagon/hexagon.h | 1 +
> include/hw/hexagon/hexagon_globalreg.h | 4 ++++
> hw/hexagon/hex-subsys.c | 29 ++++++++++++++++++++++++++
> hw/hexagon/hexagon_globalreg.c | 17 +++++++++++++++
> hw/hexagon/Kconfig | 1 +
> 5 files changed, 52 insertions(+)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest
2026-07-30 1:28 ` [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest Brian Cain
@ 2026-07-31 17:38 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:38 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Add a qtest exercising the QCT QTimer's register access, view-region
> frame addressing, and one-shot timer firing behavior.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> MAINTAINERS | 1 +
> tests/qtest/qct-qtimer-test.c | 269 ++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 2 +-
> 3 files changed, 271 insertions(+), 1 deletion(-)
> create mode 100644 tests/qtest/qct-qtimer-test.c
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases
2026-07-30 1:28 ` [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases Brian Cain
@ 2026-07-31 17:39 ` Pierrick Bouvier
2026-07-31 17:42 ` Pierrick Bouvier
1 sibling, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:39 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Add more tests from hexagon-arch-tests, enabled by QTimer device.
>
> These exercise cache maintenance ops, l2vic, thread start/stop, tlb/mmu
> operations, and user-mode transitions.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> tests/functional/hexagon/test_arch_tests.py | 32 +++++++++++++++++++++
> 1 file changed, 32 insertions(+)
>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases
2026-07-30 1:28 ` [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases Brian Cain
2026-07-31 17:39 ` Pierrick Bouvier
@ 2026-07-31 17:42 ` Pierrick Bouvier
2026-08-03 15:43 ` Brian Cain
1 sibling, 1 reply; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:42 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Add more tests from hexagon-arch-tests, enabled by QTimer device.
>
> These exercise cache maintenance ops, l2vic, thread start/stop, tlb/mmu
> operations, and user-mode transitions.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> tests/functional/hexagon/test_arch_tests.py | 32 +++++++++++++++++++++
> 1 file changed, 32 insertions(+)
>
> diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
> index 2bb34f9b8dc..0834398c3b1 100755
> --- a/tests/functional/hexagon/test_arch_tests.py
> +++ b/tests/functional/hexagon/test_arch_tests.py
> @@ -58,6 +58,38 @@ def test_int_steering(self) -> None:
> """
> self.run_uart_test("test_int_steering")
>
> + def test_cache(self) -> None:
> + """Tests cache operations: dckill/ickill, l2kill, dczeroa,
> + dccleaninva, cache disable/enable, barriers, and dcinva/dccleana.
> + """
> + self.run_uart_test("test_cache")
> +
> + def test_l2vic(self) -> None:
> + """Tests the L2VIC interrupt controller: enable readback,
> + interrupt type readback, VID capture, and the fast interface.
> + """
> + self.run_uart_test("test_l2vic")
> +
> + def test_threads(self) -> None:
> + """Tests hardware thread management: start/stop, MODECTL state,
> + per-thread HTID, shared memory, wait/resume, STID priority, and
> + SCHEDCFG/BESTWAIT readback.
> + """
> + self.run_uart_test("test_threads")
> +
> + def test_tlb_mmu(self) -> None:
> + """Tests TLB/MMU operations: write/read/probe/invalidate,
> + global entries, multiple entries, overwrite, ASID matching,
> + and permission checks.
> + """
> + self.run_uart_test("test_tlb_mmu")
> +
> + def test_user_mode(self) -> None:
> + """Tests user mode / privilege transitions: supervisor mode,
> + SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler.
> + """
> + self.run_uart_test("test_user_mode")
> +
>
> if __name__ == "__main__":
> QemuSystemTest.main()
A general question on the pattern we have here.
If those tests can be compiled with hexagon-cross container, would it
make sense to add them to tcg/tests/hexagon/system directly in the future?
Hopefully will be more easy once we have meson tcg-tests, so you don't
need to add all dependencies by hand.
Regards,
Pierrick
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 15/18] hw/timer: Add QCT QTimer device model
2026-07-30 1:28 ` [PATCH v4 15/18] hw/timer: Add QCT QTimer device model Brian Cain
@ 2026-07-31 17:59 ` Pierrick Bouvier
2026-08-05 21:10 ` Brian Cain
0 siblings, 1 reply; 33+ messages in thread
From: Pierrick Bouvier @ 2026-07-31 17:59 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino, Damien Hedde, Tobias Röhmel, Sid Manning,
Thomas Marceron, Mahmoud Kamel
On 7/29/2026 6:28 PM, Brian Cain wrote:
> Implement the QCT QTimer generic timer device used by Hexagon DSP
> systems.
>
> Co-authored-by: Damien Hedde <damien.hedde@greensocs.com>
> Co-authored-by: Tobias Röhmel <quic_trohmel@quicinc.com>
> Co-authored-by: Sid Manning <sidneym@quicinc.com>
> Co-authored-by: Thomas Marceron <tmarcero@qti.qualcomm.com>
> Co-authored-by: Mahmoud Kamel <mkamel@qti.qualcomm.com>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> MAINTAINERS | 2 +
> include/hw/timer/qct-qtimer.h | 43 +++
> hw/timer/qct-qtimer.c | 681 ++++++++++++++++++++++++++++++++++
> hw/timer/Kconfig | 3 +
> hw/timer/meson.build | 2 +
> hw/timer/trace-events | 5 +
> 6 files changed, 736 insertions(+)
> create mode 100644 include/hw/timer/qct-qtimer.h
> create mode 100644 hw/timer/qct-qtimer.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 114bbec45ad..de106416b64 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -253,6 +253,8 @@ F: target/hexagon/
> F: hw/intc/hex-l2vic.c
> F: include/hw/intc/hex-l2vic.h
> F: tests/qtest/l2vic-test.c
> +F: hw/timer/qct-qtimer.c
> +F: include/hw/timer/qct-qtimer.h
> X: target/hexagon/idef-parser/
> X: target/hexagon/gen_idef_parser_funcs.py
> F: linux-user/hexagon/
> diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h
> new file mode 100644
> index 00000000000..53d8291472d
> --- /dev/null
> +++ b/include/hw/timer/qct-qtimer.h
> @@ -0,0 +1,43 @@
> +/*
> + * Qualcomm QCT QTimer
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_TIMER_QCT_QTIMER_H
> +#define HW_TIMER_QCT_QTIMER_H
> +
> +#include "qom/object.h"
> +
> +#define TYPE_QCT_QTIMER "qct-qtimer"
> +
> +/* QTimer interface for external access from hexagon_globalreg */
> +#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if"
> +
Why not directly get access to TYPE_QCT_QTIMER?
Is it to ensure there is no other access than get_timer_lo/hi made from
there?
> +typedef struct QctQtimerInterface QctQtimerInterface;
> +
> +typedef struct QctQtimerInterfaceClass {
> + InterfaceClass parent_class;
> +
> + /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */
> + uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer);
> + uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer);
> +} QctQtimerInterfaceClass;
> +
> +DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass,
> + QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE);
> +
> +static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer)
> +{
> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
> + return k->get_timer_lo(qtimer);
> +}
> +
> +static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer)
> +{
> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
> + return k->get_timer_hi(qtimer);
> +}
> +
> +#endif /* HW_TIMER_QCT_QTIMER_H */
> diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c
> new file mode 100644
> index 00000000000..82c711000eb
> --- /dev/null
> +++ b/hw/timer/qct-qtimer.c
> @@ -0,0 +1,681 @@
> +/*
> + * Qualcomm QCT QTimer
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/sysbus.h"
> +#include "hw/timer/qct-qtimer.h"
> +#include "migration/vmstate.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/timer.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#define QTIMER_MEM_SIZE_BYTES 0x1000
> +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
> +
> +#define QCT_QTIMER_TIMER_FRAME_ELTS (16)
> +#define QCT_QTIMER_TIMER_VIEW_ELTS (2)
> +
> +#define QCT_QTIMER_AC_CNTFRQ (0x000)
> +#define QCT_QTIMER_AC_CNTSR (0x004)
> +#define QCT_QTIMER_AC_CNTTID_0 (0x08)
> +#define QCT_QTIMER_AC_CNTACR_START (0x40)
> +#define QCT_QTIMER_AC_CNTACR_END (0x5c)
> +#define QCT_QTIMER_AC_CNTTID_1 (0x108)
> +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
> +#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */
> +#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */
> +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
> +#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */
> +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
> +#define QCT_QTIMER_VERSION (0x0fd0)
> +
> +#define QCT_QTIMER_CNTPCT_LO (0x000)
> +#define QCT_QTIMER_CNTPCT_HI (0x004)
> +#define QCT_QTIMER_CNT_FREQ (0x010)
> +#define QCT_QTIMER_CNTPL0ACR (0x014)
> +#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9)
> +#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8)
> +#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1)
> +#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0)
> +#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
> +#define QCT_QTIMER_CNTP_CVAL_HI (0x024)
> +#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL
> +#define QCT_QTIMER_CNT_HI_BITS 24
> +#define QCT_QTIMER_CNTP_TVAL (0x028)
> +#define QCT_QTIMER_CNTP_CTL (0x02c)
> +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
> +#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1)
> +#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2)
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER)
> +
> +typedef struct QCTHextimerState {
> + QCTQtimerState *qtimer;
> + QEMUTimer *timer; /* one-shot deadline timer */
> + int64_t offset_ns; /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */
> + uint64_t cntval; /* 64-bit physical timer compare value */
> + uint32_t control;
> + uint32_t cnt_ctrl;
> + uint32_t cntpl0acr;
> + uint32_t int_level;
> + qemu_irq irq;
> +} QCTHextimerState;
> +
> +struct QCTQtimerState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + MemoryRegion view_iomem;
> + uint32_t secure;
> + QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS];
> + uint32_t freq_hz;
> + uint32_t nr_frames;
> + uint32_t nr_views;
> + uint32_t frame_stride;
> + uint32_t cnttid_0;
> + uint32_t cnttid_1;
> + uint32_t freq_scale;
> +};
> +
> +/*
> + * QTimer version register:
> + *
> + * 3 2 1
> + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + * | Major | Minor | Step |
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + */
> +#define QCT_QTIMER_VERSION_VALUE 0x20020000
> +
> +/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */
> +static uint64_t hex_timer_now(QCTHextimerState *s)
> +{
> + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + uint32_t scale;
> + uint64_t scaled_elapsed;
> +
> + if (now <= s->offset_ns) {
> + return 0;
> + }
> + scale = MAX(s->qtimer->freq_scale, 1u);
> + scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale;
> + return muldiv64(scaled_elapsed, s->qtimer->freq_hz,
> + NANOSECONDS_PER_SECOND) &
> + QCT_QTIMER_CNT_MASK;
> +}
> +
> +/* Arm (or disarm) the one-shot deadline timer. */
> +static void hex_timer_rearm(QCTHextimerState *s)
> +{
> + uint32_t scale;
> + uint64_t base_ns;
> + int64_t deadline_ns;
> +
> + if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) {
> + timer_del(s->timer);
> + return;
> + }
> +
> + scale = MAX(s->qtimer->freq_scale, 1u);
> + /*
> + * Round the ticks-to-ns conversion up so that hex_timer_now(), which
> + * truncates when it divides elapsed ns by scale, is guaranteed to
> + * report >= cntval once this deadline fires. A truncating conversion
> + * here could re-arm at the same deadline forever when scale > 1.
> + */
> + base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND,
> + s->qtimer->freq_hz);
> + if (base_ns >
> + ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) {
> + timer_del(s->timer);
> + return;
> + }
> + deadline_ns = s->offset_ns + (int64_t)(base_ns * scale);
> + timer_mod(s->timer, deadline_ns);
> +}
> +
> +static void hex_timer_update(QCTHextimerState *s)
> +{
> + int level = s->int_level &&
> + (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) &&
> + !(s->control & QCT_QTIMER_CNTP_CTL_INTEN);
> +
> + trace_qtimer_interrupt();
> + qemu_set_irq(s->irq, level);
> +}
> +
> +/*
> + * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/
> + * CNTTID/CNTACR per frame plus the shared VERSION register.
> + */
> +static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size)
> +{
> + QCTQtimerState *s = opaque;
> + uint32_t frame;
> +
> + switch (offset) {
> + case QCT_QTIMER_AC_CNTFRQ:
> + return s->freq_hz;
> + case QCT_QTIMER_AC_CNTSR:
> + return s->secure;
> + case QCT_QTIMER_AC_CNTTID_0:
> + return s->cnttid_0;
> + case QCT_QTIMER_AC_CNTTID_1:
> + return s->cnttid_1;
> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
> + if (frame >= s->nr_frames) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
> + __func__, (int)offset);
> + return 0;
> + }
> + return s->timer[frame].cnt_ctrl;
> + case QCT_QTIMER_VERSION:
> + return QCT_QTIMER_VERSION_VALUE;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
> + (int)offset);
> + return 0;
> + }
> +}
> +
> +static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value,
> + unsigned size)
> +{
> + QCTQtimerState *s = opaque;
> + uint32_t frame;
> +
> + switch (offset) {
> + case QCT_QTIMER_AC_CNTFRQ:
> + if (value == 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n",
> + __func__);
> + return;
> + }
> + s->freq_hz = value;
> + return;
> + case QCT_QTIMER_AC_CNTSR:
> + if (value > 0xff) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n",
> + __func__, (int)value);
> + return;
> + }
> + s->secure = value;
> + return;
> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
> + if (frame >= s->nr_frames) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
> + __func__, (int)offset);
> + return;
> + }
> + s->timer[frame].cnt_ctrl = value;
> + return;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
> + (int)offset);
> + return;
> + }
> +}
> +
> +static const MemoryRegionOps qct_qtimer_ac_ops = {
> + .read = qct_qtimer_ac_read,
> + .write = qct_qtimer_ac_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + .unaligned = false,
> + },
> + .impl = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +/*
> + * View region: a flat array of (frame, view) slots, each frame_stride
> + * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL
> + * register set.
> + */
> +static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset,
> + uint32_t *reg_offset,
> + uint32_t *view)
> +{
> + uint32_t stride = s->frame_stride;
> + uint32_t stride_shift = ctz32(stride);
> + uint32_t slot_nr = offset >> stride_shift;
> + uint32_t frame = slot_nr / s->nr_views;
> +
> + *reg_offset = offset & (stride - 1);
> + *view = slot_nr % s->nr_views;
> + if (frame >= s->nr_frames) {
> + return NULL;
> + }
> + return &s->timer[frame];
> +}
> +
> +/* Frames 8+ share cnttid_1; each frame's second view is gated by a bit. */
> +static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame,
> + uint32_t view)
> +{
> + uint32_t cnttid = frame < 8 ? s->cnttid_0 : s->cnttid_1;
> + uint32_t frame_idx = frame < 8 ? frame : frame - 8;
> +
> + return !view || (cnttid & (0x4 << (frame_idx * 4)));
> +}
> +
> +static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data,
> + unsigned size, MemTxAttrs attrs)
> +{
> + QCTQtimerState *qs = opaque;
> + uint32_t reg_offset;
> + uint32_t view;
> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
> + uint32_t frame;
> +
> + if (!s) {
> + *data = 0;
> + return MEMTX_ACCESS_ERROR;
> + }
> + frame = s - qs->timer;
> +
> + trace_qtimer_read(offset);
> +
> + if (!qct_qtimer_view_visible(qs, frame, view)) {
> + *data = 0;
> + return MEMTX_OK;
> + }
> +
> + switch (reg_offset) {
> + case QCT_QTIMER_CNT_FREQ:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RFRQ)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & (QCT_QTIMER_CNTPL0ACR_PL0PCTEN |
> + QCT_QTIMER_CNTPL0ACR_PL0VCTEN))) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + *data = s->qtimer->freq_hz;
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTP_CVAL_LO:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + *data = extract64(s->cntval, 0, 32);
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTP_CVAL_HI:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
> + *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS);
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTPCT_LO:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + *data = extract64(hex_timer_now(s), 0, 32);
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTPCT_HI:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS);
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTP_TVAL:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s));
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTP_CTL:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + /*
> + * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt
> + * pending). ISTAT tracks int_level and is read-only.
> + */
> + *data = s->control | ((s->int_level & 0x1) << 2);
> + return MEMTX_OK;
> + case QCT_QTIMER_CNTPL0ACR:
> + *data = view ? 0 : s->cntpl0acr;
> + return MEMTX_OK;
> + case QCT_QTIMER_VERSION:
> + *data = QCT_QTIMER_VERSION_VALUE;
> + return MEMTX_OK;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
> + (int)offset);
> + *data = 0;
> + return MEMTX_ACCESS_ERROR;
> + }
> +}
Similar to a previous series, it seems like we could use a table (or at
least a CHECK macro to remove redundant code for each register.
Same for write function below.
> +
> +static MemTxResult hex_timer_write(void *opaque, hwaddr offset,
> + uint64_t value, unsigned size,
> + MemTxAttrs attrs)
> +{
> + QCTQtimerState *qs = opaque;
> + uint32_t reg_offset;
> + uint32_t view;
> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
> + uint32_t frame;
> +
> + if (!s) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + frame = s - qs->timer;
> +
> + trace_qtimer_write(offset, value);
> +
> + if (!qct_qtimer_view_visible(qs, frame, view)) {
> + return MEMTX_OK;
> + }
> +
> + switch (reg_offset) {
> + case QCT_QTIMER_CNTP_CVAL_LO:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + s->int_level = 0;
> + s->cntval = deposit64(s->cntval, 0, 32, value);
> + hex_timer_rearm(s);
> + break;
> + case QCT_QTIMER_CNTP_CVAL_HI:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + s->int_level = 0;
> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
> + s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) &
> + QCT_QTIMER_CNT_MASK;
> + hex_timer_rearm(s);
> + break;
> + case QCT_QTIMER_CNTP_CTL:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */
> + s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT;
> + hex_timer_rearm(s);
> + break;
> + case QCT_QTIMER_CNTP_TVAL:
> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
> + return MEMTX_ACCESS_ERROR;
> + }
> + /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */
> + s->int_level = 0;
> + s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) &
> + QCT_QTIMER_CNT_MASK;
> + hex_timer_rearm(s);
> + break;
> + case QCT_QTIMER_CNTPL0ACR:
> + if (!view) {
> + s->cntpl0acr = value;
> + }
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
> + (int)offset);
> + return MEMTX_ACCESS_ERROR;
> + }
> + hex_timer_update(s);
> + return MEMTX_OK;
> +}
> +
> +static void hex_timer_tick(void *opaque)
> +{
> + QCTHextimerState *s = opaque;
> + uint64_t now = hex_timer_now(s);
> + uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK;
> + int64_t signed_diff = (int64_t)(diff56 << 8) >> 8;
> +
> + if (signed_diff >= 0) {
> + s->int_level = 1;
> + hex_timer_update(s);
> + } else {
> + hex_timer_rearm(s);
> + }
> +}
> +
> +static const MemoryRegionOps hex_timer_ops = {
> + .read_with_attrs = hex_timer_read,
> + .write_with_attrs = hex_timer_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 8,
> + .unaligned = false,
> + },
> + .impl = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static const VMStateDescription vmstate_qct_hextimer = {
> + .name = "qct-hextimer",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(control, QCTHextimerState),
> + VMSTATE_UINT32(cnt_ctrl, QCTHextimerState),
> + VMSTATE_INT64(offset_ns, QCTHextimerState),
> + VMSTATE_UINT64(cntval, QCTHextimerState),
> + VMSTATE_UINT32(cntpl0acr, QCTHextimerState),
> + VMSTATE_UINT32(int_level, QCTHextimerState),
> + VMSTATE_TIMER_PTR(timer, QCTHextimerState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const VMStateDescription vmstate_qct_qtimer = {
> + .name = "qct-qtimer",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(freq_hz, QCTQtimerState),
> + VMSTATE_UINT32(secure, QCTQtimerState),
> + VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames,
> + 1, vmstate_qct_hextimer, QCTHextimerState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void qct_qtimer_realize(DeviceState *dev, Error **errp)
> +{
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> + QCTQtimerState *s = QCT_QTIMER(dev);
> + unsigned int i;
> +
> + if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) {
> + error_setg(errp, "nr_frames too high");
> + return;
> + }
> + if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) {
> + error_setg(errp, "nr_views too high");
> + return;
> + }
> + if (s->freq_hz == 0) {
> + error_setg(errp, "freq-hz must be nonzero");
> + return;
> + }
> + if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) {
> + error_setg(errp, "frame_stride must be a nonzero power of two");
> + return;
> + }
> +
> + memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s,
> + "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES);
> + sysbus_init_mmio(sbd, &s->iomem);
> +
> + memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s,
> + "qct-qtimer-view",
> + (uint64_t)s->frame_stride * s->nr_frames *
> + s->nr_views);
> + sysbus_init_mmio(sbd, &s->view_iomem);
> +
> + for (i = 0; i < s->nr_frames; i++) {
> + QCTHextimerState *t = &s->timer[i];
> +
> + t->qtimer = s;
> + s->secure |= (1 << i);
> +
> + sysbus_init_irq(sbd, &t->irq);
> + t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t);
> + }
> +}
> +
> +static void qct_qtimer_unrealize(DeviceState *dev)
> +{
> + QCTQtimerState *s = QCT_QTIMER(dev);
> + unsigned int i;
> +
> + for (i = 0; i < s->nr_frames; i++) {
> + QCTHextimerState *t = &s->timer[i];
> +
> + if (t->timer) {
> + timer_free(t->timer);
> + t->timer = NULL;
> + }
> + }
> +}
> +
> +static void qct_qtimer_reset_hold(Object *obj, ResetType type)
> +{
> + QCTQtimerState *s = QCT_QTIMER(obj);
> + unsigned int i;
> +
> + for (i = 0; i < s->nr_frames; i++) {
> + QCTHextimerState *t = &s->timer[i];
> +
> + /*
> + * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that
> + * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is
> + * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a
> + * spurious fire before SW programs the compare value.
> + */
> + t->control = 0;
> + t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT |
> + QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ |
> + QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT;
> + t->cntval = 0;
> + t->cntpl0acr = 0;
> + t->int_level = 0;
> + t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + timer_del(t->timer);
> + qemu_set_irq(t->irq, 0);
> + }
> +}
> +
> +static const Property qct_qtimer_properties[] = {
> + DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz,
> + QTIMER_DEFAULT_FREQ_HZ),
> + DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1),
> + DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2),
> + DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1),
> + DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000),
> + DEFINE_PROP_UINT32("cnttid_0", QCTQtimerState, cnttid_0, 0x11),
> + DEFINE_PROP_UINT32("cnttid_1", QCTQtimerState, cnttid_1, 0x0),
> +};
> +
> +static void qct_qtimer_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> + device_class_set_props(dc, qct_qtimer_properties);
> + dc->realize = qct_qtimer_realize;
> + dc->unrealize = qct_qtimer_unrealize;
> + dc->vmsd = &vmstate_qct_qtimer;
> + rc->phases.hold = qct_qtimer_reset_hold;
> +}
> +
> +/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */
> +static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj)
> +{
> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
> +
> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32)
> + : 0;
> +}
> +
> +static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj)
> +{
> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
> +
> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32)
> + : 0;
> +}
> +
> +static void qct_qtimer_interface_class_init(ObjectClass *klass,
> + const void *data)
> +{
> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass);
> +
> + k->get_timer_lo = qct_qtimer_get_timer_lo_impl;
> + k->get_timer_hi = qct_qtimer_get_timer_hi_impl;
> +}
> +
> +static const TypeInfo qct_qtimer_types[] = {
> + {
> + .name = TYPE_QCT_QTIMER_INTERFACE,
> + .parent = TYPE_INTERFACE,
> + .class_size = sizeof(QctQtimerInterfaceClass),
> + .class_init = qct_qtimer_interface_class_init,
> + },
> + {
> + .name = TYPE_QCT_QTIMER,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(QCTQtimerState),
> + .class_init = qct_qtimer_class_init,
> + .interfaces = (InterfaceInfo[]) {
> + { TYPE_QCT_QTIMER_INTERFACE },
> + { }
> + },
> + },
> +};
> +
> +DEFINE_TYPES(qct_qtimer_types)
> diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
> index b3d823ce2c3..e1b751a54a7 100644
> --- a/hw/timer/Kconfig
> +++ b/hw/timer/Kconfig
> @@ -65,3 +65,6 @@ config STELLARIS_GPTM
>
> config AVR_TIMER16
> bool
> +
> +config HEX_QTIMER
> + bool
> diff --git a/hw/timer/meson.build b/hw/timer/meson.build
> index 201b5d8316d..0e8326b0492 100644
> --- a/hw/timer/meson.build
> +++ b/hw/timer/meson.build
> @@ -34,3 +34,5 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
> system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
>
> system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
> +
> +specific_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
Is there something preventing it from being in system_ss?
The file itself can be conditionnally included by meson, but it does not
seem to have target specifics in itself (or maybe through one header).
> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
> index 634ba1da27a..636310f8caa 100644
> --- a/hw/timer/trace-events
> +++ b/hw/timer/trace-events
> @@ -128,3 +128,8 @@ imx_epit_get_freq(uint32_t freq) "ptimer frequency is %u"
> imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x"
> imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")"
> imx_epit_cmp(uint32_t sr) "sr was %d"
> +
> +# qct-qtimer.c
> +qtimer_interrupt(void) "qtimer interrupt line updated"
> +qtimer_read(uint64_t offset) "offset 0x%" PRIx64
> +qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases
2026-07-31 17:42 ` Pierrick Bouvier
@ 2026-08-03 15:43 ` Brian Cain
2026-08-04 21:35 ` Pierrick Bouvier
0 siblings, 1 reply; 33+ messages in thread
From: Brian Cain @ 2026-08-03 15:43 UTC (permalink / raw)
To: Pierrick Bouvier, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 7/31/2026 12:42 PM, Pierrick Bouvier wrote:
> On 7/29/2026 6:28 PM, Brian Cain wrote:
>> Add more tests from hexagon-arch-tests, enabled by QTimer device.
>>
>> These exercise cache maintenance ops, l2vic, thread start/stop, tlb/mmu
>> operations, and user-mode transitions.
>>
>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>> ---
>> tests/functional/hexagon/test_arch_tests.py | 32 +++++++++++++++++++++
>> 1 file changed, 32 insertions(+)
>>
>> diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
>> index 2bb34f9b8dc..0834398c3b1 100755
>> --- a/tests/functional/hexagon/test_arch_tests.py
>> +++ b/tests/functional/hexagon/test_arch_tests.py
>> @@ -58,6 +58,38 @@ def test_int_steering(self) -> None:
>> """
>> self.run_uart_test("test_int_steering")
>>
>> + def test_cache(self) -> None:
>> + """Tests cache operations: dckill/ickill, l2kill, dczeroa,
>> + dccleaninva, cache disable/enable, barriers, and dcinva/dccleana.
>> + """
>> + self.run_uart_test("test_cache")
>> +
>> + def test_l2vic(self) -> None:
>> + """Tests the L2VIC interrupt controller: enable readback,
>> + interrupt type readback, VID capture, and the fast interface.
>> + """
>> + self.run_uart_test("test_l2vic")
>> +
>> + def test_threads(self) -> None:
>> + """Tests hardware thread management: start/stop, MODECTL state,
>> + per-thread HTID, shared memory, wait/resume, STID priority, and
>> + SCHEDCFG/BESTWAIT readback.
>> + """
>> + self.run_uart_test("test_threads")
>> +
>> + def test_tlb_mmu(self) -> None:
>> + """Tests TLB/MMU operations: write/read/probe/invalidate,
>> + global entries, multiple entries, overwrite, ASID matching,
>> + and permission checks.
>> + """
>> + self.run_uart_test("test_tlb_mmu")
>> +
>> + def test_user_mode(self) -> None:
>> + """Tests user mode / privilege transitions: supervisor mode,
>> + SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler.
>> + """
>> + self.run_uart_test("test_user_mode")
>> +
>>
>> if __name__ == "__main__":
>> QemuSystemTest.main()
> A general question on the pattern we have here.
>
> If those tests can be compiled with hexagon-cross container, would it
> make sense to add them to tcg/tests/hexagon/system directly in the future?
> Hopefully will be more easy once we have meson tcg-tests, so you don't
> need to add all dependencies by hand.
Glad you asked -- in a downstream fork, we originally did have several
tests like these (not these particular ones but ~similar scope) running
in check-tcg. But we pivoted away from that because:
1. they're not testing merely translation: they depend on several sysemu
devices. More like integration/functional testing.
2. sometimes it's useful to verify not merely the exit code but also
some output text to semihost/uart console. This *can* be done with
shell programs in make/meson, but it feels like "coloring outside of the
lines."
3. most other architectures seem to have fairly ~light system emu
check-tcg tests, perhaps because of #1/2?
We did this change with the assumption that these sysemu check-tcg tests
wouldn't be welcomed by community because it diverges from what other
targets do. But maybe that was an overreaction?
It's certainly convenient to have test cases in-project so that changes
to the tests don't require an indirect step to update a test code repo.
So, we can do whatever best conforms to the project idioms in this
regard. Note that these particular tests in this patch are written in
Rust and would introduce a new dependency (in the existing container, I
suppose) beyond the C/C++ toolchain. Fine w/me but food for thought.
>
> Regards,
> Pierrick
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases
2026-08-03 15:43 ` Brian Cain
@ 2026-08-04 21:35 ` Pierrick Bouvier
0 siblings, 0 replies; 33+ messages in thread
From: Pierrick Bouvier @ 2026-08-04 21:35 UTC (permalink / raw)
To: Brian Cain, qemu-devel, Alex Bennée, Richard Henderson
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino
On 8/3/2026 8:43 AM, Brian Cain wrote:
>
> On 7/31/2026 12:42 PM, Pierrick Bouvier wrote:
>> On 7/29/2026 6:28 PM, Brian Cain wrote:
>>> Add more tests from hexagon-arch-tests, enabled by QTimer device.
>>>
>>> These exercise cache maintenance ops, l2vic, thread start/stop, tlb/mmu
>>> operations, and user-mode transitions.
>>>
>>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>>> ---
>>> tests/functional/hexagon/test_arch_tests.py | 32 +++++++++++++++++++++
>>> 1 file changed, 32 insertions(+)
>>>
>>> diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/
>>> functional/hexagon/test_arch_tests.py
>>> index 2bb34f9b8dc..0834398c3b1 100755
>>> --- a/tests/functional/hexagon/test_arch_tests.py
>>> +++ b/tests/functional/hexagon/test_arch_tests.py
>>> @@ -58,6 +58,38 @@ def test_int_steering(self) -> None:
>>> """
>>> self.run_uart_test("test_int_steering")
>>> + def test_cache(self) -> None:
>>> + """Tests cache operations: dckill/ickill, l2kill, dczeroa,
>>> + dccleaninva, cache disable/enable, barriers, and dcinva/
>>> dccleana.
>>> + """
>>> + self.run_uart_test("test_cache")
>>> +
>>> + def test_l2vic(self) -> None:
>>> + """Tests the L2VIC interrupt controller: enable readback,
>>> + interrupt type readback, VID capture, and the fast interface.
>>> + """
>>> + self.run_uart_test("test_l2vic")
>>> +
>>> + def test_threads(self) -> None:
>>> + """Tests hardware thread management: start/stop, MODECTL state,
>>> + per-thread HTID, shared memory, wait/resume, STID priority, and
>>> + SCHEDCFG/BESTWAIT readback.
>>> + """
>>> + self.run_uart_test("test_threads")
>>> +
>>> + def test_tlb_mmu(self) -> None:
>>> + """Tests TLB/MMU operations: write/read/probe/invalidate,
>>> + global entries, multiple entries, overwrite, ASID matching,
>>> + and permission checks.
>>> + """
>>> + self.run_uart_test("test_tlb_mmu")
>>> +
>>> + def test_user_mode(self) -> None:
>>> + """Tests user mode / privilege transitions: supervisor mode,
>>> + SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler.
>>> + """
>>> + self.run_uart_test("test_user_mode")
>>> +
>>> if __name__ == "__main__":
>>> QemuSystemTest.main()
>> A general question on the pattern we have here.
>>
>> If those tests can be compiled with hexagon-cross container, would it
>> make sense to add them to tcg/tests/hexagon/system directly in the
>> future?
>> Hopefully will be more easy once we have meson tcg-tests, so you don't
>> need to add all dependencies by hand.
>
>
> Glad you asked -- in a downstream fork, we originally did have several
> tests like these (not these particular ones but ~similar scope) running
> in check-tcg. But we pivoted away from that because:
>
> 1. they're not testing merely translation: they depend on several sysemu
> devices. More like integration/functional testing.
>
That's a fair point. I'm not sure where is the exact border of what we
should/should not exercise through tcg tests.
@Alex: As tcg tests maintainer, do you have on opinion on this?
@Richard: as tcg maintainer, would you consider it as part of tcg test
suite for system mode?
> 2. sometimes it's useful to verify not merely the exit code but also
> some output text to semihost/uart console. This *can* be done with
> shell programs in make/meson, but it feels like "coloring outside of the
> lines."
>
We can add the necessary wrappers for that. Functional tests are such
programs in some way.
> 3. most other architectures seem to have fairly ~light system emu check-
> tcg tests, perhaps because of #1/2?
>
>
> We did this change with the assumption that these sysemu check-tcg tests
> wouldn't be welcomed by community because it diverges from what other
> targets do. But maybe that was an overreaction?
>
> It's certainly convenient to have test cases in-project so that changes
> to the tests don't require an indirect step to update a test code repo.
> So, we can do whatever best conforms to the project idioms in this
> regard. Note that these particular tests in this patch are written in
> Rust and would introduce a new dependency (in the existing container, I
> suppose) beyond the C/C++ toolchain. Fine w/me but food for thought.
>
For now, having it outside of QEMU is totally fine, and it doesn't seem
we should move it. We can revisit this in the future.
>
>>
>> Regards,
>> Pierrick
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH v4 15/18] hw/timer: Add QCT QTimer device model
2026-07-31 17:59 ` Pierrick Bouvier
@ 2026-08-05 21:10 ` Brian Cain
0 siblings, 0 replies; 33+ messages in thread
From: Brian Cain @ 2026-08-05 21:10 UTC (permalink / raw)
To: Pierrick Bouvier, qemu-devel
Cc: philmd, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
matheus.bernardino, Damien Hedde, Tobias Röhmel, Sid Manning,
Thomas Marceron, Mahmoud Kamel
On 7/31/2026 12:59 PM, Pierrick Bouvier wrote:
> On 7/29/2026 6:28 PM, Brian Cain wrote:
>> Implement the QCT QTimer generic timer device used by Hexagon DSP
>> systems.
>>
>> Co-authored-by: Damien Hedde <damien.hedde@greensocs.com>
>> Co-authored-by: Tobias Röhmel <quic_trohmel@quicinc.com>
>> Co-authored-by: Sid Manning <sidneym@quicinc.com>
>> Co-authored-by: Thomas Marceron <tmarcero@qti.qualcomm.com>
>> Co-authored-by: Mahmoud Kamel <mkamel@qti.qualcomm.com>
>> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
>> ---
>> MAINTAINERS | 2 +
>> include/hw/timer/qct-qtimer.h | 43 +++
>> hw/timer/qct-qtimer.c | 681 ++++++++++++++++++++++++++++++++++
>> hw/timer/Kconfig | 3 +
>> hw/timer/meson.build | 2 +
>> hw/timer/trace-events | 5 +
>> 6 files changed, 736 insertions(+)
>> create mode 100644 include/hw/timer/qct-qtimer.h
>> create mode 100644 hw/timer/qct-qtimer.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 114bbec45ad..de106416b64 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -253,6 +253,8 @@ F: target/hexagon/
>> F: hw/intc/hex-l2vic.c
>> F: include/hw/intc/hex-l2vic.h
>> F: tests/qtest/l2vic-test.c
>> +F: hw/timer/qct-qtimer.c
>> +F: include/hw/timer/qct-qtimer.h
>> X: target/hexagon/idef-parser/
>> X: target/hexagon/gen_idef_parser_funcs.py
>> F: linux-user/hexagon/
>> diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h
>> new file mode 100644
>> index 00000000000..53d8291472d
>> --- /dev/null
>> +++ b/include/hw/timer/qct-qtimer.h
>> @@ -0,0 +1,43 @@
>> +/*
>> + * Qualcomm QCT QTimer
>> + *
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_TIMER_QCT_QTIMER_H
>> +#define HW_TIMER_QCT_QTIMER_H
>> +
>> +#include "qom/object.h"
>> +
>> +#define TYPE_QCT_QTIMER "qct-qtimer"
>> +
>> +/* QTimer interface for external access from hexagon_globalreg */
>> +#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if"
>> +
> Why not directly get access to TYPE_QCT_QTIMER?
> Is it to ensure there is no other access than get_timer_lo/hi made from
> there?
The export for the sake of the globalreg only needs access to that timer
count, so -- yes, I guess. And I think this also allows us to keep the
state private?
>> +typedef struct QctQtimerInterface QctQtimerInterface;
>> +
>> +typedef struct QctQtimerInterfaceClass {
>> + InterfaceClass parent_class;
>> +
>> + /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */
>> + uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer);
>> + uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer);
>> +} QctQtimerInterfaceClass;
>> +
>> +DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass,
>> + QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE);
>> +
>> +static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer)
>> +{
>> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
>> + return k->get_timer_lo(qtimer);
>> +}
>> +
>> +static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer)
>> +{
>> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
>> + return k->get_timer_hi(qtimer);
>> +}
>> +
>> +#endif /* HW_TIMER_QCT_QTIMER_H */
>> diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c
>> new file mode 100644
>> index 00000000000..82c711000eb
>> --- /dev/null
>> +++ b/hw/timer/qct-qtimer.c
>> @@ -0,0 +1,681 @@
>> +/*
>> + * Qualcomm QCT QTimer
>> + *
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/core/irq.h"
>> +#include "hw/core/qdev-properties.h"
>> +#include "hw/core/sysbus.h"
>> +#include "hw/timer/qct-qtimer.h"
>> +#include "migration/vmstate.h"
>> +#include "qemu/bitops.h"
>> +#include "qemu/log.h"
>> +#include "qemu/module.h"
>> +#include "qemu/timer.h"
>> +#include "qapi/error.h"
>> +#include "trace.h"
>> +
>> +#define QTIMER_MEM_SIZE_BYTES 0x1000
>> +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
>> +
>> +#define QCT_QTIMER_TIMER_FRAME_ELTS (16)
>> +#define QCT_QTIMER_TIMER_VIEW_ELTS (2)
>> +
>> +#define QCT_QTIMER_AC_CNTFRQ (0x000)
>> +#define QCT_QTIMER_AC_CNTSR (0x004)
>> +#define QCT_QTIMER_AC_CNTTID_0 (0x08)
>> +#define QCT_QTIMER_AC_CNTACR_START (0x40)
>> +#define QCT_QTIMER_AC_CNTACR_END (0x5c)
>> +#define QCT_QTIMER_AC_CNTTID_1 (0x108)
>> +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
>> +#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */
>> +#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */
>> +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
>> +#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */
>> +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
>> +#define QCT_QTIMER_VERSION (0x0fd0)
>> +
>> +#define QCT_QTIMER_CNTPCT_LO (0x000)
>> +#define QCT_QTIMER_CNTPCT_HI (0x004)
>> +#define QCT_QTIMER_CNT_FREQ (0x010)
>> +#define QCT_QTIMER_CNTPL0ACR (0x014)
>> +#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9)
>> +#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8)
>> +#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1)
>> +#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0)
>> +#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
>> +#define QCT_QTIMER_CNTP_CVAL_HI (0x024)
>> +#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL
>> +#define QCT_QTIMER_CNT_HI_BITS 24
>> +#define QCT_QTIMER_CNTP_TVAL (0x028)
>> +#define QCT_QTIMER_CNTP_CTL (0x02c)
>> +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
>> +#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1)
>> +#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2)
>> +
>> +OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER)
>> +
>> +typedef struct QCTHextimerState {
>> + QCTQtimerState *qtimer;
>> + QEMUTimer *timer; /* one-shot deadline timer */
>> + int64_t offset_ns; /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */
>> + uint64_t cntval; /* 64-bit physical timer compare value */
>> + uint32_t control;
>> + uint32_t cnt_ctrl;
>> + uint32_t cntpl0acr;
>> + uint32_t int_level;
>> + qemu_irq irq;
>> +} QCTHextimerState;
>> +
>> +struct QCTQtimerState {
>> + SysBusDevice parent_obj;
>> +
>> + MemoryRegion iomem;
>> + MemoryRegion view_iomem;
>> + uint32_t secure;
>> + QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS];
>> + uint32_t freq_hz;
>> + uint32_t nr_frames;
>> + uint32_t nr_views;
>> + uint32_t frame_stride;
>> + uint32_t cnttid_0;
>> + uint32_t cnttid_1;
>> + uint32_t freq_scale;
>> +};
>> +
>> +/*
>> + * QTimer version register:
>> + *
>> + * 3 2 1
>> + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
>> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
>> + * | Major | Minor | Step |
>> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
>> + */
>> +#define QCT_QTIMER_VERSION_VALUE 0x20020000
>> +
>> +/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */
>> +static uint64_t hex_timer_now(QCTHextimerState *s)
>> +{
>> + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
>> + uint32_t scale;
>> + uint64_t scaled_elapsed;
>> +
>> + if (now <= s->offset_ns) {
>> + return 0;
>> + }
>> + scale = MAX(s->qtimer->freq_scale, 1u);
>> + scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale;
>> + return muldiv64(scaled_elapsed, s->qtimer->freq_hz,
>> + NANOSECONDS_PER_SECOND) &
>> + QCT_QTIMER_CNT_MASK;
>> +}
>> +
>> +/* Arm (or disarm) the one-shot deadline timer. */
>> +static void hex_timer_rearm(QCTHextimerState *s)
>> +{
>> + uint32_t scale;
>> + uint64_t base_ns;
>> + int64_t deadline_ns;
>> +
>> + if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) {
>> + timer_del(s->timer);
>> + return;
>> + }
>> +
>> + scale = MAX(s->qtimer->freq_scale, 1u);
>> + /*
>> + * Round the ticks-to-ns conversion up so that hex_timer_now(), which
>> + * truncates when it divides elapsed ns by scale, is guaranteed to
>> + * report >= cntval once this deadline fires. A truncating conversion
>> + * here could re-arm at the same deadline forever when scale > 1.
>> + */
>> + base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND,
>> + s->qtimer->freq_hz);
>> + if (base_ns >
>> + ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) {
>> + timer_del(s->timer);
>> + return;
>> + }
>> + deadline_ns = s->offset_ns + (int64_t)(base_ns * scale);
>> + timer_mod(s->timer, deadline_ns);
>> +}
>> +
>> +static void hex_timer_update(QCTHextimerState *s)
>> +{
>> + int level = s->int_level &&
>> + (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) &&
>> + !(s->control & QCT_QTIMER_CNTP_CTL_INTEN);
>> +
>> + trace_qtimer_interrupt();
>> + qemu_set_irq(s->irq, level);
>> +}
>> +
>> +/*
>> + * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/
>> + * CNTTID/CNTACR per frame plus the shared VERSION register.
>> + */
>> +static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size)
>> +{
>> + QCTQtimerState *s = opaque;
>> + uint32_t frame;
>> +
>> + switch (offset) {
>> + case QCT_QTIMER_AC_CNTFRQ:
>> + return s->freq_hz;
>> + case QCT_QTIMER_AC_CNTSR:
>> + return s->secure;
>> + case QCT_QTIMER_AC_CNTTID_0:
>> + return s->cnttid_0;
>> + case QCT_QTIMER_AC_CNTTID_1:
>> + return s->cnttid_1;
>> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
>> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
>> + if (frame >= s->nr_frames) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
>> + __func__, (int)offset);
>> + return 0;
>> + }
>> + return s->timer[frame].cnt_ctrl;
>> + case QCT_QTIMER_VERSION:
>> + return QCT_QTIMER_VERSION_VALUE;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
>> + (int)offset);
>> + return 0;
>> + }
>> +}
>> +
>> +static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value,
>> + unsigned size)
>> +{
>> + QCTQtimerState *s = opaque;
>> + uint32_t frame;
>> +
>> + switch (offset) {
>> + case QCT_QTIMER_AC_CNTFRQ:
>> + if (value == 0) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n",
>> + __func__);
>> + return;
>> + }
>> + s->freq_hz = value;
>> + return;
>> + case QCT_QTIMER_AC_CNTSR:
>> + if (value > 0xff) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n",
>> + __func__, (int)value);
>> + return;
>> + }
>> + s->secure = value;
>> + return;
>> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
>> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
>> + if (frame >= s->nr_frames) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
>> + __func__, (int)offset);
>> + return;
>> + }
>> + s->timer[frame].cnt_ctrl = value;
>> + return;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
>> + (int)offset);
>> + return;
>> + }
>> +}
>> +
>> +static const MemoryRegionOps qct_qtimer_ac_ops = {
>> + .read = qct_qtimer_ac_read,
>> + .write = qct_qtimer_ac_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + .unaligned = false,
>> + },
>> + .impl = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +/*
>> + * View region: a flat array of (frame, view) slots, each frame_stride
>> + * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL
>> + * register set.
>> + */
>> +static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset,
>> + uint32_t *reg_offset,
>> + uint32_t *view)
>> +{
>> + uint32_t stride = s->frame_stride;
>> + uint32_t stride_shift = ctz32(stride);
>> + uint32_t slot_nr = offset >> stride_shift;
>> + uint32_t frame = slot_nr / s->nr_views;
>> +
>> + *reg_offset = offset & (stride - 1);
>> + *view = slot_nr % s->nr_views;
>> + if (frame >= s->nr_frames) {
>> + return NULL;
>> + }
>> + return &s->timer[frame];
>> +}
>> +
>> +/* Frames 8+ share cnttid_1; each frame's second view is gated by a bit. */
>> +static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame,
>> + uint32_t view)
>> +{
>> + uint32_t cnttid = frame < 8 ? s->cnttid_0 : s->cnttid_1;
>> + uint32_t frame_idx = frame < 8 ? frame : frame - 8;
>> +
>> + return !view || (cnttid & (0x4 << (frame_idx * 4)));
>> +}
>> +
>> +static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data,
>> + unsigned size, MemTxAttrs attrs)
>> +{
>> + QCTQtimerState *qs = opaque;
>> + uint32_t reg_offset;
>> + uint32_t view;
>> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
>> + uint32_t frame;
>> +
>> + if (!s) {
>> + *data = 0;
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + frame = s - qs->timer;
>> +
>> + trace_qtimer_read(offset);
>> +
>> + if (!qct_qtimer_view_visible(qs, frame, view)) {
>> + *data = 0;
>> + return MEMTX_OK;
>> + }
>> +
>> + switch (reg_offset) {
>> + case QCT_QTIMER_CNT_FREQ:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RFRQ)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & (QCT_QTIMER_CNTPL0ACR_PL0PCTEN |
>> + QCT_QTIMER_CNTPL0ACR_PL0VCTEN))) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + *data = s->qtimer->freq_hz;
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTP_CVAL_LO:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + *data = extract64(s->cntval, 0, 32);
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTP_CVAL_HI:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
>> + *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS);
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTPCT_LO:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + *data = extract64(hex_timer_now(s), 0, 32);
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTPCT_HI:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS);
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTP_TVAL:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s));
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTP_CTL:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + /*
>> + * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt
>> + * pending). ISTAT tracks int_level and is read-only.
>> + */
>> + *data = s->control | ((s->int_level & 0x1) << 2);
>> + return MEMTX_OK;
>> + case QCT_QTIMER_CNTPL0ACR:
>> + *data = view ? 0 : s->cntpl0acr;
>> + return MEMTX_OK;
>> + case QCT_QTIMER_VERSION:
>> + *data = QCT_QTIMER_VERSION_VALUE;
>> + return MEMTX_OK;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
>> + (int)offset);
>> + *data = 0;
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> +}
> Similar to a previous series, it seems like we could use a table (or at
> least a CHECK macro to remove redundant code for each register.
> Same for write function below.
I'll add an access_ok() check to remove redundancies.
>> +
>> +static MemTxResult hex_timer_write(void *opaque, hwaddr offset,
>> + uint64_t value, unsigned size,
>> + MemTxAttrs attrs)
>> +{
>> + QCTQtimerState *qs = opaque;
>> + uint32_t reg_offset;
>> + uint32_t view;
>> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view);
>> + uint32_t frame;
>> +
>> + if (!s) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + frame = s - qs->timer;
>> +
>> + trace_qtimer_write(offset, value);
>> +
>> + if (!qct_qtimer_view_visible(qs, frame, view)) {
>> + return MEMTX_OK;
>> + }
>> +
>> + switch (reg_offset) {
>> + case QCT_QTIMER_CNTP_CVAL_LO:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + s->int_level = 0;
>> + s->cntval = deposit64(s->cntval, 0, 32, value);
>> + hex_timer_rearm(s);
>> + break;
>> + case QCT_QTIMER_CNTP_CVAL_HI:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + s->int_level = 0;
>> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
>> + s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) &
>> + QCT_QTIMER_CNT_MASK;
>> + hex_timer_rearm(s);
>> + break;
>> + case QCT_QTIMER_CNTP_CTL:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */
>> + s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT;
>> + hex_timer_rearm(s);
>> + break;
>> + case QCT_QTIMER_CNTP_TVAL:
>> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) {
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */
>> + s->int_level = 0;
>> + s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) &
>> + QCT_QTIMER_CNT_MASK;
>> + hex_timer_rearm(s);
>> + break;
>> + case QCT_QTIMER_CNTPL0ACR:
>> + if (!view) {
>> + s->cntpl0acr = value;
>> + }
>> + break;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
>> + (int)offset);
>> + return MEMTX_ACCESS_ERROR;
>> + }
>> + hex_timer_update(s);
>> + return MEMTX_OK;
>> +}
>> +
>> +static void hex_timer_tick(void *opaque)
>> +{
>> + QCTHextimerState *s = opaque;
>> + uint64_t now = hex_timer_now(s);
>> + uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK;
>> + int64_t signed_diff = (int64_t)(diff56 << 8) >> 8;
>> +
>> + if (signed_diff >= 0) {
>> + s->int_level = 1;
>> + hex_timer_update(s);
>> + } else {
>> + hex_timer_rearm(s);
>> + }
>> +}
>> +
>> +static const MemoryRegionOps hex_timer_ops = {
>> + .read_with_attrs = hex_timer_read,
>> + .write_with_attrs = hex_timer_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 4,
>> + .max_access_size = 8,
>> + .unaligned = false,
>> + },
>> + .impl = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static const VMStateDescription vmstate_qct_hextimer = {
>> + .name = "qct-hextimer",
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_UINT32(control, QCTHextimerState),
>> + VMSTATE_UINT32(cnt_ctrl, QCTHextimerState),
>> + VMSTATE_INT64(offset_ns, QCTHextimerState),
>> + VMSTATE_UINT64(cntval, QCTHextimerState),
>> + VMSTATE_UINT32(cntpl0acr, QCTHextimerState),
>> + VMSTATE_UINT32(int_level, QCTHextimerState),
>> + VMSTATE_TIMER_PTR(timer, QCTHextimerState),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static const VMStateDescription vmstate_qct_qtimer = {
>> + .name = "qct-qtimer",
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_UINT32(freq_hz, QCTQtimerState),
>> + VMSTATE_UINT32(secure, QCTQtimerState),
>> + VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames,
>> + 1, vmstate_qct_hextimer, QCTHextimerState),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static void qct_qtimer_realize(DeviceState *dev, Error **errp)
>> +{
>> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>> + QCTQtimerState *s = QCT_QTIMER(dev);
>> + unsigned int i;
>> +
>> + if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) {
>> + error_setg(errp, "nr_frames too high");
>> + return;
>> + }
>> + if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) {
>> + error_setg(errp, "nr_views too high");
>> + return;
>> + }
>> + if (s->freq_hz == 0) {
>> + error_setg(errp, "freq-hz must be nonzero");
>> + return;
>> + }
>> + if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) {
>> + error_setg(errp, "frame_stride must be a nonzero power of two");
>> + return;
>> + }
>> +
>> + memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s,
>> + "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES);
>> + sysbus_init_mmio(sbd, &s->iomem);
>> +
>> + memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s,
>> + "qct-qtimer-view",
>> + (uint64_t)s->frame_stride * s->nr_frames *
>> + s->nr_views);
>> + sysbus_init_mmio(sbd, &s->view_iomem);
>> +
>> + for (i = 0; i < s->nr_frames; i++) {
>> + QCTHextimerState *t = &s->timer[i];
>> +
>> + t->qtimer = s;
>> + s->secure |= (1 << i);
>> +
>> + sysbus_init_irq(sbd, &t->irq);
>> + t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t);
>> + }
>> +}
>> +
>> +static void qct_qtimer_unrealize(DeviceState *dev)
>> +{
>> + QCTQtimerState *s = QCT_QTIMER(dev);
>> + unsigned int i;
>> +
>> + for (i = 0; i < s->nr_frames; i++) {
>> + QCTHextimerState *t = &s->timer[i];
>> +
>> + if (t->timer) {
>> + timer_free(t->timer);
>> + t->timer = NULL;
>> + }
>> + }
>> +}
>> +
>> +static void qct_qtimer_reset_hold(Object *obj, ResetType type)
>> +{
>> + QCTQtimerState *s = QCT_QTIMER(obj);
>> + unsigned int i;
>> +
>> + for (i = 0; i < s->nr_frames; i++) {
>> + QCTHextimerState *t = &s->timer[i];
>> +
>> + /*
>> + * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that
>> + * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is
>> + * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a
>> + * spurious fire before SW programs the compare value.
>> + */
>> + t->control = 0;
>> + t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT |
>> + QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ |
>> + QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT;
>> + t->cntval = 0;
>> + t->cntpl0acr = 0;
>> + t->int_level = 0;
>> + t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
>> + timer_del(t->timer);
>> + qemu_set_irq(t->irq, 0);
>> + }
>> +}
>> +
>> +static const Property qct_qtimer_properties[] = {
>> + DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz,
>> + QTIMER_DEFAULT_FREQ_HZ),
>> + DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1),
>> + DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2),
>> + DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1),
>> + DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000),
>> + DEFINE_PROP_UINT32("cnttid_0", QCTQtimerState, cnttid_0, 0x11),
>> + DEFINE_PROP_UINT32("cnttid_1", QCTQtimerState, cnttid_1, 0x0),
>> +};
>> +
>> +static void qct_qtimer_class_init(ObjectClass *klass, const void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> + ResettableClass *rc = RESETTABLE_CLASS(klass);
>> +
>> + device_class_set_props(dc, qct_qtimer_properties);
>> + dc->realize = qct_qtimer_realize;
>> + dc->unrealize = qct_qtimer_unrealize;
>> + dc->vmsd = &vmstate_qct_qtimer;
>> + rc->phases.hold = qct_qtimer_reset_hold;
>> +}
>> +
>> +/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */
>> +static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj)
>> +{
>> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
>> +
>> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32)
>> + : 0;
>> +}
>> +
>> +static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj)
>> +{
>> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
>> +
>> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32)
>> + : 0;
>> +}
>> +
>> +static void qct_qtimer_interface_class_init(ObjectClass *klass,
>> + const void *data)
>> +{
>> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass);
>> +
>> + k->get_timer_lo = qct_qtimer_get_timer_lo_impl;
>> + k->get_timer_hi = qct_qtimer_get_timer_hi_impl;
>> +}
>> +
>> +static const TypeInfo qct_qtimer_types[] = {
>> + {
>> + .name = TYPE_QCT_QTIMER_INTERFACE,
>> + .parent = TYPE_INTERFACE,
>> + .class_size = sizeof(QctQtimerInterfaceClass),
>> + .class_init = qct_qtimer_interface_class_init,
>> + },
>> + {
>> + .name = TYPE_QCT_QTIMER,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(QCTQtimerState),
>> + .class_init = qct_qtimer_class_init,
>> + .interfaces = (InterfaceInfo[]) {
>> + { TYPE_QCT_QTIMER_INTERFACE },
>> + { }
>> + },
>> + },
>> +};
>> +
>> +DEFINE_TYPES(qct_qtimer_types)
>> diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
>> index b3d823ce2c3..e1b751a54a7 100644
>> --- a/hw/timer/Kconfig
>> +++ b/hw/timer/Kconfig
>> @@ -65,3 +65,6 @@ config STELLARIS_GPTM
>>
>> config AVR_TIMER16
>> bool
>> +
>> +config HEX_QTIMER
>> + bool
>> diff --git a/hw/timer/meson.build b/hw/timer/meson.build
>> index 201b5d8316d..0e8326b0492 100644
>> --- a/hw/timer/meson.build
>> +++ b/hw/timer/meson.build
>> @@ -34,3 +34,5 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
>> system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
>>
>> system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
>> +
>> +specific_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
> Is there something preventing it from being in system_ss?
> The file itself can be conditionnally included by meson, but it does not
> seem to have target specifics in itself (or maybe through one header).
I'll change it to `system_ss`.
>
>> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
>> index 634ba1da27a..636310f8caa 100644
>> --- a/hw/timer/trace-events
>> +++ b/hw/timer/trace-events
>> @@ -128,3 +128,8 @@ imx_epit_get_freq(uint32_t freq) "ptimer frequency is %u"
>> imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x"
>> imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")"
>> imx_epit_cmp(uint32_t sr) "sr was %d"
>> +
>> +# qct-qtimer.c
>> +qtimer_interrupt(void) "qtimer interrupt line updated"
>> +qtimer_read(uint64_t offset) "offset 0x%" PRIx64
>> +qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
^ permalink raw reply [flat|nested] 33+ messages in thread
end of thread, other threads:[~2026-08-05 21:11 UTC | newest]
Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 1:28 [PATCH v4 00/18] Add hexagon l2vic, qtimer devices Brian Cain
2026-07-30 1:28 ` [PATCH v4 01/18] hw/hexagon: add hex-subsys Brian Cain
2026-07-31 17:28 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 02/18] hw/hexagon: move VTCM to the common machine state Brian Cain
2026-07-31 17:29 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 03/18] hw/hexagon: move global registers to hex-subsys Brian Cain
2026-07-31 17:29 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 04/18] hw/hexagon: move the TLB " Brian Cain
2026-07-31 17:30 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 05/18] hw/hexagon: group the CPUs in a cluster Brian Cain
2026-07-31 17:34 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 06/18] bitops.h: Add find_first_bit32() Brian Cain
2026-07-30 1:28 ` [PATCH v4 07/18] hw/intc: Add l2vic interrupt controller Brian Cain
2026-07-30 1:28 ` [PATCH v4 08/18] hw/hexagon: extract get_reg_value/set_reg_value stubs in globalreg Brian Cain
2026-07-30 1:28 ` [PATCH v4 09/18] hw/hexagon: connect l2vic device Brian Cain
2026-07-30 1:28 ` [PATCH v4 10/18] hw/hexagon/virt: instantiate virtio-mmio transports Brian Cain
2026-07-30 1:28 ` [PATCH v4 11/18] hw/hexagon/virt: add l2vic interrupt-controller and virtio-mmio FDT nodes Brian Cain
2026-07-31 17:35 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 12/18] hw/hexagon/virt: connect pl011 UART interrupt Brian Cain
2026-07-30 1:28 ` [PATCH v4 13/18] tests/qtest: add L2VIC qtest Brian Cain
2026-07-30 1:28 ` [PATCH v4 14/18] tests/functional/hexagon: add arch_tests functional test Brian Cain
2026-07-30 1:28 ` [PATCH v4 15/18] hw/timer: Add QCT QTimer device model Brian Cain
2026-07-31 17:59 ` Pierrick Bouvier
2026-08-05 21:10 ` Brian Cain
2026-07-30 1:28 ` [PATCH v4 16/18] hw/hexagon: connect qtimer device Brian Cain
2026-07-31 17:36 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 17/18] tests/qtest: add qct-qtimer qtest Brian Cain
2026-07-31 17:38 ` Pierrick Bouvier
2026-07-30 1:28 ` [PATCH v4 18/18] tests/functional/hexagon: enable more arch_tests cases Brian Cain
2026-07-31 17:39 ` Pierrick Bouvier
2026-07-31 17:42 ` Pierrick Bouvier
2026-08-03 15:43 ` Brian Cain
2026-08-04 21:35 ` Pierrick Bouvier
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.