qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support
@ 2025-11-11 11:46 Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl Daniel Henrique Barboza
                   ` (17 more replies)
  0 siblings, 18 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Hi,

In this v2 we're addressing a review comment from Konstantin in patch
14. We're also doing changes in the FDT (patch 5) to keep up with the
changes that the kernel support is making [1].

I've been thinking about adding partial support for N-trace in this work
as well. From what I can tell the difference between N-trace and E-trace
are the encoding of the packages, with everything else staying the same.
In this case we could add hooks in the code to be ready to support
N-trace encoding in the future.

No other changes made. Patches based on current master.

Changes in v2:
- patch 5:
  - changed tr_compat from "ventana,veyron-v2-trace" to "qemu,trace-component"
  - renamed trace encoder prop handler "cpu" to "cpus"
- patch 14:
  - removed updiscon generation in gen_jal()
- v1 link: https://lore.kernel.org/qemu-riscv/20251002112335.2374517-1-dbarboza@ventanamicro.com/


[1] https://lore.kernel.org/linux-riscv/20251101154245.162492-1-apatel@ventanamicro.com/

Daniel Henrique Barboza (16):
  hw/riscv: Trace Encoder initial impl
  hw/riscv: Trace RAM Sink initial impl
  hw/riscv/trace-encoder: add trace start/stop logic
  hw/riscv/virt.c: add trace encoders and trace ram sinks
  hw/riscv: add e-trace message helpers
  target/riscv: add initial trace instrumentation
  hw/riscv/trace-encoder: write e-trace packets to RAM sink
  test/qtest: add riscv-trace-test.c
  hw/riscv/rv-trace-messages.c: add encoded trap message
  hw/riscv, target/riscv: send trace trap messages
  target/riscv, hw/riscv: send trace ppccd packets
  hw/riscv/trace: add format2 msg helper
  hw/riscv, target/riscv: send resync updiscon trace packets
  hw/riscv/rv-trace-messages: add format 1 msgs with branch info
  hw/riscv/trace-encoder: send branches info
  hw/riscv/trace: update branch bit in sync messages

Mayuresh Chitale (1):
  hw/riscv/virt.c add trace encoder and ramsink fdt nodes

 hw/riscv/Kconfig                              |   5 +
 hw/riscv/meson.build                          |   2 +
 hw/riscv/rv-trace-messages.c                  | 373 +++++++++++
 hw/riscv/rv-trace-messages.h                  |  40 ++
 hw/riscv/trace-encoder.c                      | 609 ++++++++++++++++++
 hw/riscv/trace-encoder.h                      |  62 ++
 hw/riscv/trace-events                         |   9 +
 hw/riscv/trace-ram-sink.c                     | 263 ++++++++
 hw/riscv/trace-ram-sink.h                     |  83 +++
 hw/riscv/virt.c                               | 135 ++++
 include/hw/riscv/virt.h                       |   2 +
 target/riscv/cpu.h                            |  11 +
 target/riscv/cpu_helper.c                     |  26 +-
 target/riscv/helper.h                         |   6 +
 .../riscv/insn_trans/trans_privileged.c.inc   |  11 +
 target/riscv/insn_trans/trans_rvi.c.inc       |  15 +
 target/riscv/meson.build                      |   3 +-
 target/riscv/tcg/tcg-cpu.c                    |   5 +
 target/riscv/trace_helper.c                   |  62 ++
 target/riscv/translate.c                      |  19 +
 tests/qtest/meson.build                       |   2 +-
 tests/qtest/riscv-trace-test.c                | 120 ++++
 22 files changed, 1860 insertions(+), 3 deletions(-)
 create mode 100644 hw/riscv/rv-trace-messages.c
 create mode 100644 hw/riscv/rv-trace-messages.h
 create mode 100644 hw/riscv/trace-encoder.c
 create mode 100644 hw/riscv/trace-encoder.h
 create mode 100644 hw/riscv/trace-ram-sink.c
 create mode 100644 hw/riscv/trace-ram-sink.h
 create mode 100644 target/riscv/trace_helper.c
 create mode 100644 tests/qtest/riscv-trace-test.c

-- 
2.51.1



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-20 15:56   ` Konstantin Semichastnov
  2025-11-11 11:46 ` [PATCH v2 02/17] hw/riscv: Trace RAM Sink " Daniel Henrique Barboza
                   ` (16 subsequent siblings)
  17 siblings, 1 reply; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

The Trace Encoder is a hardware module that interacts with a CPU,
gathering execution information (a.k.a instruction delta trace), and
send it downstream to other storage components such as a RAM Sink (a RAM
storage). This trace info can then be read via software (e.g. perf) to
reproduce the behavior of a given binary that ran in the CPU.

This implementation is based on the Efficient Trace for RISC-V [1] and
RISC-V Trace Control Interface Specification [2]. It's not intended to
implement all spec features: the idea is to provide a base where we can
add extra features on demand.

We'll get back to the instruction delta trace and how we'll instrument
TCG to produce it later. For now we'll set the minimal components to get
the basic framework running.

This Trace Encoder impl has just the minimal bits specified in [2],
section "Minimal Implementation". RO and RSVP bits are taken verbatim
from [2] without considering what we're actually going to support. The
base impl is heavily inspired by the XLNZ-ZDMA device w.r.t the usage of
the RegisterInfo and register.h framework.

Discovery of the Trace Encoder will be made via fdt, a single entry per
CPU. We'll connect each Trace Encoder to its CPU in the 'virt' board
later.

[1] https://github.com/riscv-non-isa/riscv-trace-spec/releases/download/v2.0-20250616/riscv-trace-spec-asciidoc.pdf
[2] https://github.com/riscv-non-isa/tg-nexus-trace/releases/download/1.0_Ratified/RISC-V-Trace-Control-Interface.pdf

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/Kconfig         |   5 +
 hw/riscv/meson.build     |   1 +
 hw/riscv/trace-encoder.c | 216 +++++++++++++++++++++++++++++++++++++++
 hw/riscv/trace-encoder.h |  42 ++++++++
 hw/riscv/trace-events    |   4 +
 5 files changed, 268 insertions(+)
 create mode 100644 hw/riscv/trace-encoder.c
 create mode 100644 hw/riscv/trace-encoder.h

diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index fc9c35bd98..2de0892496 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -7,6 +7,10 @@ config RISCV_NUMA
 config IBEX
     bool
 
+config RISCV_TRACE
+    bool
+    select REGISTER
+
 # RISC-V machines in alphabetical order
 
 config MICROCHIP_PFSOC
@@ -68,6 +72,7 @@ config RISCV_VIRT
     select PLATFORM_BUS
     select ACPI
     select ACPI_PCI
+    select RISCV_TRACE
 
 config SHAKTI_C
     bool
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 2a8d5b136c..b4a9988a62 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -14,5 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
 	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
 riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
 riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
+riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c'))
 
 hw_arch += {'riscv': riscv_ss}
diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
new file mode 100644
index 0000000000..21bf650a6a
--- /dev/null
+++ b/hw/riscv/trace-encoder.c
@@ -0,0 +1,216 @@
+/*
+ * Emulation of a RISC-V Trace Encoder
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "trace-encoder.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "system/device_tree.h"
+#include "hw/register.h"
+#include "cpu.h"
+
+/*
+ * trTeControl register fields
+ */
+REG32(TR_TE_CONTROL, 0x0)
+    FIELD(TR_TE_CONTROL, ACTIVE, 0, 1)
+    FIELD(TR_TE_CONTROL, ENABLE, 1, 1)
+    FIELD(TR_TE_CONTROL, INST_TRACING, 2, 1)
+    FIELD(TR_TE_CONTROL, EMPTY, 3, 1)
+    FIELD(TR_TE_CONTROL, INST_MODE, 4, 3)
+    FIELD(TR_TE_CONTROL, INST_SYNC_MODE, 16, 2)
+    FIELD(TR_TE_CONTROL, FORMAT, 24, 3)
+    /* reserved bits */
+    FIELD(TR_TE_CONTROL, RSVP1, 7, 2)
+    FIELD(TR_TE_CONTROL, RSVP2, 10, 1)
+    FIELD(TR_TE_CONTROL, RSVP3, 14, 1)
+    FIELD(TR_TE_CONTROL, RSVP4, 18, 2)
+    FIELD(TR_TE_CONTROL, RSVP5, 27, 4)
+
+#define R_TR_TE_CONTROL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
+                                   R_TR_TE_CONTROL_RSVP1_MASK | \
+                                   R_TR_TE_CONTROL_RSVP2_MASK | \
+                                   R_TR_TE_CONTROL_RSVP3_MASK | \
+                                   R_TR_TE_CONTROL_RSVP4_MASK | \
+                                   R_TR_TE_CONTROL_RSVP5_MASK)
+
+/* trTeControlEmpty is the only RO field and reset value */
+#define R_TR_TE_CONTROL_RESET R_TR_TE_CONTROL_EMPTY_MASK
+#define R_TR_TE_CONTROL_RO_BITS R_TR_TE_CONTROL_EMPTY_MASK
+
+/*
+ * trTeImpl register fields
+ */
+REG32(TR_TE_IMPL, 0x4)
+    FIELD(TR_TE_IMPL, VER_MAJOR, 0, 4)
+    FIELD(TR_TE_IMPL, VER_MINOR, 4, 4)
+    FIELD(TR_TE_IMPL, COMP_TYPE, 8, 4)
+    FIELD(TR_TE_IMPL, PROTOCOL_MAJOR, 16, 4)
+    FIELD(TR_TE_IMPL, PROTOCOL_MINOR, 20, 4)
+    /* reserved bits */
+    FIELD(TR_TE_IMPL, RSVP1, 12, 4)
+    FIELD(TR_TE_IMPL, RSVP2, 24, 8)
+
+#define R_TR_TE_IMPL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
+                                R_TR_TE_IMPL_RSVP1_MASK | \
+                                R_TR_TE_IMPL_RSVP2_MASK)
+
+#define R_TR_TE_IMPL_RO_BITS (R_TR_TE_IMPL_VER_MAJOR_MASK | \
+                              R_TR_TE_IMPL_VER_MINOR_MASK | \
+                              R_TR_TE_IMPL_COMP_TYPE_MASK | \
+                              R_TR_TE_IMPL_PROTOCOL_MAJOR_MASK | \
+                              R_TR_TE_IMPL_PROTOCOL_MINOR_MASK)
+
+#define R_TR_TE_IMPL_RESET (BIT(0) | BIT(8))
+
+static RegisterAccessInfo trencoder_regs_info[] = {
+    {   .name = "TR_TE_CONTROL", .addr = A_TR_TE_CONTROL,
+        .rsvd = R_TR_TE_CONTROL_RSVP_BITS,
+        .reset = R_TR_TE_CONTROL_RESET,
+        .ro = R_TR_TE_CONTROL_RO_BITS,
+    },
+    {   .name = "TR_TE_IMPL", .addr = A_TR_TE_IMPL,
+        .rsvd = R_TR_TE_IMPL_RSVP_BITS,
+        .reset = R_TR_TE_IMPL_RESET,
+        .ro = R_TR_TE_IMPL_RO_BITS,
+    },
+};
+
+static uint64_t trencoder_read(void *opaque, hwaddr addr, unsigned size)
+{
+    TraceEncoder *te = TRACE_ENCODER(opaque);
+    RegisterInfo *r = &te->regs_info[addr / 4];
+
+    if (!r->data) {
+        trace_trencoder_read_error(addr);
+        return 0;
+    }
+
+    return register_read(r, ~0, NULL, false);
+}
+
+static void trencoder_write(void *opaque, hwaddr addr,
+                            uint64_t value, unsigned size)
+{
+    TraceEncoder *te = TRACE_ENCODER(opaque);
+    RegisterInfo *r = &te->regs_info[addr / 4];
+
+    if (!r->data) {
+        trace_trencoder_write_error(addr, value);
+        return;
+    }
+
+    register_write(r, value, ~0, NULL, false);
+}
+
+static const MemoryRegionOps trencoder_ops = {
+    .read = trencoder_read,
+    .write = trencoder_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void trencoder_reset(DeviceState *dev)
+{
+    TraceEncoder *te = TRACE_ENCODER(dev);
+
+    for (int i = 0; i < ARRAY_SIZE(te->regs_info); i++) {
+        register_reset(&te->regs_info[i]);
+    }
+}
+
+static void trencoder_realize(DeviceState *dev, Error **errp)
+{
+    TraceEncoder *te = TRACE_ENCODER(dev);
+
+    memory_region_init_io(&te->reg_mem, OBJECT(dev),
+                          &trencoder_ops, te,
+                          TYPE_TRACE_ENCODER,
+                          te->reg_mem_size);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &te->reg_mem);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, te->baseaddr);
+
+    /* RegisterInfo init taken from hw/dma/xlnx-zdma.c */
+    for (int i = 0; i < ARRAY_SIZE(trencoder_regs_info); i++) {
+        uint32_t reg_idx = trencoder_regs_info[i].addr / 4;
+        RegisterInfo *r = &te->regs_info[reg_idx];
+
+        *r = (RegisterInfo) {
+            .data = (uint8_t *)&te->regs[reg_idx],
+            .data_size = sizeof(uint32_t),
+            .access = &trencoder_regs_info[i],
+            .opaque = te,
+        };
+    }
+}
+
+static const Property trencoder_props[] = {
+    /*
+     * We need a link to the associated CPU to
+     * enable/disable tracing.
+     */
+    DEFINE_PROP_LINK("cpu", TraceEncoder, cpu, TYPE_RISCV_CPU, RISCVCPU *),
+    DEFINE_PROP_UINT64("baseaddr", TraceEncoder, baseaddr, 0),
+    DEFINE_PROP_UINT64("dest-baseaddr", TraceEncoder, dest_baseaddr, 0),
+    DEFINE_PROP_UINT64("ramsink-ramstart", TraceEncoder,
+                       ramsink_ramstart, 0),
+    DEFINE_PROP_UINT64("ramsink-ramlimit", TraceEncoder,
+                       ramsink_ramlimit, 0),
+    DEFINE_PROP_UINT32("reg-mem-size", TraceEncoder,
+                       reg_mem_size, TRACE_R_MAX * 4),
+    DEFINE_PROP_INT32("cpu-id", TraceEncoder, cpu_id, 0),
+};
+
+static const VMStateDescription vmstate_trencoder = {
+    .name = TYPE_TRACE_ENCODER,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, TraceEncoder, TRACE_R_MAX),
+        VMSTATE_UINT64(baseaddr, TraceEncoder),
+        VMSTATE_UINT64(dest_baseaddr, TraceEncoder),
+        VMSTATE_UINT64(ramsink_ramstart, TraceEncoder),
+        VMSTATE_UINT64(ramsink_ramlimit, TraceEncoder),
+        VMSTATE_INT32(cpu_id, TraceEncoder),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static void trencoder_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, trencoder_reset);
+    device_class_set_props(dc, trencoder_props);
+    dc->realize = trencoder_realize;
+    dc->vmsd = &vmstate_trencoder;
+}
+
+static const TypeInfo trencoder_info = {
+    .name          = TYPE_TRACE_ENCODER,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(TraceEncoder),
+    .class_init    = trencoder_class_init,
+};
+
+static void trencoder_register_types(void)
+{
+    type_register_static(&trencoder_info);
+}
+
+type_init(trencoder_register_types)
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
new file mode 100644
index 0000000000..71002f58a4
--- /dev/null
+++ b/hw/riscv/trace-encoder.h
@@ -0,0 +1,42 @@
+/*
+ * Emulation of a RISC-V Trace Encoder
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef RISCV_TRACE_ENCODER_H
+#define RISCV_TRACE_ENCODER_H
+
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "system/dma.h"
+#include "qom/object.h"
+#include "cpu.h"
+
+#define TRACE_R_MAX (0xFFF / 4)
+
+struct TraceEncoder {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    RISCVCPU *cpu;
+    int cpu_id;
+
+    MemoryRegion reg_mem;
+    uint32_t reg_mem_size;
+
+    hwaddr baseaddr;
+    hwaddr dest_baseaddr;
+    hwaddr ramsink_ramstart;
+    hwaddr ramsink_ramlimit;
+    uint32_t regs[TRACE_R_MAX];
+    RegisterInfo regs_info[TRACE_R_MAX];
+};
+
+#define TYPE_TRACE_ENCODER "trace-encoder"
+
+OBJECT_DECLARE_SIMPLE_TYPE(TraceEncoder, TRACE_ENCODER)
+
+#endif
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
index b50b14a654..0cbf6ffcb6 100644
--- a/hw/riscv/trace-events
+++ b/hw/riscv/trace-events
@@ -24,3 +24,7 @@ riscv_iommu_hpm_incr_ctr(uint64_t cntr_val) "cntr_val 0x%"PRIx64
 riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d"
 riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64
 riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64
+
+# trace-encoder.c
+trencoder_read_error(uint64_t addr) "addr 0x%" PRIx64
+trencoder_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 02/17] hw/riscv: Trace RAM Sink initial impl
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-20 18:03   ` Konstantin Semichastnov
  2025-11-11 11:46 ` [PATCH v2 03/17] hw/riscv/trace-encoder: add trace start/stop logic Daniel Henrique Barboza
                   ` (15 subsequent siblings)
  17 siblings, 1 reply; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Following the effort to implement the basic support for Efficient Trace
(e-trace) in QEMU we'll add a Trace RAM Sink implementation.

Similar to the Trace Encoder, this is inspired in both the Efficient
Trace for RISC-V [1] and  RISC-V Trace Control Interface Specification
[2] specs. It implements a minimal set of features to get started - only
SMEM will be supported for now.

We'll implement the RAM sink logic in the next patches, although most of
the work will be done by the trace encoder.

[1] https://github.com/riscv-non-isa/riscv-trace-spec/releases/download/v2.0-20250616/riscv-trace-spec-asciidoc.pdf
[2] https://github.com/riscv-non-isa/tg-nexus-trace/releases/download/1.0_Ratified/RISC-V-Trace-Control-Interface.pdf

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/meson.build      |   2 +-
 hw/riscv/trace-events     |   4 +
 hw/riscv/trace-ram-sink.c | 263 ++++++++++++++++++++++++++++++++++++++
 hw/riscv/trace-ram-sink.h |  83 ++++++++++++
 4 files changed, 351 insertions(+), 1 deletion(-)
 create mode 100644 hw/riscv/trace-ram-sink.c
 create mode 100644 hw/riscv/trace-ram-sink.h

diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index b4a9988a62..2aadbe1e50 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -14,6 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
 	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
 riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
 riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
-riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c'))
+riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c', 'trace-ram-sink.c'))
 
 hw_arch += {'riscv': riscv_ss}
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
index 0cbf6ffcb6..14e333fd9e 100644
--- a/hw/riscv/trace-events
+++ b/hw/riscv/trace-events
@@ -28,3 +28,7 @@ riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx
 # trace-encoder.c
 trencoder_read_error(uint64_t addr) "addr 0x%" PRIx64
 trencoder_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
+
+# trace-ram-sink.c
+tr_ramsink_read_error(uint64_t addr) "addr 0x%" PRIx64
+tr_ramsink_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/hw/riscv/trace-ram-sink.c b/hw/riscv/trace-ram-sink.c
new file mode 100644
index 0000000000..e00de80c04
--- /dev/null
+++ b/hw/riscv/trace-ram-sink.c
@@ -0,0 +1,263 @@
+/*
+ * Emulation of a RISC-V Trace RAM Sink
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "trace-ram-sink.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "system/device_tree.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+
+#define R_TR_RAM_CONTROL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
+                                    R_TR_RAM_CONTROL_RSVP1_MASK | \
+                                    R_TR_RAM_CONTROL_RSVP2_MASK | \
+                                    R_TR_RAM_CONTROL_RSVP3_MASK | \
+                                    R_TR_RAM_CONTROL_RSVP4_MASK)
+
+/* trRamEmpty is the only RO field and reset value */
+#define R_TR_RAM_CONTROL_RESET R_TR_RAM_CONTROL_EMPTY_MASK
+#define R_TR_RAM_CONTROL_RO_BITS R_TR_RAM_CONTROL_EMPTY_MASK
+
+#define R_TR_RAM_IMPL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
+                                 R_TR_RAM_IMPL_RSVP1_MASK)
+
+#define R_TR_RAM_IMPL_RO_BITS (R_TR_RAM_IMPL_VER_MAJOR_MASK | \
+                               R_TR_RAM_IMPL_VER_MINOR_MASK | \
+                               R_TR_RAM_IMPL_COMP_TYPE_MASK | \
+                               R_TR_RAM_IMPL_HAS_SRAM_MASK | \
+                               R_TR_RAM_IMPL_HAS_SMEM_MASK)
+
+#define R_TR_RAM_IMPL_RESET (BIT(0) | 0x9 << 8)
+
+static RegisterAccessInfo tr_ramsink_regs_info[] = {
+    {   .name = "TR_RAM_CONTROL", .addr = A_TR_RAM_CONTROL,
+        .rsvd = R_TR_RAM_CONTROL_RSVP_BITS,
+        .reset = R_TR_RAM_CONTROL_RESET,
+        .ro = R_TR_RAM_CONTROL_RO_BITS,
+    },
+    {   .name = "TR_RAM_IMPL", .addr = A_TR_RAM_IMPL,
+        .rsvd = R_TR_RAM_IMPL_RSVP_BITS,
+        .reset = R_TR_RAM_IMPL_RESET,
+        .ro = R_TR_RAM_IMPL_RO_BITS,
+    },
+    {   .name = "TR_RAM_START_LOW", .addr = A_TR_RAM_START_LOW,
+    },
+    {   .name = "TR_RAM_START_HIGH", .addr = A_TR_RAM_START_HIGH,
+    },
+    {   .name = "TR_RAM_LIMIT_LOW", .addr = A_TR_RAM_LIMIT_LOW,
+    },
+    {   .name = "TR_RAM_LIMIT_HIGH", .addr = A_TR_RAM_LIMIT_HIGH,
+    },
+    {   .name = "TR_RAM_WP_LOW", .addr = A_TR_RAM_WP_LOW,
+    },
+    {   .name = "TR_RAM_WP_HIGH", .addr = A_TR_RAM_WP_HIGH,
+    },
+};
+
+static uint64_t tr_ramsink_regread(void *opaque, hwaddr addr, unsigned size)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
+    RegisterInfo *r = &tram->regs_info[addr / 4];
+
+    if (!r->data) {
+        trace_tr_ramsink_read_error(addr);
+        return 0;
+    }
+
+    return register_read(r, ~0, NULL, false);
+}
+
+static void tr_ramsink_regwrite(void *opaque, hwaddr addr,
+                            uint64_t value, unsigned size)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
+    RegisterInfo *r = &tram->regs_info[addr / 4];
+
+    if (!r->data) {
+        trace_tr_ramsink_write_error(addr, value);
+        return;
+    }
+
+    register_write(r, value, ~0, NULL, false);
+}
+
+static const MemoryRegionOps tr_ramsink_regops = {
+    .read = tr_ramsink_regread,
+    .write = tr_ramsink_regwrite,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static uint64_t tr_ramsink_msgread(void *opaque, hwaddr addr, unsigned size)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
+
+    switch (size) {
+    case 1:
+        return tram->msgs[addr];
+    case 2:
+        return (uint16_t)tram->msgs[addr];
+    case 4:
+        return (uint32_t)tram->msgs[addr];
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void tr_ramsink_msgwrite(void *opaque, hwaddr addr,
+                                uint64_t value, unsigned size)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
+
+    switch (size) {
+    case 1:
+        tram->msgs[addr] = value;
+        break;
+    case 2:
+        tram->msgs[addr] = extract16(value, 0, 8);
+        tram->msgs[addr + 1] = extract16(value, 8, 8);
+        break;
+    case 4:
+        tram->msgs[addr] = extract32(value, 0, 8);
+        tram->msgs[addr + 1] = extract32(value, 8, 8);
+        tram->msgs[addr + 2] = extract32(value, 16, 8);
+        tram->msgs[addr + 3] = extract32(value, 24, 8);
+        break;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static const MemoryRegionOps tr_ramsink_smemops = {
+    .read = tr_ramsink_msgread,
+    .write = tr_ramsink_msgwrite,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+};
+
+static void tr_ramsink_setup_regs(TraceRamSink *tram)
+{
+    hwaddr ramlimit = tram->smemaddr + tram->smemsize;
+
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_START_LOW, ADDR,
+                     extract64(tram->smemaddr, 2, 30));
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_START_HIGH, ADDR,
+                     extract64(tram->smemaddr, 32, 32));
+
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_WP_LOW, ADDR,
+                     extract64(tram->smemaddr, 2, 30));
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_WP_HIGH, ADDR,
+                     extract64(tram->smemaddr, 32, 32));
+
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_LIMIT_LOW, ADDR,
+                     extract64(ramlimit, 2, 30));
+    ARRAY_FIELD_DP32(tram->regs, TR_RAM_LIMIT_HIGH, ADDR,
+                     extract64(ramlimit, 32, 32));
+}
+
+static void tr_ramsink_reset(DeviceState *dev)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(dev);
+
+    for (int i = 0; i < ARRAY_SIZE(tram->regs_info); i++) {
+        register_reset(&tram->regs_info[i]);
+    }
+
+    tr_ramsink_setup_regs(tram);
+}
+
+static void tr_ramsink_realize(DeviceState *dev, Error **errp)
+{
+    TraceRamSink *tram = TRACE_RAM_SINK(dev);
+
+    memory_region_init_io(&tram->reg_mem, OBJECT(dev),
+                          &tr_ramsink_regops, tram,
+                          "trace-ram-sink-regs",
+                          tram->reg_mem_size);
+    sysbus_init_mmio(SYS_BUS_DEVICE(tram), &tram->reg_mem);
+    sysbus_mmio_map(SYS_BUS_DEVICE(tram), 0, tram->baseaddr);
+
+    g_assert(tram->smemsize > 0);
+    tram->msgs = g_malloc0(tram->smemsize);
+
+    memory_region_init_io(&tram->smem, OBJECT(dev),
+                          &tr_ramsink_smemops, tram,
+                          "trace-ram-sink-smem",
+                          tram->smemsize);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &tram->smem);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, tram->smemaddr);
+
+    /* RegisterInfo init taken from hw/dma/xlnx-zdma.c */
+    for (int i = 0; i < ARRAY_SIZE(tr_ramsink_regs_info); i++) {
+        uint32_t reg_idx = tr_ramsink_regs_info[i].addr / 4;
+        RegisterInfo *r = &tram->regs_info[reg_idx];
+
+        *r = (RegisterInfo) {
+            .data = (uint8_t *)&tram->regs[reg_idx],
+            .data_size = sizeof(uint32_t),
+            .access = &tr_ramsink_regs_info[i],
+            .opaque = tram,
+        };
+    }
+}
+
+static const Property tr_ramsink_props[] = {
+    DEFINE_PROP_UINT64("baseaddr", TraceRamSink, baseaddr, 0),
+    DEFINE_PROP_UINT64("smemaddr", TraceRamSink, smemaddr, 0),
+    DEFINE_PROP_UINT32("smemsize", TraceRamSink, smemsize, 0),
+    DEFINE_PROP_UINT32("reg-mem-size", TraceRamSink,
+                       reg_mem_size, TR_DEV_REGMAP_SIZE),
+};
+
+static const VMStateDescription vmstate_tr_ramsink = {
+    .name = TYPE_TRACE_RAM_SINK,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, TraceRamSink, TRACE_R_MAX),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static void tr_ramsink_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, tr_ramsink_reset);
+    device_class_set_props(dc, tr_ramsink_props);
+    dc->realize = tr_ramsink_realize;
+    dc->vmsd = &vmstate_tr_ramsink;
+}
+
+static const TypeInfo tr_ramsink_info = {
+    .name          = TYPE_TRACE_RAM_SINK,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(TraceRamSink),
+    .class_init    = tr_ramsink_class_init,
+};
+
+static void tr_ramsink_register_types(void)
+{
+    type_register_static(&tr_ramsink_info);
+}
+
+type_init(tr_ramsink_register_types)
diff --git a/hw/riscv/trace-ram-sink.h b/hw/riscv/trace-ram-sink.h
new file mode 100644
index 0000000000..cd55145669
--- /dev/null
+++ b/hw/riscv/trace-ram-sink.h
@@ -0,0 +1,83 @@
+/*
+ * Emulation of a RISC-V Trace RAM Sink
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef RISCV_TRACE_RAM_SINK_H
+#define RISCV_TRACE_RAM_SINK_H
+
+#include "hw/register.h"
+#include "hw/sysbus.h"
+#include "system/dma.h"
+#include "qom/object.h"
+
+#define TRACE_R_MAX (0xFFF / 4)
+#define TR_DEV_REGMAP_SIZE 0x1000
+
+/*
+ * The Trace Encoder will read/write those regs so put their
+ * declaration in this header.
+ */
+REG32(TR_RAM_CONTROL, 0x0)
+    FIELD(TR_RAM_CONTROL, ACTIVE, 0, 1)
+    FIELD(TR_RAM_CONTROL, ENABLE, 1, 1)
+    FIELD(TR_RAM_CONTROL, EMPTY, 3, 1)
+    FIELD(TR_RAM_CONTROL, MODE, 4, 1)
+    FIELD(TR_RAM_CONTROL, STOP_ON_WRAP, 8, 1)
+    FIELD(TR_RAM_CONTROL, MEM_FORMAT, 9, 2)
+    /* reserved bits */
+    FIELD(TR_RAM_CONTROL, RSVP1, 2, 1)
+    FIELD(TR_RAM_CONTROL, RSVP2, 5, 2)
+    FIELD(TR_RAM_CONTROL, RSVP3, 11, 1)
+    FIELD(TR_RAM_CONTROL, RSVP4, 15, 16)
+
+REG32(TR_RAM_IMPL, 0x4)
+    FIELD(TR_RAM_IMPL, VER_MAJOR, 0, 4)
+    FIELD(TR_RAM_IMPL, VER_MINOR, 4, 4)
+    FIELD(TR_RAM_IMPL, COMP_TYPE, 8, 4)
+    FIELD(TR_RAM_IMPL, HAS_SRAM, 12, 1)
+    FIELD(TR_RAM_IMPL, HAS_SMEM, 13, 1)
+    /* reserved bits */
+    FIELD(TR_RAM_IMPL, RSVP1, 14, 18)
+
+REG32(TR_RAM_START_LOW, 0x010)
+    FIELD(TR_RAM_START_LOW, ADDR, 2, 30)
+REG32(TR_RAM_START_HIGH, 0x014)
+    FIELD(TR_RAM_START_HIGH, ADDR, 0, 32)
+
+REG32(TR_RAM_LIMIT_LOW, 0x018)
+    FIELD(TR_RAM_LIMIT_LOW, ADDR, 2, 30)
+REG32(TR_RAM_LIMIT_HIGH, 0x01C)
+    FIELD(TR_RAM_LIMIT_HIGH, ADDR, 0, 32)
+
+REG32(TR_RAM_WP_LOW, 0x020)
+    FIELD(TR_RAM_WP_LOW, WRAP, 0, 1)
+    FIELD(TR_RAM_WP_LOW, ADDR, 2, 30)
+REG32(TR_RAM_WP_HIGH, 0x024)
+    FIELD(TR_RAM_WP_HIGH, ADDR, 0, 32)
+
+struct TraceRamSink {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    MemoryRegion reg_mem;
+    uint32_t reg_mem_size;
+
+    hwaddr baseaddr;
+    uint32_t regs[TRACE_R_MAX];
+    RegisterInfo regs_info[TRACE_R_MAX];
+
+    hwaddr smemaddr;
+    MemoryRegion smem;
+    uint32_t smemsize;
+    uint8_t *msgs;
+};
+
+#define TYPE_TRACE_RAM_SINK "trace-ram-sink"
+
+OBJECT_DECLARE_SIMPLE_TYPE(TraceRamSink, TRACE_RAM_SINK)
+
+#endif
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 03/17] hw/riscv/trace-encoder: add trace start/stop logic
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 02/17] hw/riscv: Trace RAM Sink " Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 04/17] hw/riscv/virt.c: add trace encoders and trace ram sinks Daniel Henrique Barboza
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Starting/stopping a trace session will rely on two flags:

- trace_next_insn: this flag will be used to signal the translation that
  the encoder wants to know the PC of the current insn. The translation
  helper (to be added) will clear this flag after sending the current
  insn to the encoder;

- trace_running: shows that we're running a trace session and certain
  insns classes (traps, certain jumps and branches) need to report to
  the trace encoder.

These flags are controlled by the trTeInstTracing bit. This bit requires
other two bits to be set beforehand (ACTIVE and ENABLE). We'll revisit
these bits in the future as more features are implemented.

While we're at it, add hardwire bits to indicate which features we're
(not) implementing at this time.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c | 107 +++++++++++++++++++++++++++++++++++++++
 hw/riscv/trace-encoder.h |   4 ++
 2 files changed, 111 insertions(+)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 21bf650a6a..d45e45d17e 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -30,7 +30,11 @@ REG32(TR_TE_CONTROL, 0x0)
     FIELD(TR_TE_CONTROL, INST_TRACING, 2, 1)
     FIELD(TR_TE_CONTROL, EMPTY, 3, 1)
     FIELD(TR_TE_CONTROL, INST_MODE, 4, 3)
+    FIELD(TR_TE_CONTROL, CONTEXT, 9, 1)
+    FIELD(TR_TE_CONTROL, INST_STALL_ENA, 13, 1)
+    FIELD(TR_TE_CONTROL, INHIBIT_SRC, 15, 1)
     FIELD(TR_TE_CONTROL, INST_SYNC_MODE, 16, 2)
+    FIELD(TR_TE_CONTROL, INST_SYNC_MAX, 20, 4)
     FIELD(TR_TE_CONTROL, FORMAT, 24, 3)
     /* reserved bits */
     FIELD(TR_TE_CONTROL, RSVP1, 7, 2)
@@ -75,17 +79,116 @@ REG32(TR_TE_IMPL, 0x4)
 
 #define R_TR_TE_IMPL_RESET (BIT(0) | BIT(8))
 
+REG32(TR_TE_INST_FEATURES, 0x8)
+    FIELD(TR_TE_INST_FEATURES, NO_ADDR_DIFF, 0, 1)
+
+static uint64_t trencoder_te_ctrl_set_hardwire_vals(uint64_t input)
+{
+    input = FIELD_DP32(input, TR_TE_CONTROL, INST_MODE, 0x6);
+    input = FIELD_DP32(input, TR_TE_CONTROL, CONTEXT, 0);
+    input = FIELD_DP32(input, TR_TE_CONTROL, INST_STALL_ENA, 0);
+    input = FIELD_DP32(input, TR_TE_CONTROL, INHIBIT_SRC, 1);
+    input = FIELD_DP32(input, TR_TE_CONTROL, FORMAT, 0);
+
+    /* SYNC_MODE and SYNC_MAX will be revisited */
+    input = FIELD_DP32(input, TR_TE_CONTROL, INST_SYNC_MODE, 0);
+    input = FIELD_DP32(input, TR_TE_CONTROL, INST_SYNC_MAX, 0);
+
+    return input;
+}
+
+static uint64_t trencoder_te_ctrl_prew(RegisterInfo *reg, uint64_t val)
+{
+    TraceEncoder *te = TRACE_ENCODER(reg->opaque);
+    uint32_t trTeActive = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL, ACTIVE);
+    uint32_t trTeInstTracing = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL,
+                                                INST_TRACING);
+    uint32_t temp;
+
+    val = trencoder_te_ctrl_set_hardwire_vals(val);
+
+    if (!trTeActive) {
+        /*
+         * 11.2 Reset and discovery, table 58, trTeControl = 0x1
+         * means "Release from reset and set all defaults." Do
+         * that only if trTeActive is 0.
+         */
+        if (val == 0x1) {
+            val = FIELD_DP32(val, TR_TE_CONTROL, EMPTY, 1);
+
+            return val;
+        }
+
+        /*
+         * 11.3 Enabling and Disabling hints that the device must
+         * be activated first (trTeActive = 1), then enabled.
+         * Do not enable the device if it's not active
+         * beforehand.
+         */
+        temp = FIELD_EX32(val, TR_TE_CONTROL, ENABLE);
+        if (temp) {
+            val = FIELD_DP32(val, TR_TE_CONTROL, ENABLE, 0);
+        }
+    }
+
+    /*
+     * Do not allow inst tracing to start if the device isn't
+     * already enabled. Do not allow enabling the devince and
+     * and enable tracing at the same time.
+     */
+    if (!te->enabled && trTeInstTracing) {
+        val = FIELD_DP32(val, TR_TE_CONTROL, INST_TRACING, 0);
+    }
+
+    return val;
+}
+
+static void trencoder_te_ctrl_postw(RegisterInfo *reg, uint64_t val)
+{
+    TraceEncoder *te = TRACE_ENCODER(reg->opaque);
+    uint32_t trTeActive = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL, ACTIVE);
+    uint32_t trTeEnable = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL, ENABLE);
+    uint32_t trTeInstTracing = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL,
+                                                INST_TRACING);
+
+    if (!trTeActive) {
+        te->enabled = false;
+        te->trace_running = false;
+        te->trace_next_insn = false;
+        return;
+    }
+
+    if (te->enabled && !trTeEnable) {
+        /* TODO: this should cause a pending trace data flush. */
+    }
+
+    te->enabled = trTeEnable ? true : false;
+
+    if (!te->trace_running && trTeInstTracing) {
+        /* Starting trace. Ask the CPU for the first trace insn */
+        te->trace_next_insn = true;
+    }
+
+    te->trace_running = trTeInstTracing ? true : false;
+}
+
 static RegisterAccessInfo trencoder_regs_info[] = {
     {   .name = "TR_TE_CONTROL", .addr = A_TR_TE_CONTROL,
         .rsvd = R_TR_TE_CONTROL_RSVP_BITS,
         .reset = R_TR_TE_CONTROL_RESET,
         .ro = R_TR_TE_CONTROL_RO_BITS,
+        .pre_write = &trencoder_te_ctrl_prew,
+        .post_write = &trencoder_te_ctrl_postw,
     },
     {   .name = "TR_TE_IMPL", .addr = A_TR_TE_IMPL,
         .rsvd = R_TR_TE_IMPL_RSVP_BITS,
         .reset = R_TR_TE_IMPL_RESET,
         .ro = R_TR_TE_IMPL_RO_BITS,
     },
+    {   .name = "TR_TE_INST_FEATURES", .addr = A_TR_TE_INST_FEATURES,
+        .reset = R_TR_TE_INST_FEATURES_NO_ADDR_DIFF_MASK,
+        .ro = ~0,
+    },
 };
 
 static uint64_t trencoder_read(void *opaque, hwaddr addr, unsigned size)
@@ -132,6 +235,10 @@ static void trencoder_reset(DeviceState *dev)
     for (int i = 0; i < ARRAY_SIZE(te->regs_info); i++) {
         register_reset(&te->regs_info[i]);
     }
+
+    te->enabled = false;
+    te->trace_running = false;
+    te->trace_next_insn = false;
 }
 
 static void trencoder_realize(DeviceState *dev, Error **errp)
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index 71002f58a4..001d872514 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -33,6 +33,10 @@ struct TraceEncoder {
     hwaddr ramsink_ramlimit;
     uint32_t regs[TRACE_R_MAX];
     RegisterInfo regs_info[TRACE_R_MAX];
+
+    bool enabled;
+    bool trace_running;
+    bool trace_next_insn;
 };
 
 #define TYPE_TRACE_ENCODER "trace-encoder"
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 04/17] hw/riscv/virt.c: add trace encoders and trace ram sinks
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (2 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 03/17] hw/riscv/trace-encoder: add trace start/stop logic Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 05/17] hw/riscv/virt.c add trace encoder and ramsink fdt nodes Daniel Henrique Barboza
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Add the minimal topology required for e-trace support in the virt
machine, e.g. each CPU will have a single trace encoder, and each trace
encoder will communicate to a single associated trace ram sink.

At this moment we're not going to support more complex topologies with
trace funnels and so on.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/virt.c            | 77 ++++++++++++++++++++++++++++++++++++++
 include/hw/riscv/virt.h    |  2 +
 target/riscv/cpu.h         |  9 +++++
 target/riscv/tcg/tcg-cpu.c |  5 +++
 4 files changed, 93 insertions(+)

diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 17909206c7..b1a4d63efd 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -58,6 +58,8 @@
 #include "qapi/qapi-visit-common.h"
 #include "hw/virtio/virtio-iommu.h"
 #include "hw/uefi/var-service-api.h"
+#include "hw/riscv/trace-encoder.h"
+#include "hw/riscv/trace-ram-sink.h"
 
 /* KVM AIA only supports APLIC MSI. APLIC Wired is always emulated by QEMU. */
 static bool virt_use_kvm_aia_aplic_imsic(RISCVVirtAIAType aia_type)
@@ -79,6 +81,17 @@ static bool virt_aclint_allowed(void)
     return tcg_enabled() || qtest_enabled();
 }
 
+#define TR_DEV_REGMAP_SIZE 0x1000
+/* For VIRT_CPUS_MAX = 512: TRACE_DEV_REG_MAX = 0x200000 */
+#define TRACE_DEV_REG_MAX (TR_DEV_REGMAP_SIZE * VIRT_CPUS_MAX)
+
+/*
+ * 64k for the RAM Sink that includes the 4k (0x1000)
+ * for regs, for each possible CPU. For 512 max CPUs,
+ * total size = 0x2000000.
+ */
+#define TRACE_RAM_SINK_SIZE (1UL << 16)
+
 static const MemMapEntry virt_memmap[] = {
     [VIRT_DEBUG] =        {        0x0,         0x100 },
     [VIRT_MROM] =         {     0x1000,        0xf000 },
@@ -88,7 +101,9 @@ static const MemMapEntry virt_memmap[] = {
     [VIRT_ACLINT_SSWI] =  {  0x2F00000,        0x4000 },
     [VIRT_PCIE_PIO] =     {  0x3000000,       0x10000 },
     [VIRT_IOMMU_SYS] =    {  0x3010000,        0x1000 },
+    [VIRT_TR_ENCODERS] =  {  0x3020000, TRACE_DEV_REG_MAX },
     [VIRT_PLATFORM_BUS] = {  0x4000000,     0x2000000 },
+    [VIRT_TR_RAM_SINKS] = {  0x6000000, TRACE_RAM_SINK_SIZE * VIRT_CPUS_MAX },
     [VIRT_PLIC] =         {  0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },
     [VIRT_APLIC_M] =      {  0xc000000, APLIC_SIZE(VIRT_CPUS_MAX) },
     [VIRT_APLIC_S] =      {  0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },
@@ -1525,6 +1540,64 @@ static void virt_machine_done(Notifier *notifier, void *data)
     }
 }
 
+/*
+ * Must be called after 'soc' realize since it
+ * uses CPU objs.
+ */
+static void virt_init_socket_trace_hw(RISCVVirtState *s, int socket_num)
+{
+    for (int cpu = 0; cpu < s->soc[socket_num].num_harts; cpu++) {
+        RISCVCPU *cpu_ptr = &s->soc[socket_num].harts[cpu];
+        DeviceState *trencoder, *ram_sink;
+        uint64_t trencoder_addr, ram_sink_addr, smem_addr;
+        uint32_t smem_size = TRACE_RAM_SINK_SIZE - TR_DEV_REGMAP_SIZE;
+
+        ram_sink = qdev_new(TYPE_TRACE_RAM_SINK);
+
+        ram_sink_addr = virt_memmap[VIRT_TR_RAM_SINKS].base +
+                        TRACE_RAM_SINK_SIZE * cpu;
+        /* smem is located right after ram sink base */
+        smem_addr = ram_sink_addr + TR_DEV_REGMAP_SIZE;
+
+        object_property_set_uint(OBJECT(ram_sink), "baseaddr",
+                                 ram_sink_addr, &error_fatal);
+        object_property_set_uint(OBJECT(ram_sink), "smemaddr",
+                                 smem_addr, &error_fatal);
+        object_property_set_uint(OBJECT(ram_sink), "smemsize",
+                                 smem_size, &error_fatal);
+        sysbus_realize_and_unref(SYS_BUS_DEVICE(ram_sink), &error_fatal);
+
+        /*
+         * We can't do object_property_set_link() because we're
+         * coming after cpu.realize() (the riscv_hart obj creates
+         * the CPU objs in its realize() since it has no init).
+         * We need changes in how riscv_hart works to use
+         * set_link() and to not manually realize the trace
+         * encoder.
+         *
+         * For now do everything manually.
+         */
+        trencoder = qdev_new(TYPE_TRACE_ENCODER);
+        cpu_ptr->trencoder = OBJECT(trencoder);
+
+        trencoder_addr = virt_memmap[VIRT_TR_ENCODERS].base +
+                         TR_DEV_REGMAP_SIZE * cpu;
+
+        object_property_set_link(OBJECT(trencoder), "cpu",
+                                 OBJECT(cpu_ptr), &error_fatal);
+        object_property_set_int(OBJECT(trencoder), "cpu-id", cpu, &error_fatal);
+        object_property_set_uint(OBJECT(trencoder), "baseaddr",
+                                 trencoder_addr, &error_fatal);
+        object_property_set_uint(OBJECT(trencoder), "dest-baseaddr",
+                                 ram_sink_addr, &error_fatal);
+        object_property_set_uint(OBJECT(trencoder), "ramsink-ramstart",
+                                 smem_addr, &error_fatal);
+        object_property_set_uint(OBJECT(trencoder), "ramsink-ramlimit",
+                                 smem_addr + smem_size, &error_fatal);
+        sysbus_realize_and_unref(SYS_BUS_DEVICE(trencoder), &error_fatal);
+    }
+}
+
 static void virt_machine_init(MachineState *machine)
 {
     RISCVVirtState *s = RISCV_VIRT_MACHINE(machine);
@@ -1580,6 +1653,10 @@ static void virt_machine_init(MachineState *machine)
                                 hart_count, &error_abort);
         sysbus_realize(SYS_BUS_DEVICE(&s->soc[i]), &error_fatal);
 
+        if (tcg_enabled()) {
+            virt_init_socket_trace_hw(s, i);
+        }
+
         if (virt_aclint_allowed() && s->have_aclint) {
             if (s->aia_type == VIRT_AIA_TYPE_APLIC_IMSIC) {
                 /* Per-socket ACLINT MTIMER */
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 7b4c2c8b7d..e2aa6fbbcd 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -88,6 +88,8 @@ enum {
     VIRT_PLATFORM_BUS,
     VIRT_PCIE_ECAM,
     VIRT_IOMMU_SYS,
+    VIRT_TR_ENCODERS,
+    VIRT_TR_RAM_SINKS,
 };
 
 enum {
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 36e7f10037..12251e4d94 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -553,6 +553,15 @@ struct ArchCPU {
     /* Mapping of events to counters */
     GHashTable *pmu_event_ctr_map;
     const GPtrArray *decoders;
+
+#ifndef CONFIG_USER_ONLY
+    /*
+     * Associated Trace Encoder. It will not be NULL if
+     * we're running with TCG and initialized manually by
+     * the board.
+     */
+    Object *trencoder;
+#endif
 };
 
 typedef struct RISCVCSR RISCVCSR;
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index d3968251fa..a381ee2840 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -37,6 +37,7 @@
 #include "hw/boards.h"
 #include "system/tcg.h"
 #include "exec/icount.h"
+#include "hw/riscv/trace-encoder.h"
 #endif
 
 /* Hash that stores user set extensions */
@@ -1297,6 +1298,10 @@ static bool riscv_tcg_cpu_realize(CPUState *cs, Error **errp)
     if (riscv_has_ext(env, RVH)) {
         env->mideleg = MIP_VSSIP | MIP_VSTIP | MIP_VSEIP | MIP_SGEIP;
     }
+
+    if (cpu->trencoder) {
+        qdev_realize(DEVICE(cpu->trencoder), NULL, &error_fatal);
+    }
 #endif
 
     return true;
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 05/17] hw/riscv/virt.c add trace encoder and ramsink fdt nodes
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (3 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 04/17] hw/riscv/virt.c: add trace encoders and trace ram sinks Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 06/17] hw/riscv: add e-trace message helpers Daniel Henrique Barboza
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Mayuresh Chitale, Daniel Henrique Barboza

From: Mayuresh Chitale <mchitale@ventanamicro.com>

The trace encoder and trace ramsink nodes should confirm to the bindings
described in "riscv,trace-component.yaml" in the Linux kernel. That way,
encoder and ramsink devices get populated on the rvtrace bus and perf
record is able to capture the trace data in the auxtrace section as
expected.

Signed-off-by: Mayuresh Chitale <mchitale@ventanamicro.com>
Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/virt.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index b1a4d63efd..30e89a6c5a 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1142,6 +1142,62 @@ static void create_fdt_iommu(RISCVVirtState *s, uint16_t bdf)
     s->pci_iommu_bdf = bdf;
 }
 
+static void create_fdt_rvtrace(RISCVVirtState *s)
+{
+    static const char * const tr_compat[2] = { "qemu,trace-component",
+                                               "riscv,trace-component" };
+    g_autofree char *cpu_name = NULL, *ram_sink_name = NULL,
+                    *trencoder_name = NULL, *ep = NULL;
+    MachineState *ms = MACHINE(s);
+    int socket_count = riscv_socket_count(ms), i;
+    uint64_t addr, size = 0x100;
+    uint32_t rs_phandle;
+    RISCVCPU *cpu_ptr;
+
+
+    for (i = 0; i < socket_count; i++) {
+        for (int cpu = 0; cpu < s->soc[i].num_harts; cpu++) {
+            cpu_ptr = &s->soc[i].harts[cpu];
+            if (!cpu_ptr->trencoder) {
+                continue;
+            }
+            cpu_name = g_strdup_printf("/cpus/cpu@%d",
+                                       s->soc[i].hartid_base + cpu);
+            ram_sink_name = g_strdup_printf("/soc/ramsink@%d",
+                                            s->soc[i].hartid_base + cpu);
+            qemu_fdt_add_subnode(ms->fdt, ram_sink_name);
+            addr = object_property_get_uint(cpu_ptr->trencoder, "dest-baseaddr",
+                                            &error_abort);
+            qemu_fdt_setprop_sized_cells(ms->fdt, ram_sink_name, "reg", 2, addr,
+                                         2, size);
+            qemu_fdt_setprop_string_array(ms->fdt, ram_sink_name, "compatible",
+                                          (char **)&tr_compat,
+                                          ARRAY_SIZE(tr_compat));
+            qemu_fdt_setprop_phandle(ms->fdt, ram_sink_name, "cpu", cpu_name);
+            ep = g_strdup_printf("%s/in-ports/port/endpoint", ram_sink_name);
+            qemu_fdt_add_path(ms->fdt, ep);
+            rs_phandle = qemu_fdt_alloc_phandle(ms->fdt);
+            qemu_fdt_setprop_cell(ms->fdt, ep, "phandle", rs_phandle);
+
+
+            trencoder_name = g_strdup_printf("/soc/encoder@%d",
+                                             s->soc[i].hartid_base + cpu);
+            qemu_fdt_add_subnode(ms->fdt, trencoder_name);
+            addr = object_property_get_uint(cpu_ptr->trencoder, "baseaddr",
+                                            &error_abort);
+            qemu_fdt_setprop_sized_cells(ms->fdt, trencoder_name, "reg", 2,
+                                         addr, 2, size);
+            qemu_fdt_setprop_string_array(ms->fdt, trencoder_name, "compatible",
+                                          (char **)&tr_compat,
+                                          ARRAY_SIZE(tr_compat));
+            qemu_fdt_setprop_phandle(ms->fdt, trencoder_name, "cpus", cpu_name);
+            ep = g_strdup_printf("%s/out-ports/port/endpoint", trencoder_name);
+            qemu_fdt_add_path(ms->fdt, ep);
+            qemu_fdt_setprop_cell(ms->fdt, ep, "remote-endpoint", rs_phandle);
+        }
+    }
+}
+
 static void finalize_fdt(RISCVVirtState *s)
 {
     uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1;
@@ -1166,6 +1222,8 @@ static void finalize_fdt(RISCVVirtState *s)
     create_fdt_uart(s, irq_mmio_phandle);
 
     create_fdt_rtc(s, irq_mmio_phandle);
+
+    create_fdt_rvtrace(s);
 }
 
 static void create_fdt(RISCVVirtState *s)
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 06/17] hw/riscv: add e-trace message helpers
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (4 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 05/17] hw/riscv/virt.c add trace encoder and ramsink fdt nodes Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 07/17] target/riscv: add initial trace instrumentation Daniel Henrique Barboza
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Before making the trace encoder writing into the RAM sink we need a way
to encode the messages/packets. The encoding is LSB (least significant
bit) based. The doc "Efficient Trace for RISC-V", Chapter 7, mentions:

"The remainder of this section describes the contents of the payload
portion which should be independent of the infrastructure. In each
table, the fields are listed in transmission order: first field in
the table is transmitted first, and multi-bit fields are transmitted
LSB first."

The "RISC-V Trace Control Interface Specification" docs, Chapter 7,
states when talking about the Trace RAM Sink:

"Trace data is placed in memory in LSB order (first byte of trace
packet/data is placed on LSB)."

This means that the LSB encoding must be used to write into the RAM Sink
memory, which is our goal.

The design we're going for is to have all these encoder helpers, along
with the message formats, in a separated file. The trace encoder will
make use of these helpers to blindly write a byte array with the packet
desired, and then write it as is in the RAM Sink.

We'll start by modeling the synchronisation packet first, adding more
formats as we increment the Trace Encoder capabilities.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/meson.build         |  3 +-
 hw/riscv/rv-trace-messages.c | 94 ++++++++++++++++++++++++++++++++++++
 hw/riscv/rv-trace-messages.h | 25 ++++++++++
 3 files changed, 121 insertions(+), 1 deletion(-)
 create mode 100644 hw/riscv/rv-trace-messages.c
 create mode 100644 hw/riscv/rv-trace-messages.h

diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 2aadbe1e50..7d3576fcdf 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -14,6 +14,7 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
 	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
 riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
 riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
-riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c', 'trace-ram-sink.c'))
+riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c',
+        'trace-ram-sink.c', 'rv-trace-messages.c'))
 
 hw_arch += {'riscv': riscv_ss}
diff --git a/hw/riscv/rv-trace-messages.c b/hw/riscv/rv-trace-messages.c
new file mode 100644
index 0000000000..215135dd47
--- /dev/null
+++ b/hw/riscv/rv-trace-messages.c
@@ -0,0 +1,94 @@
+/*
+ * Helpers for RISC-V Trace Messages
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "rv-trace-messages.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+typedef struct RVTraceMessageHeader {
+    uint8_t length:5;
+    uint8_t flow:2;
+    uint8_t extend:1;
+} RVTraceMessageHeader;
+#define HEADER_SIZE 1
+
+/*
+ * Format 3 subformat 0 without 'time' and 'context' fields
+ */
+typedef struct RVTraceSyncPayload {
+    uint8_t format:2;
+    uint8_t subformat:2;
+    uint8_t branch:1;
+    uint8_t privilege:3;
+    uint32_t addressLow;
+    uint32_t addressHigh;
+} RVTraceSyncPayload;
+#define SYNC_PAYLOAD_SIZE_64BITS 9
+
+static void rv_etrace_write_bits(uint8_t *bytes, uint32_t bit_pos,
+                                 uint32_t num_bits, uint32_t val)
+{
+    uint32_t pos, byte_index, byte_pos, byte_bits = 0;
+
+    if (!num_bits || 32 < num_bits) {
+        return;
+    }
+
+    for (pos = 0; pos < num_bits; pos += byte_bits) {
+        byte_index = (bit_pos + pos) >> 3;
+        byte_pos = (bit_pos + pos) & 0x7;
+        byte_bits = (8 - byte_pos) < (num_bits - pos) ?
+                    (8 - byte_pos) : (num_bits - pos);
+        bytes[byte_index] &= ~(((1U << byte_bits) - 1) << byte_pos);
+        bytes[byte_index] |= ((val >> pos) & ((1U << byte_bits) - 1)) << byte_pos;
+    }
+}
+
+static void rv_etrace_write_header(uint8_t *buf, RVTraceMessageHeader header)
+{
+    /* flow and extend are always zero, i.e just write length */
+    rv_etrace_write_bits(buf, 0, 5, header.length);
+}
+
+size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
+                                      TracePrivLevel priv_level)
+{
+    RVTraceSyncPayload payload = {.format = 0b11,
+                                  .subformat = 0b00,
+                                  .branch = 1,
+                                  .privilege = priv_level};
+    RVTraceMessageHeader header = {.flow = 0, .extend = 0,
+                                   .length = SYNC_PAYLOAD_SIZE_64BITS};
+    uint8_t bit_pos;
+
+    payload.addressLow = extract64(pc, 0, 32);
+    payload.addressHigh = extract64(pc, 32, 32);
+
+    rv_etrace_write_header(buf, header);
+    bit_pos = 8;
+
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.format);
+    bit_pos += 2;
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.subformat);
+    bit_pos += 2;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.branch);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 3, payload.privilege);
+    bit_pos += 3;
+
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressLow);
+    bit_pos += 32;
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressHigh);
+
+    return HEADER_SIZE + SYNC_PAYLOAD_SIZE_64BITS;
+}
diff --git a/hw/riscv/rv-trace-messages.h b/hw/riscv/rv-trace-messages.h
new file mode 100644
index 0000000000..aeafea8849
--- /dev/null
+++ b/hw/riscv/rv-trace-messages.h
@@ -0,0 +1,25 @@
+/*
+ * Helpers for RISC-V Trace Messages
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef RISCV_RV_TRACE_MESSAGES_H
+#define RISCV_RV_TRACE_MESSAGES_H
+
+typedef enum {
+    U = 0,
+    S_HS = 1,
+    RESERVED = 2,
+    M = 3,
+    D = 4,
+    VU = 5,
+    VS = 6,
+} TracePrivLevel;
+
+size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
+                                      TracePrivLevel priv_level);
+
+#endif
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 07/17] target/riscv: add initial trace instrumentation
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (5 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 06/17] hw/riscv: add e-trace message helpers Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 08/17] hw/riscv/trace-encoder: write e-trace packets to RAM sink Daniel Henrique Barboza
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

We'll have to instrument some RISC-V translations, as well as the start
of a translation block, communicating with the associated trace encoder
in case we're running a trace session. The trace encoder needs to report
the first insn of the session so we'll start with that.

Unfortunately we've run out of bits in tb_flags, meaning we can't use
riscv_tr_init_disas_context() and riscv_get_tb_cpu_state() to
communicate whether we're running a trace session. One alternative would
be to fold the "trace is running" logic in each trace helper. That would
make all code paths, regardless of even having a trace encoder
associated with the CPU, to check for trace encoder existence.

Another alternative, which is implemented here, is to add an additional
mirror flag 'trace_running' in CPURISCVState and turn it into a TCG
global 'cpu_trace_running'. Each trace helper call is gated via this
global, allowing us to skip calling trace helpers when we're not running
a trace session.

In case we end up increasing the size of tb_flags in the future we
should revisit this code and use tb_flags instead.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c    | 17 +++++++++++++++++
 hw/riscv/trace-encoder.h    |  4 ++++
 hw/riscv/trace-events       |  1 +
 target/riscv/cpu.h          |  2 ++
 target/riscv/helper.h       |  4 ++++
 target/riscv/meson.build    |  3 ++-
 target/riscv/trace_helper.c | 36 ++++++++++++++++++++++++++++++++++++
 target/riscv/translate.c    | 10 ++++++++++
 8 files changed, 76 insertions(+), 1 deletion(-)
 create mode 100644 target/riscv/trace_helper.c

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index d45e45d17e..9701ce43cf 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -150,11 +150,15 @@ static void trencoder_te_ctrl_postw(RegisterInfo *reg, uint64_t val)
     uint32_t trTeEnable = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL, ENABLE);
     uint32_t trTeInstTracing = ARRAY_FIELD_EX32(te->regs, TR_TE_CONTROL,
                                                 INST_TRACING);
+    RISCVCPU *cpu = te->cpu;
+    CPURISCVState *env = &cpu->env;
 
     if (!trTeActive) {
         te->enabled = false;
         te->trace_running = false;
         te->trace_next_insn = false;
+
+        env->trace_running = false;
         return;
     }
 
@@ -170,6 +174,7 @@ static void trencoder_te_ctrl_postw(RegisterInfo *reg, uint64_t val)
     }
 
     te->trace_running = trTeInstTracing ? true : false;
+    env->trace_running = te->trace_running;
 }
 
 static RegisterAccessInfo trencoder_regs_info[] = {
@@ -231,6 +236,8 @@ static const MemoryRegionOps trencoder_ops = {
 static void trencoder_reset(DeviceState *dev)
 {
     TraceEncoder *te = TRACE_ENCODER(dev);
+    RISCVCPU *cpu = te->cpu;
+    CPURISCVState *env = &cpu->env;
 
     for (int i = 0; i < ARRAY_SIZE(te->regs_info); i++) {
         register_reset(&te->regs_info[i]);
@@ -239,6 +246,7 @@ static void trencoder_reset(DeviceState *dev)
     te->enabled = false;
     te->trace_running = false;
     te->trace_next_insn = false;
+    env->trace_running = false;
 }
 
 static void trencoder_realize(DeviceState *dev, Error **errp)
@@ -266,6 +274,14 @@ static void trencoder_realize(DeviceState *dev, Error **errp)
     }
 }
 
+void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
+{
+    TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+
+    trencoder->first_pc = pc;
+    trace_trencoder_first_trace_insn(pc);
+}
+
 static const Property trencoder_props[] = {
     /*
      * We need a link to the associated CPU to
@@ -294,6 +310,7 @@ static const VMStateDescription vmstate_trencoder = {
         VMSTATE_UINT64(ramsink_ramstart, TraceEncoder),
         VMSTATE_UINT64(ramsink_ramlimit, TraceEncoder),
         VMSTATE_INT32(cpu_id, TraceEncoder),
+        VMSTATE_UINT64(first_pc, TraceEncoder),
         VMSTATE_END_OF_LIST(),
     }
 };
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index 001d872514..cf3177aefe 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -27,6 +27,8 @@ struct TraceEncoder {
     MemoryRegion reg_mem;
     uint32_t reg_mem_size;
 
+    uint64_t first_pc;
+
     hwaddr baseaddr;
     hwaddr dest_baseaddr;
     hwaddr ramsink_ramstart;
@@ -43,4 +45,6 @@ struct TraceEncoder {
 
 OBJECT_DECLARE_SIMPLE_TYPE(TraceEncoder, TRACE_ENCODER)
 
+void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc);
+
 #endif
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
index 14e333fd9e..dc25377acf 100644
--- a/hw/riscv/trace-events
+++ b/hw/riscv/trace-events
@@ -28,6 +28,7 @@ riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx
 # trace-encoder.c
 trencoder_read_error(uint64_t addr) "addr 0x%" PRIx64
 trencoder_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
+trencoder_first_trace_insn(uint64_t pc) "pc 0x%" PRIx64
 
 # trace-ram-sink.c
 tr_ramsink_read_error(uint64_t addr) "addr 0x%" PRIx64
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 12251e4d94..16c6c280a8 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -514,6 +514,8 @@ struct CPUArchState {
     target_ulong rnmip;
     uint64_t rnmi_irqvec;
     uint64_t rnmi_excpvec;
+
+    bool trace_running;
 };
 
 /*
diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index b785456ee0..e80320ad16 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -128,6 +128,10 @@ DEF_HELPER_4(csrrw, tl, env, int, tl, tl)
 DEF_HELPER_2(csrr_i128, tl, env, int)
 DEF_HELPER_4(csrw_i128, void, env, int, tl, tl)
 DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl)
+
+/* Trace helpers (should be put inside ifdef) */
+DEF_HELPER_2(trace_insn, void, env, i64)
+
 #ifndef CONFIG_USER_ONLY
 DEF_HELPER_1(sret, tl, env)
 DEF_HELPER_1(mret, tl, env)
diff --git a/target/riscv/meson.build b/target/riscv/meson.build
index fdefe88ccd..564e2da5f2 100644
--- a/target/riscv/meson.build
+++ b/target/riscv/meson.build
@@ -26,7 +26,8 @@ riscv_ss.add(files(
   'm128_helper.c',
   'crypto_helper.c',
   'zce_helper.c',
-  'vcrypto_helper.c'
+  'vcrypto_helper.c',
+  'trace_helper.c'
 ))
 
 riscv_system_ss = ss.source_set()
diff --git a/target/riscv/trace_helper.c b/target/riscv/trace_helper.c
new file mode 100644
index 0000000000..ed84e6f79a
--- /dev/null
+++ b/target/riscv/trace_helper.c
@@ -0,0 +1,36 @@
+/*
+ * RISC-V Trace Support TCG helpers
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "cpu.h"
+#include "trace.h"
+#include "exec/helper-proto.h"
+
+#ifndef CONFIG_USER_ONLY
+#include "hw/riscv/trace-encoder.h"
+#endif
+
+#ifndef CONFIG_USER_ONLY
+void helper_trace_insn(CPURISCVState *env, uint64_t pc)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    TraceEncoder *te = TRACE_ENCODER(cpu->trencoder);
+
+    if (te->trace_next_insn) {
+        trencoder_set_first_trace_insn(cpu->trencoder, pc);
+        te->trace_next_insn = false;
+    }
+}
+#else /* #ifndef CONFIG_USER_ONLY */
+void helper_trace_insn(CPURISCVState *env, uint64_t pc)
+{
+    return;
+}
+#endif /* #ifndef CONFIG_USER_ONLY*/
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index e1f4dc5ffd..ff288051e3 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -43,6 +43,9 @@ static TCGv_i64 cpu_fpr[32]; /* assume F and D extensions */
 static TCGv load_res;
 static TCGv load_val;
 
+/* TODO: this should be a tb_flag instead of a global */
+static TCGv cpu_trace_running;
+
 /*
  * If an operation is being performed on less than TARGET_LONG_BITS,
  * it may require the inputs to be sign- or zero-extended; which will
@@ -1340,6 +1343,11 @@ static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
 
 static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)
 {
+    TCGLabel *skip = gen_new_label();
+
+    tcg_gen_brcondi_tl(TCG_COND_EQ, cpu_trace_running, 0, skip);
+    gen_helper_trace_insn(tcg_env, tcg_constant_i64(db->pc_first));
+    gen_set_label(skip);
 }
 
 static void riscv_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu)
@@ -1464,4 +1472,6 @@ void riscv_translate_init(void)
                              "load_res");
     load_val = tcg_global_mem_new(tcg_env, offsetof(CPURISCVState, load_val),
                              "load_val");
+    cpu_trace_running = tcg_global_mem_new(tcg_env,
+                offsetof(CPURISCVState, trace_running), "trace_running");
 }
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 08/17] hw/riscv/trace-encoder: write e-trace packets to RAM sink
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (6 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 07/17] target/riscv: add initial trace instrumentation Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 09/17] test/qtest: add riscv-trace-test.c Daniel Henrique Barboza
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

We have the needed pieces to make the trace encoder write messages (or
packets, in e-trace lingo) in its associated RAM sink.

We'll start by writing a sync message that we're already able to fetch
from the TCG instruction train. The trace encoder must be able to
read/write TR_RAM_WP RAM sink regs to know where it is supposed to write
packets, thus a handful of helpers are added to manipulate both the ram
sink SMEM memory and relevant registers.

The bulk of the work is done in trencoder_send_message_smem() where the
trace encoder writes each trace packet. The 'wrap' mechanism is
implemented to keep writing the RAM sink memory even after a SMEM
workflow.  If we can't fit a full packet in the end of the ring buffer
we're filling with NULLs and writing a new packet back to ramstart.

In an overflow event we'll wrap the TR_RAM_WP_LOW WRAP bit and userspace
is supposed to clear it after the wrapping was handled (i.e. userspace
read the whole ring buffer back to the start).

We're also allowing userspace to freely set ramsink and ramstart before
the trace session begins, allowing for more freedom w.r.t where the SMEM
will be written by the encoder. Note that this also means that userspace
is responsible for setting at least TR_RAM_WP_LOW and TR_RAM_WP_HIGH
before starting a trace session.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c | 121 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 9701ce43cf..16c9475d46 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -20,6 +20,34 @@
 #include "system/device_tree.h"
 #include "hw/register.h"
 #include "cpu.h"
+#include "hw/riscv/trace-ram-sink.h"
+#include "rv-trace-messages.h"
+
+/*
+ * Size of header + payload since we're not sending
+ * srcID and timestamp.
+ */
+#define TRACE_MSG_MAX_SIZE 32
+
+static TracePrivLevel trencoder_get_curr_priv_level(TraceEncoder *te)
+{
+    CPURISCVState *env = &te->cpu->env;
+
+    switch (env->priv) {
+    case PRV_U:
+        return env->virt_enabled ? VU : U;
+    case PRV_S:
+        return env->virt_enabled ? VS : S_HS;
+    case PRV_M:
+        return M;
+    }
+
+    /*
+     * Return a reserved value to signal an error.
+     * TODO: handle Debug (D).
+     */
+    return RESERVED;
+}
 
 /*
  * trTeControl register fields
@@ -82,6 +110,41 @@ REG32(TR_TE_IMPL, 0x4)
 REG32(TR_TE_INST_FEATURES, 0x8)
     FIELD(TR_TE_INST_FEATURES, NO_ADDR_DIFF, 0, 1)
 
+static uint32_t trencoder_read_reg(TraceEncoder *te, uint32_t reg_addr)
+{
+    hwaddr addr = te->dest_baseaddr + reg_addr;
+    uint32_t val;
+
+    cpu_physical_memory_read(addr, &val, sizeof(uint32_t));
+    return val;
+}
+
+static void trencoder_write_reg(TraceEncoder *te, uint32_t reg_addr,
+                                uint32_t val)
+{
+    hwaddr addr = te->dest_baseaddr + reg_addr;
+
+    cpu_physical_memory_write(addr, &val, sizeof(uint32_t));
+}
+
+static hwaddr trencoder_read_ramsink_writep(TraceEncoder *te)
+{
+    hwaddr ret = trencoder_read_reg(te, A_TR_RAM_WP_HIGH);
+    ret <<= 32;
+    ret += trencoder_read_reg(te, A_TR_RAM_WP_LOW);
+
+    return ret;
+}
+
+static hwaddr trencoder_read_ramsink_ramlimit(TraceEncoder *te)
+{
+    hwaddr ret = trencoder_read_reg(te, A_TR_RAM_LIMIT_HIGH);
+    ret <<= 32;
+    ret += trencoder_read_reg(te, A_TR_RAM_LIMIT_LOW);
+
+    return ret;
+}
+
 static uint64_t trencoder_te_ctrl_set_hardwire_vals(uint64_t input)
 {
     input = FIELD_DP32(input, TR_TE_CONTROL, INST_MODE, 0x6);
@@ -171,6 +234,9 @@ static void trencoder_te_ctrl_postw(RegisterInfo *reg, uint64_t val)
     if (!te->trace_running && trTeInstTracing) {
         /* Starting trace. Ask the CPU for the first trace insn */
         te->trace_next_insn = true;
+
+        te->ramsink_ramstart = trencoder_read_ramsink_writep(te);
+        te->ramsink_ramlimit = trencoder_read_ramsink_ramlimit(te);
     }
 
     te->trace_running = trTeInstTracing ? true : false;
@@ -274,12 +340,67 @@ static void trencoder_realize(DeviceState *dev, Error **errp)
     }
 }
 
+static void trencoder_update_ramsink_writep(TraceEncoder *te,
+                                            hwaddr wp_val,
+                                            bool wrapped)
+{
+    uint32_t wp_low = trencoder_read_reg(te, A_TR_RAM_WP_LOW);
+
+    wp_low = FIELD_DP32(wp_low, TR_RAM_WP_LOW, ADDR,
+                        extract64(wp_val, 2, 30));
+
+    if (wrapped) {
+        wp_low = FIELD_DP32(wp_low, TR_RAM_WP_LOW, WRAP, 1);
+    }
+
+    trencoder_write_reg(te, A_TR_RAM_WP_LOW, wp_low);
+    trencoder_write_reg(te, A_TR_RAM_WP_HIGH, extract64(wp_val, 32, 32));
+}
+
+static void trencoder_send_message_smem(TraceEncoder *trencoder,
+                                        uint8_t *msg, uint8_t msg_size)
+{
+    hwaddr dest = trencoder_read_ramsink_writep(trencoder);
+    bool wrapped = false;
+
+    msg_size = QEMU_ALIGN_UP(msg_size, 4);
+
+    /* clear trRamWrap before writing to SMEM */
+    dest = FIELD_DP64(dest, TR_RAM_WP_LOW, WRAP, 0);
+
+    /*
+     * Fill with null bytes if we can't fit the packet in
+     * ramlimit, set wrap and write the packet in ramstart.
+     */
+    if (dest + msg_size > trencoder->ramsink_ramlimit) {
+        g_autofree uint8_t *null_packet = NULL;
+        uint8_t null_size = trencoder->ramsink_ramlimit - dest;
+
+        null_packet = g_malloc0(null_size);
+        cpu_physical_memory_write(dest, null_packet, null_size);
+
+        dest = trencoder->ramsink_ramstart;
+        wrapped = true;
+    }
+
+    cpu_physical_memory_write(dest, msg, msg_size);
+    dest += msg_size;
+
+    trencoder_update_ramsink_writep(trencoder, dest, wrapped);
+}
+
 void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
 {
     TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+    TracePrivLevel priv = trencoder_get_curr_priv_level(trencoder);
+    g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
+    uint8_t msg_size;
 
     trencoder->first_pc = pc;
     trace_trencoder_first_trace_insn(pc);
+    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv);
+
+    trencoder_send_message_smem(trencoder, msg, msg_size);
 }
 
 static const Property trencoder_props[] = {
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 09/17] test/qtest: add riscv-trace-test.c
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (7 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 08/17] hw/riscv/trace-encoder: write e-trace packets to RAM sink Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 10/17] hw/riscv/rv-trace-messages.c: add encoded trap message Daniel Henrique Barboza
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Add a simple smoke test for the trace encoder/trace ram sink integration
with the RISC-V 'virt' machine.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/virt.c                |   2 +-
 tests/qtest/meson.build        |   2 +-
 tests/qtest/riscv-trace-test.c | 120 +++++++++++++++++++++++++++++++++
 3 files changed, 122 insertions(+), 2 deletions(-)
 create mode 100644 tests/qtest/riscv-trace-test.c

diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 30e89a6c5a..fe49b1eda2 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1711,7 +1711,7 @@ static void virt_machine_init(MachineState *machine)
                                 hart_count, &error_abort);
         sysbus_realize(SYS_BUS_DEVICE(&s->soc[i]), &error_fatal);
 
-        if (tcg_enabled()) {
+        if (tcg_enabled()  || qtest_enabled()) {
             virt_init_socket_trace_hw(s, i);
         }
 
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 669d07c06b..07663c4836 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -281,7 +281,7 @@ qtests_s390x = \
 qtests_riscv32 = \
   (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : [])
 
-qtests_riscv64 = ['riscv-csr-test'] + \
+qtests_riscv64 = ['riscv-csr-test', 'riscv-trace-test'] + \
   (unpack_edk2_blobs ? ['bios-tables-test'] : [])
 
 qos_test_ss = ss.source_set()
diff --git a/tests/qtest/riscv-trace-test.c b/tests/qtest/riscv-trace-test.c
new file mode 100644
index 0000000000..e442f69286
--- /dev/null
+++ b/tests/qtest/riscv-trace-test.c
@@ -0,0 +1,120 @@
+/*
+ * Testcase for RISC-V Trace framework
+ *
+ * Copyright (C) 2025 Ventana Micro Systems Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "hw/registerfields.h"
+
+/* taken from virt machine memmap */
+#define TE_BASE   0x3020000
+#define TRAM_BASE 0x6000000
+
+REG32(TR_TE_CONTROL, 0x0)
+    FIELD(TR_TE_CONTROL, ACTIVE, 0, 1)
+    FIELD(TR_TE_CONTROL, ENABLE, 1, 1)
+    FIELD(TR_TE_CONTROL, INST_TRACING, 2, 1)
+
+REG32(TR_RAM_START_LOW, 0x010)
+    FIELD(TR_RAM_START_LOW, ADDR, 0, 32)
+REG32(TR_RAM_START_HIGH, 0x014)
+    FIELD(TR_RAM_START_HIGH, ADDR, 0, 32)
+
+REG32(TR_RAM_LIMIT_LOW, 0x018)
+    FIELD(TR_RAM_LIMIT_LOW, ADDR, 0, 32)
+REG32(TR_RAM_LIMIT_HIGH, 0x01C)
+    FIELD(TR_RAM_LIMIT_HIGH, ADDR, 0, 32)
+
+REG32(TR_RAM_WP_LOW, 0x020)
+    FIELD(TR_RAM_WP_LOW, WRAP, 0, 1)
+    FIELD(TR_RAM_WP_LOW, ADDR, 0, 32)
+REG32(TR_RAM_WP_HIGH, 0x024)
+    FIELD(TR_RAM_WP_HIGH, ADDR, 0, 32)
+
+static uint32_t test_read_te_control(QTestState *qts)
+{
+    return qtest_readl(qts, TE_BASE + A_TR_TE_CONTROL);
+}
+
+static void test_write_te_control(QTestState *qts, uint32_t val)
+{
+    qtest_writel(qts, TE_BASE + A_TR_TE_CONTROL, val);
+}
+
+static uint64_t test_read_tram_ramstart(QTestState *qts)
+{
+    uint64_t reg = qtest_readl(qts, TRAM_BASE + A_TR_RAM_START_HIGH);
+
+    reg <<= 32;
+    reg += qtest_readl(qts, TRAM_BASE + A_TR_RAM_START_LOW);
+    return reg;
+}
+
+static uint64_t test_read_tram_writep(QTestState *qts)
+{
+    uint64_t reg = qtest_readl(qts, TRAM_BASE + A_TR_RAM_WP_HIGH);
+
+    reg <<= 32;
+    reg += qtest_readl(qts, TRAM_BASE + A_TR_RAM_WP_LOW);
+    return reg;
+}
+
+static void test_trace_simple(void)
+{
+    QTestState *qts = qtest_init("-machine virt -accel tcg");
+    double timeout_sec = 0.5;
+    uint64_t reg_tram_start, reg_tram_writep;
+    uint32_t reg;
+
+    reg = test_read_te_control(qts);
+    reg = FIELD_DP32(reg, TR_TE_CONTROL, ACTIVE, 1);
+    test_write_te_control(qts, reg);
+    reg = test_read_te_control(qts);
+    g_assert(1 == FIELD_EX32(reg, TR_TE_CONTROL, ACTIVE));
+
+    reg = FIELD_DP32(reg, TR_TE_CONTROL, ENABLE, 1);
+    test_write_te_control(qts, reg);
+    reg = test_read_te_control(qts);
+    g_assert(1 == FIELD_EX32(reg, TR_TE_CONTROL, ENABLE));
+
+    /*
+     * Verify if RAM Sink write pointer is equal to
+     * ramstart before start tracing.
+     */
+    reg_tram_start = test_read_tram_ramstart(qts);
+    g_assert(reg_tram_start > 0);
+    reg_tram_writep = test_read_tram_writep(qts);
+    g_assert(reg_tram_writep == reg_tram_start);
+
+    reg = FIELD_DP32(reg, TR_TE_CONTROL, INST_TRACING, 1);
+    test_write_te_control(qts, reg);
+    reg = test_read_te_control(qts);
+    g_assert(1 == FIELD_EX32(reg, TR_TE_CONTROL, INST_TRACING));
+
+    g_test_timer_start();
+    for (;;) {
+        reg_tram_writep = test_read_tram_writep(qts);
+        if (reg_tram_writep > reg_tram_start) {
+            break;
+        }
+
+        g_assert(g_test_timer_elapsed() <= timeout_sec);
+    }
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/riscv-trace-test/test-trace-simple",
+                   test_trace_simple);
+    return g_test_run();
+}
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 10/17] hw/riscv/rv-trace-messages.c: add encoded trap message
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (8 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 09/17] test/qtest: add riscv-trace-test.c Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 11/17] hw/riscv, target/riscv: send trace trap messages Daniel Henrique Barboza
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

The trap message consists of a Format 3 Subformat 1 e-trace message.

According to the Efficient Trace for RISC-V spec, section "Format 3
thaddr, address and privilege fields", the 'thaddr' will be zero if:

- we can't infer the EPC after an uninferable PC discontinuity (like traps).
  This doesn't happen in our current TCG backend - we'll always know the
  trap EPC in riscv_cpu_do_interrupt();

- a second interrupt/exception happens while the handler of the first
  trap hasn't exited. This also doesn't happen in TCG given that we do
  not emulate a multi-insn pipeline model, i.e. we'll only retire one
  insns at a time.

This means that we'll always send trap packets with 'thaddr' set to 1,
thus we're hardcoding it in the message helper.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/rv-trace-messages.c | 94 ++++++++++++++++++++++++++++++++++++
 hw/riscv/rv-trace-messages.h |  5 ++
 2 files changed, 99 insertions(+)

diff --git a/hw/riscv/rv-trace-messages.c b/hw/riscv/rv-trace-messages.c
index 215135dd47..3e9466633d 100644
--- a/hw/riscv/rv-trace-messages.c
+++ b/hw/riscv/rv-trace-messages.c
@@ -35,6 +35,24 @@ typedef struct RVTraceSyncPayload {
 } RVTraceSyncPayload;
 #define SYNC_PAYLOAD_SIZE_64BITS 9
 
+/*
+ * Format 3 subformat 1 without 'time' and 'context' fields
+ */
+typedef struct RVTraceTrapPayload {
+    uint8_t format:2;
+    uint8_t subformat:2;
+    uint8_t branch:1;
+    uint8_t privilege:3;
+    uint8_t ecause:6;
+    uint8_t interrupt:1;
+    uint8_t thaddr:1;
+    uint32_t addressLow;
+    uint32_t addressHigh;
+    uint32_t tvalLow;
+    uint32_t tvalHigh;
+} RVTraceTrapPayload;
+#define TRAP_PAYLOAD_SIZE_64BITS 18
+
 static void rv_etrace_write_bits(uint8_t *bytes, uint32_t bit_pos,
                                  uint32_t num_bits, uint32_t val)
 {
@@ -92,3 +110,79 @@ size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
 
     return HEADER_SIZE + SYNC_PAYLOAD_SIZE_64BITS;
 }
+
+/*
+ * Note: this function assumes thaddr = 1.
+ */
+size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
+                                      TracePrivLevel priv_level,
+                                      uint8_t ecause,
+                                      bool is_interrupt,
+                                      uint64_t tval)
+{
+    RVTraceTrapPayload payload = {.format = 0b11,
+                                  .subformat = 0b01,
+                                  .branch = 1,
+                                  .privilege = priv_level,
+                                  .ecause = ecause};
+    RVTraceMessageHeader header = {.flow = 0, .extend = 0,
+                                   .length = TRAP_PAYLOAD_SIZE_64BITS};
+    uint8_t bit_pos;
+
+    payload.addressLow = extract64(trap_addr, 0, 32);
+    payload.addressHigh = extract64(trap_addr, 32, 32);
+
+    /*
+     * When interrupt = 1 'tval' is ommited. Take 8 bytes
+     * from the final size.
+     */
+    if (is_interrupt) {
+        header.length = TRAP_PAYLOAD_SIZE_64BITS - 8;
+    }
+
+    rv_etrace_write_header(buf, header);
+    bit_pos = 8;
+
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.format);
+    bit_pos += 2;
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.subformat);
+    bit_pos += 2;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.branch);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 3, payload.privilege);
+    bit_pos += 3;
+
+    rv_etrace_write_bits(buf, bit_pos, 6, payload.ecause);
+    bit_pos += 6;
+
+    if (is_interrupt) {
+        rv_etrace_write_bits(buf, bit_pos, 1, 1);
+    } else {
+        rv_etrace_write_bits(buf, bit_pos, 1, 0);
+    }
+    bit_pos += 1;
+
+    /* thaddr is hardcoded to 1 for now */
+    rv_etrace_write_bits(buf, bit_pos, 1, 1);
+    bit_pos += 1;
+
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressLow);
+    bit_pos += 32;
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressHigh);
+    bit_pos += 32;
+
+    /* Skip trap_addr if is_interrupt  */
+    if (is_interrupt) {
+        goto out;
+    }
+
+    payload.tvalLow = extract64(trap_addr, 0, 32);
+    payload.tvalHigh = extract64(trap_addr, 32, 32);
+
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.tvalLow);
+    bit_pos += 32;
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.tvalHigh);
+
+out:
+    return HEADER_SIZE + header.length;
+}
diff --git a/hw/riscv/rv-trace-messages.h b/hw/riscv/rv-trace-messages.h
index aeafea8849..f3e38b571f 100644
--- a/hw/riscv/rv-trace-messages.h
+++ b/hw/riscv/rv-trace-messages.h
@@ -21,5 +21,10 @@ typedef enum {
 
 size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
                                       TracePrivLevel priv_level);
+size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
+                                      TracePrivLevel priv_level,
+                                      uint8_t ecause,
+                                      bool is_interrupt,
+                                      uint64_t tval);
 
 #endif
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 11/17] hw/riscv, target/riscv: send trace trap messages
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (9 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 10/17] hw/riscv/rv-trace-messages.c: add encoded trap message Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 12/17] target/riscv, hw/riscv: send trace ppccd packets Daniel Henrique Barboza
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Use the rv_etrace_gen_encoded_trap_msg() helper we added in the previous
patch to encode trap packets to be written in the RAM sink SMEM.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c  | 17 +++++++++++++++++
 hw/riscv/trace-encoder.h  |  4 ++++
 target/riscv/cpu_helper.c | 13 +++++++++++++
 3 files changed, 34 insertions(+)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 16c9475d46..0750bd22b5 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -403,6 +403,23 @@ void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
 
+void trencoder_trace_trap_insn(Object *trencoder_obj,
+                               uint64_t pc, uint32_t ecause,
+                               bool is_interrupt,
+                               uint64_t tval)
+{
+    TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+    TracePrivLevel priv = trencoder_get_curr_priv_level(trencoder);
+    g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
+    uint8_t msg_size;
+
+    msg_size = rv_etrace_gen_encoded_trap_msg(msg, pc, priv,
+                                              ecause, is_interrupt,
+                                              tval);
+
+    trencoder_send_message_smem(trencoder, msg, msg_size);
+}
+
 static const Property trencoder_props[] = {
     /*
      * We need a link to the associated CPU to
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index cf3177aefe..4898026f2b 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -46,5 +46,9 @@ struct TraceEncoder {
 OBJECT_DECLARE_SIMPLE_TYPE(TraceEncoder, TRACE_ENCODER)
 
 void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc);
+void trencoder_trace_trap_insn(Object *trencoder_obj,
+                               uint64_t pc, uint32_t ecause,
+                               bool is_interrupt,
+                               uint64_t tval);
 
 #endif
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index c4fb68b5de..f2990cf7c4 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -38,6 +38,10 @@
 #include "pmp.h"
 #include "qemu/plugin.h"
 
+#ifndef CONFIG_USER_ONLY
+#include "hw/riscv/trace-encoder.h"
+#endif
+
 int riscv_env_mmu_index(CPURISCVState *env, bool ifetch)
 {
 #ifdef CONFIG_USER_ONLY
@@ -2285,6 +2289,15 @@ void riscv_cpu_do_interrupt(CPUState *cs)
                   __func__, env->mhartid, async, cause, env->pc, tval,
                   riscv_cpu_get_trap_name(cause, async));
 
+    if (cpu->trencoder) {
+        TraceEncoder *te = TRACE_ENCODER(cpu->trencoder);
+
+        if (te->trace_running) {
+            trencoder_trace_trap_insn(cpu->trencoder, env->pc,
+                                      cause, async, tval);
+        }
+    }
+
     mode = env->priv <= PRV_S && cause < 64 &&
         (((deleg >> cause) & 1) || s_injected || vs_injected) ? PRV_S : PRV_M;
 
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 12/17] target/riscv, hw/riscv: send trace ppccd packets
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (10 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 11/17] hw/riscv, target/riscv: send trace trap messages Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 13/17] hw/riscv/trace: add format2 msg helper Daniel Henrique Barboza
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

The ppccd (privilege change) trace packets consists of sync packets like we
already sent during trace start and resyncs.

The privilege change is being fetched via riscv_cpu_set_mode().

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c  | 18 ++++++++++++++++++
 hw/riscv/trace-encoder.h  |  1 +
 target/riscv/cpu_helper.c | 13 ++++++++++++-
 3 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 0750bd22b5..9a4530bbea 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -389,6 +389,19 @@ static void trencoder_send_message_smem(TraceEncoder *trencoder,
     trencoder_update_ramsink_writep(trencoder, dest, wrapped);
 }
 
+static void trencoder_send_sync_msg(Object *trencoder_obj, uint64_t pc)
+{
+    TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+    TracePrivLevel priv = trencoder_get_curr_priv_level(trencoder);
+    g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
+    uint8_t msg_size;
+
+    trencoder->first_pc = pc;
+    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv);
+
+    trencoder_send_message_smem(trencoder, msg, msg_size);
+}
+
 void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
 {
     TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
@@ -420,6 +433,11 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
 
+void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc)
+{
+    trencoder_send_sync_msg(trencoder_obj, pc);
+}
+
 static const Property trencoder_props[] = {
     /*
      * We need a link to the associated CPU to
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index 4898026f2b..2bf07c01f6 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -50,5 +50,6 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
                                uint64_t pc, uint32_t ecause,
                                bool is_interrupt,
                                uint64_t tval);
+void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc);
 
 #endif
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index f2990cf7c4..665aad6dea 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -1034,6 +1034,8 @@ void riscv_ctr_add_entry(CPURISCVState *env, target_long src, target_long dst,
 
 void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en)
 {
+    CPUState *cs = env_cpu(env);
+
     g_assert(newpriv <= PRV_M && newpriv != PRV_RESERVED);
 
     if (newpriv != env->priv || env->virt_enabled != virt_en) {
@@ -1041,6 +1043,15 @@ void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en)
             riscv_itrigger_update_priv(env);
         }
 
+        if (newpriv != env->priv) {
+            RISCVCPU *cpu = RISCV_CPU(cs);
+
+            if (cpu->trencoder &&
+                TRACE_ENCODER(cpu->trencoder)->trace_running) {
+                trencoder_trace_ppccd(cpu->trencoder, env->pc);
+            }
+        }
+
         riscv_pmu_update_fixed_ctrs(env, newpriv, virt_en);
     }
 
@@ -1061,7 +1072,7 @@ void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en)
     if (riscv_has_ext(env, RVH)) {
         /* Flush the TLB on all virt mode changes. */
         if (env->virt_enabled != virt_en) {
-            tlb_flush(env_cpu(env));
+            tlb_flush(cs);
         }
 
         env->virt_enabled = virt_en;
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 13/17] hw/riscv/trace: add format2 msg helper
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (11 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 12/17] target/riscv, hw/riscv: send trace ppccd packets Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 14/17] hw/riscv, target/riscv: send resync updiscon trace packets Daniel Henrique Barboza
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Before handling updiscon (uninferable PC discontinuity) cases add the
encoded message we're going to use, format 2. Format 1 will be added
when we add the branch map.

There are design decisions worth documenting in this patch. For irreport,
the e-trace-encap spec [1] in section 7.6. "Format 2 packets",  says that
we should make this bit !updiscon if we're either:

"following a return because its address differs from the predicted return
address at the top of the implicit_return return address stack, or the
last retired before an exception, interrupt, privilege change or resync
because it is necessary to report the current address stack depth or
nested call count."

We do not implement any form of call/return prediction in the encoder,
and TCG will always retire a single insn per cycle. This means that
we'll never set irreport, i.e. irreport will always be equal to
updiscon. If this interpretation turns out to be wrong we'll revisit
this helper.

Likewise, irdepth is also not implemented since we'll always return a
package where irreport == updiscon. The size of the field is arbitrarily
set to '3' to avoid adding padding in the end of the msg to complete an
extra byte.

[1] https://github.com/riscv-non-isa/e-trace-encap/releases/tag/v1.0

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/rv-trace-messages.c | 59 ++++++++++++++++++++++++++++++++++++
 hw/riscv/rv-trace-messages.h |  2 ++
 2 files changed, 61 insertions(+)

diff --git a/hw/riscv/rv-trace-messages.c b/hw/riscv/rv-trace-messages.c
index 3e9466633d..3fa70ba81c 100644
--- a/hw/riscv/rv-trace-messages.c
+++ b/hw/riscv/rv-trace-messages.c
@@ -53,6 +53,17 @@ typedef struct RVTraceTrapPayload {
 } RVTraceTrapPayload;
 #define TRAP_PAYLOAD_SIZE_64BITS 18
 
+typedef struct RVTraceFormat2Payload {
+    uint8_t format:2;
+    uint32_t addressLow;
+    uint32_t addressHigh;
+    uint8_t notify:1;
+    uint8_t updiscon:1;
+    uint8_t irreport:1;
+    uint8_t irdepth:3;
+} RVTraceFormat2Payload;
+#define FORMAT2_PAYLOAD_SIZE_64BITS 9
+
 static void rv_etrace_write_bits(uint8_t *bytes, uint32_t bit_pos,
                                  uint32_t num_bits, uint32_t val)
 {
@@ -186,3 +197,51 @@ size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
 out:
     return HEADER_SIZE + header.length;
 }
+
+/*
+ * Note: irreport and irdepth is always == updiscon.
+ *
+ * return_stack_size_p + call_counter_size_p is hardcoded
+ * to 3 since we don't implement neither ATM.
+ */
+size_t rv_etrace_gen_encoded_format2_msg(uint8_t *buf, uint64_t addr,
+                                         bool notify, bool updiscon)
+{
+    RVTraceFormat2Payload payload = {.format = 0b11,
+                                     .notify = notify,
+                                     .updiscon = updiscon};
+    RVTraceMessageHeader header = {.flow = 0, .extend = 0,
+                                   .length = FORMAT2_PAYLOAD_SIZE_64BITS};
+    uint8_t bit_pos;
+
+    payload.addressLow = extract64(addr, 0, 32);
+    payload.addressHigh = extract64(addr, 32, 32);
+
+    payload.irreport = updiscon;
+    if (updiscon) {
+        payload.irdepth = 0b111;
+    } else {
+        payload.irdepth = 0;
+    }
+
+    rv_etrace_write_header(buf, header);
+    bit_pos = 8;
+
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.format);
+    bit_pos += 2;
+
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressLow);
+    bit_pos += 32;
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressHigh);
+    bit_pos += 32;
+
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.notify);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.updiscon);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.irreport);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 3, payload.irdepth);
+
+    return HEADER_SIZE + header.length;
+}
diff --git a/hw/riscv/rv-trace-messages.h b/hw/riscv/rv-trace-messages.h
index f3e38b571f..50a4a0ef19 100644
--- a/hw/riscv/rv-trace-messages.h
+++ b/hw/riscv/rv-trace-messages.h
@@ -26,5 +26,7 @@ size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
                                       uint8_t ecause,
                                       bool is_interrupt,
                                       uint64_t tval);
+size_t rv_etrace_gen_encoded_format2_msg(uint8_t *buf, uint64_t addr,
+                                         bool notify, bool updiscon);
 
 #endif
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 14/17] hw/riscv, target/riscv: send resync updiscon trace packets
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (12 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 13/17] hw/riscv/trace: add format2 msg helper Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 15/17] hw/riscv/rv-trace-messages: add format 1 msgs with branch info Daniel Henrique Barboza
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Send updiscon packets based on the constraints already discussed in the
previous patch:

- We do not implement any form of call/return prediction in the encoder,
  and TCG will always retire a single insn per cycle, e.g. irreport will
  always be equal to updiscon;

- irdepth is not implemented since we'll always return a  package where
  irreport == updiscon.

Note that we're sending an updiscon packet if the 'updiscon_pending'
flag is set when we're about the send a resync or a trap packet. The TCG
helper in this case is just setting the trace encoder flags instead of
actually triggering a RAM sink SMEM write.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c                      | 37 +++++++++++++++++++
 hw/riscv/trace-encoder.h                      |  3 ++
 target/riscv/helper.h                         |  1 +
 .../riscv/insn_trans/trans_privileged.c.inc   | 11 ++++++
 target/riscv/insn_trans/trans_rvi.c.inc       |  2 +
 target/riscv/trace_helper.c                   | 14 +++++++
 target/riscv/translate.c                      |  9 +++++
 7 files changed, 77 insertions(+)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 9a4530bbea..5572483d26 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -402,6 +402,22 @@ static void trencoder_send_sync_msg(Object *trencoder_obj, uint64_t pc)
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
 
+static void trencoder_send_updiscon(TraceEncoder *trencoder, uint64_t pc)
+{
+    g_autofree uint8_t *format2_msg = g_malloc0(TRACE_MSG_MAX_SIZE);
+    uint8_t addr_msb = extract64(pc, 31, 1);
+    bool notify = addr_msb;
+    bool updiscon = !notify;
+    uint8_t msg_size;
+
+    msg_size = rv_etrace_gen_encoded_format2_msg(format2_msg, pc,
+                                                 notify,
+                                                 updiscon);
+    trencoder_send_message_smem(trencoder, format2_msg, msg_size);
+
+    trencoder->updiscon_pending = false;
+}
+
 void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
 {
     TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
@@ -409,6 +425,10 @@ void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
     g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
     uint8_t msg_size;
 
+    if (trencoder->updiscon_pending) {
+        trencoder_send_updiscon(trencoder, pc);
+    }
+
     trencoder->first_pc = pc;
     trace_trencoder_first_trace_insn(pc);
     msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv);
@@ -426,6 +446,10 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
     g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
     uint8_t msg_size;
 
+    if (trencoder->updiscon_pending) {
+        trencoder_send_updiscon(trencoder, pc);
+    }
+
     msg_size = rv_etrace_gen_encoded_trap_msg(msg, pc, priv,
                                               ecause, is_interrupt,
                                               tval);
@@ -435,9 +459,22 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
 
 void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc)
 {
+    TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+
+    if (trencoder->updiscon_pending) {
+        trencoder_send_updiscon(trencoder, pc);
+    }
+
     trencoder_send_sync_msg(trencoder_obj, pc);
 }
 
+void trencoder_report_updiscon(Object *trencoder_obj)
+{
+    TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
+
+    trencoder->updiscon_pending = true;
+}
+
 static const Property trencoder_props[] = {
     /*
      * We need a link to the associated CPU to
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index 2bf07c01f6..0c44092ccb 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -36,6 +36,8 @@ struct TraceEncoder {
     uint32_t regs[TRACE_R_MAX];
     RegisterInfo regs_info[TRACE_R_MAX];
 
+    bool updiscon_pending;
+
     bool enabled;
     bool trace_running;
     bool trace_next_insn;
@@ -51,5 +53,6 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
                                bool is_interrupt,
                                uint64_t tval);
 void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc);
+void trencoder_report_updiscon(Object *trencoder_obj);
 
 #endif
diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index e80320ad16..f27ff319e9 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -131,6 +131,7 @@ DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl)
 
 /* Trace helpers (should be put inside ifdef) */
 DEF_HELPER_2(trace_insn, void, env, i64)
+DEF_HELPER_1(trace_updiscon, void, env)
 
 #ifndef CONFIG_USER_ONLY
 DEF_HELPER_1(sret, tl, env)
diff --git a/target/riscv/insn_trans/trans_privileged.c.inc b/target/riscv/insn_trans/trans_privileged.c.inc
index 8a62b4cfcd..28089539d5 100644
--- a/target/riscv/insn_trans/trans_privileged.c.inc
+++ b/target/riscv/insn_trans/trans_privileged.c.inc
@@ -26,6 +26,8 @@
 
 static bool trans_ecall(DisasContext *ctx, arg_ecall *a)
 {
+    gen_trace_updiscon();
+
     /* always generates U-level ECALL, fixed in do_interrupt handler */
     generate_exception(ctx, RISCV_EXCP_U_ECALL);
     return true;
@@ -40,6 +42,8 @@ static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a)
     uint32_t ebreak = 0;
     uint32_t post   = 0;
 
+    gen_trace_updiscon();
+
     /*
      * The RISC-V semihosting spec specifies the following
      * three-instruction sequence to flag a semihosting call:
@@ -95,6 +99,8 @@ static bool trans_sret(DisasContext *ctx, arg_sret *a)
 {
 #ifndef CONFIG_USER_ONLY
     if (has_ext(ctx, RVS)) {
+        gen_trace_updiscon();
+
         decode_save_opc(ctx, 0);
         translator_io_start(&ctx->base);
         gen_update_pc(ctx, 0);
@@ -113,6 +119,8 @@ static bool trans_sret(DisasContext *ctx, arg_sret *a)
 static bool trans_mret(DisasContext *ctx, arg_mret *a)
 {
 #ifndef CONFIG_USER_ONLY
+    gen_trace_updiscon();
+
     decode_save_opc(ctx, 0);
     translator_io_start(&ctx->base);
     gen_update_pc(ctx, 0);
@@ -129,6 +137,9 @@ static bool trans_mnret(DisasContext *ctx, arg_mnret *a)
 {
 #ifndef CONFIG_USER_ONLY
     REQUIRE_SMRNMI(ctx);
+
+    gen_trace_updiscon();
+
     decode_save_opc(ctx, 0);
     gen_helper_mnret(cpu_pc, tcg_env);
     tcg_gen_exit_tb(NULL, 0); /* no chaining */
diff --git a/target/riscv/insn_trans/trans_rvi.c.inc b/target/riscv/insn_trans/trans_rvi.c.inc
index 54b9b4f241..ac00cbc802 100644
--- a/target/riscv/insn_trans/trans_rvi.c.inc
+++ b/target/riscv/insn_trans/trans_rvi.c.inc
@@ -183,6 +183,8 @@ static bool trans_jalr(DisasContext *ctx, arg_jalr *a)
         }
     }
 
+    gen_trace_updiscon();
+
     lookup_and_goto_ptr(ctx);
 
     if (misaligned) {
diff --git a/target/riscv/trace_helper.c b/target/riscv/trace_helper.c
index ed84e6f79a..4b2b645f04 100644
--- a/target/riscv/trace_helper.c
+++ b/target/riscv/trace_helper.c
@@ -28,9 +28,23 @@ void helper_trace_insn(CPURISCVState *env, uint64_t pc)
         te->trace_next_insn = false;
     }
 }
+
+void helper_trace_updiscon(CPURISCVState *env)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    TraceEncoder *te = TRACE_ENCODER(cpu->trencoder);
+
+    te->updiscon_pending = true;
+    te->trace_next_insn = true;
+}
 #else /* #ifndef CONFIG_USER_ONLY */
 void helper_trace_insn(CPURISCVState *env, uint64_t pc)
 {
     return;
 }
+
+void helper_trace_updiscon(CPURISCVState *env)
+{
+    return;
+}
 #endif /* #ifndef CONFIG_USER_ONLY*/
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index ff288051e3..26c7678cb9 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -617,6 +617,15 @@ static void gen_ctr_jal(DisasContext *ctx, int rd, target_ulong imm)
 }
 #endif
 
+static void gen_trace_updiscon(void)
+{
+    TCGLabel *skip = gen_new_label();
+
+    tcg_gen_brcondi_tl(TCG_COND_EQ, cpu_trace_running, 0, skip);
+    gen_helper_trace_updiscon(tcg_env);
+    gen_set_label(skip);
+}
+
 static void gen_jal(DisasContext *ctx, int rd, target_ulong imm)
 {
     TCGv succ_pc = dest_gpr(ctx, rd);
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 15/17] hw/riscv/rv-trace-messages: add format 1 msgs with branch info
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (13 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 14/17] hw/riscv, target/riscv: send resync updiscon trace packets Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 16/17] hw/riscv/trace-encoder: send branches info Daniel Henrique Barboza
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Add encoded message helpers to send branch information trace packets.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/rv-trace-messages.c | 126 +++++++++++++++++++++++++++++++++++
 hw/riscv/rv-trace-messages.h |   7 ++
 2 files changed, 133 insertions(+)

diff --git a/hw/riscv/rv-trace-messages.c b/hw/riscv/rv-trace-messages.c
index 3fa70ba81c..a93b0adfc6 100644
--- a/hw/riscv/rv-trace-messages.c
+++ b/hw/riscv/rv-trace-messages.c
@@ -64,6 +64,31 @@ typedef struct RVTraceFormat2Payload {
 } RVTraceFormat2Payload;
 #define FORMAT2_PAYLOAD_SIZE_64BITS 9
 
+typedef struct RVTraceFormat1BasePayload {
+    uint8_t format:2;
+    uint8_t branches:5;
+    uint32_t branch_map:31;
+} RVTraceFormat1BasePayload;
+#define FORMAT1_BASE_PAYLOAD_SIZE_64BITS 5
+
+typedef struct RVTraceFormat1Payload {
+    uint8_t format:2;
+    uint8_t branches:5;
+    uint32_t branch_map;
+    uint32_t addressLow;
+    uint32_t addressHigh;
+    uint8_t notify:1;
+    uint8_t updiscon:1;
+    uint8_t irreport:1;
+    uint8_t irdepth:3;
+} RVTraceFormat1Payload;
+
+/*
+ * FORMAT2_PAYLOAD_SIZE_64BITS = 9 plus 5 bits of 'branches',
+ * plus minimal 3 bits of 'branch_map' = 10 bytes.
+ */
+#define FORMAT1_PAYLOAD_MIN_SIZE_64BITS 10
+
 static void rv_etrace_write_bits(uint8_t *bytes, uint32_t bit_pos,
                                  uint32_t num_bits, uint32_t val)
 {
@@ -245,3 +270,104 @@ size_t rv_etrace_gen_encoded_format2_msg(uint8_t *buf, uint64_t addr,
 
     return HEADER_SIZE + header.length;
 }
+
+size_t rv_etrace_gen_encoded_format1_noaddr(uint8_t *buf,
+                                            uint8_t branches,
+                                            uint32_t branch_map)
+{
+    RVTraceMessageHeader header = {.flow = 0, .extend = 0,
+        .length = FORMAT1_BASE_PAYLOAD_SIZE_64BITS};
+    RVTraceFormat1BasePayload payload = {.format = 0b01,
+        .branches = branches, .branch_map = branch_map};
+    uint8_t bit_pos;
+
+    rv_etrace_write_header(buf, header);
+    bit_pos = 8;
+
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.format);
+    bit_pos += 2;
+
+    rv_etrace_write_bits(buf, bit_pos, 5, payload.branches);
+    bit_pos += 5;
+
+    rv_etrace_write_bits(buf, bit_pos, 31, payload.branch_map);
+
+    return HEADER_SIZE + header.length;
+}
+
+/*
+ * Same reservations made in the format 2 helper:
+ *
+ * - irreport and irdepth is always == updiscon;
+ *
+ * - return_stack_size_p + call_counter_size_p is hardcoded
+ * to 3 since we don't implement neither ATM.
+ */
+size_t rv_etrace_gen_encoded_format1(uint8_t *buf,
+                                     uint8_t branches, uint32_t branch_map,
+                                     uint64_t addr,
+                                     bool notify, bool updiscon)
+{
+    RVTraceMessageHeader header = {.flow = 0, .extend = 0};
+    RVTraceFormat1Payload payload = {.format = 0b01,
+                                     .branches = branches,
+                                     .notify = notify,
+                                     .updiscon = updiscon};
+    uint8_t payload_size = FORMAT1_PAYLOAD_MIN_SIZE_64BITS;
+    uint8_t branch_map_size = 0;
+    uint8_t bit_pos;
+
+    g_assert(branches < 32);
+
+    if (branches <= 3) {
+        branch_map_size = 3;
+    } else if (branches <= 7) {
+        branch_map_size = 7;
+        payload_size++;
+    } else if (branches <= 15) {
+        branch_map_size = 15;
+        payload_size += 2;
+    } else {
+        branch_map_size = 31;
+        payload_size += 4;
+    }
+
+    header.length = payload_size;
+
+    rv_etrace_write_header(buf, header);
+    bit_pos = 8;
+
+    payload.addressLow = extract64(addr, 0, 32);
+    payload.addressHigh = extract64(addr, 32, 32);
+
+    payload.irreport = updiscon;
+    if (updiscon) {
+        payload.irdepth = 0b111;
+    } else {
+        payload.irdepth = 0;
+    }
+
+    rv_etrace_write_bits(buf, bit_pos, 2, payload.format);
+    bit_pos += 2;
+
+    rv_etrace_write_bits(buf, bit_pos, 5, payload.branches);
+    bit_pos += 5;
+
+    rv_etrace_write_bits(buf, bit_pos, branch_map_size, payload.branch_map);
+    bit_pos += branch_map_size;
+
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressLow);
+    bit_pos += 32;
+    rv_etrace_write_bits(buf, bit_pos, 32, payload.addressHigh);
+    bit_pos += 32;
+
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.notify);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.updiscon);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 1, payload.irreport);
+    bit_pos += 1;
+    rv_etrace_write_bits(buf, bit_pos, 3, payload.irdepth);
+
+    return HEADER_SIZE + header.length;
+}
diff --git a/hw/riscv/rv-trace-messages.h b/hw/riscv/rv-trace-messages.h
index 50a4a0ef19..2b371641a4 100644
--- a/hw/riscv/rv-trace-messages.h
+++ b/hw/riscv/rv-trace-messages.h
@@ -28,5 +28,12 @@ size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
                                       uint64_t tval);
 size_t rv_etrace_gen_encoded_format2_msg(uint8_t *buf, uint64_t addr,
                                          bool notify, bool updiscon);
+size_t rv_etrace_gen_encoded_format1_noaddr(uint8_t *buf,
+                                            uint8_t branches,
+                                            uint32_t branch_map);
+size_t rv_etrace_gen_encoded_format1(uint8_t *buf,
+                                     uint8_t branches, uint32_t branch_map,
+                                     uint64_t addr,
+                                     bool notify, bool updiscon);
 
 #endif
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 16/17] hw/riscv/trace-encoder: send branches info
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (14 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 15/17] hw/riscv/rv-trace-messages: add format 1 msgs with branch info Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-11 11:46 ` [PATCH v2 17/17] hw/riscv/trace: update branch bit in sync messages Daniel Henrique Barboza
  2025-11-20 12:14 ` [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Konstantin Semichastnov
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Branch info is reported via the TCG helpers, updating the trace encoder
internal branch map.

Branch packets are sent in two circunstances:

- when the branch map is full;

- when an updiscon packet is about to be sent and the branch map
  isn't empty.

The former will trigger a Format 1 no-addr packet, the latter a Format 1
with the updiscon address.

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/trace-encoder.c                | 52 ++++++++++++++++++++++++-
 hw/riscv/trace-encoder.h                |  4 ++
 target/riscv/helper.h                   |  1 +
 target/riscv/insn_trans/trans_rvi.c.inc | 13 +++++++
 target/riscv/trace_helper.c             | 12 ++++++
 5 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 5572483d26..5b8f773b11 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -29,6 +29,8 @@
  */
 #define TRACE_MSG_MAX_SIZE 32
 
+#define TRACE_MAX_BRANCHES 31
+
 static TracePrivLevel trencoder_get_curr_priv_level(TraceEncoder *te)
 {
     CPURISCVState *env = &te->cpu->env;
@@ -313,6 +315,9 @@ static void trencoder_reset(DeviceState *dev)
     te->trace_running = false;
     te->trace_next_insn = false;
     env->trace_running = false;
+
+    te->branch_map = 0;
+    te->branches = 0;
 }
 
 static void trencoder_realize(DeviceState *dev, Error **errp)
@@ -410,9 +415,20 @@ static void trencoder_send_updiscon(TraceEncoder *trencoder, uint64_t pc)
     bool updiscon = !notify;
     uint8_t msg_size;
 
-    msg_size = rv_etrace_gen_encoded_format2_msg(format2_msg, pc,
+    if (trencoder->branches > 0) {
+        msg_size = rv_etrace_gen_encoded_format1(format2_msg,
+                                                 trencoder->branches,
+                                                 trencoder->branch_map,
+                                                 pc,
                                                  notify,
                                                  updiscon);
+        trencoder->branches = 0;
+    } else {
+        msg_size = rv_etrace_gen_encoded_format2_msg(format2_msg, pc,
+                                                     notify,
+                                                     updiscon);
+    }
+
     trencoder_send_message_smem(trencoder, format2_msg, msg_size);
 
     trencoder->updiscon_pending = false;
@@ -457,6 +473,40 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
 
+static void trencoder_send_branch_map(Object *trencoder_obj)
+{
+    TraceEncoder *te = TRACE_ENCODER(trencoder_obj);
+    g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
+    uint8_t msg_size;
+
+    msg_size = rv_etrace_gen_encoded_format1_noaddr(msg,
+                                                    te->branches,
+                                                    te->branch_map);
+    trencoder_send_message_smem(te, msg, msg_size);
+}
+
+void trencoder_report_branch(Object *trencoder_obj, uint64_t pc, bool taken)
+{
+    TraceEncoder *te = TRACE_ENCODER(trencoder_obj);
+
+    /*
+     * Note: the e-trace spec determines the value '1' for a
+     * branch *not* taken. The helper API is using taken = 1
+     * to be more intuitive when reading TCG code.
+     */
+    if (!taken) {
+        te->branch_map = deposit32(te->branch_map, te->branches, 1, 1);
+    }
+
+    te->last_branch_pc = pc;
+    te->branches++;
+
+    if (te->branches == TRACE_MAX_BRANCHES) {
+        trencoder_send_branch_map(trencoder_obj);
+        te->branches = 0;
+    }
+}
+
 void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc)
 {
     TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
index 0c44092ccb..854f8a9ad6 100644
--- a/hw/riscv/trace-encoder.h
+++ b/hw/riscv/trace-encoder.h
@@ -28,6 +28,9 @@ struct TraceEncoder {
     uint32_t reg_mem_size;
 
     uint64_t first_pc;
+    uint64_t last_branch_pc;
+    uint32_t branch_map;
+    uint8_t branches;
 
     hwaddr baseaddr;
     hwaddr dest_baseaddr;
@@ -54,5 +57,6 @@ void trencoder_trace_trap_insn(Object *trencoder_obj,
                                uint64_t tval);
 void trencoder_trace_ppccd(Object *trencoder_obj, uint64_t pc);
 void trencoder_report_updiscon(Object *trencoder_obj);
+void trencoder_report_branch(Object *trencoder_obj, uint64_t pc, bool taken);
 
 #endif
diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index f27ff319e9..b1de064e17 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -132,6 +132,7 @@ DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl)
 /* Trace helpers (should be put inside ifdef) */
 DEF_HELPER_2(trace_insn, void, env, i64)
 DEF_HELPER_1(trace_updiscon, void, env)
+DEF_HELPER_3(trace_branch, void, env, tl, int)
 
 #ifndef CONFIG_USER_ONLY
 DEF_HELPER_1(sret, tl, env)
diff --git a/target/riscv/insn_trans/trans_rvi.c.inc b/target/riscv/insn_trans/trans_rvi.c.inc
index ac00cbc802..ee29adbdeb 100644
--- a/target/riscv/insn_trans/trans_rvi.c.inc
+++ b/target/riscv/insn_trans/trans_rvi.c.inc
@@ -268,6 +268,15 @@ static void gen_setcond_i128(TCGv rl, TCGv rh,
     tcg_gen_movi_tl(rh, 0);
 }
 
+static void gen_trace_branch(int taken)
+{
+    TCGLabel *skip = gen_new_label();
+
+    tcg_gen_brcondi_tl(TCG_COND_EQ, cpu_trace_running, 0, skip);
+    gen_helper_trace_branch(tcg_env, cpu_pc, tcg_constant_i32(taken));
+    gen_set_label(skip);
+}
+
 static bool gen_branch(DisasContext *ctx, arg_b *a, TCGCond cond)
 {
     TCGLabel *l = gen_new_label();
@@ -299,11 +308,15 @@ static bool gen_branch(DisasContext *ctx, arg_b *a, TCGCond cond)
     }
 #endif
 
+    gen_trace_branch(0);
+
     gen_goto_tb(ctx, 1, ctx->cur_insn_len);
     ctx->pc_save = orig_pc_save;
 
     gen_set_label(l); /* branch taken */
 
+    gen_trace_branch(1);
+
     if (!riscv_cpu_allow_16bit_insn(ctx->cfg_ptr,
                                     ctx->priv_ver,
                                     ctx->misa_ext) &&
diff --git a/target/riscv/trace_helper.c b/target/riscv/trace_helper.c
index 4b2b645f04..b48b89e0db 100644
--- a/target/riscv/trace_helper.c
+++ b/target/riscv/trace_helper.c
@@ -37,6 +37,13 @@ void helper_trace_updiscon(CPURISCVState *env)
     te->updiscon_pending = true;
     te->trace_next_insn = true;
 }
+
+void helper_trace_branch(CPURISCVState *env, target_ulong pc, int taken)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+
+    trencoder_report_branch(cpu->trencoder, pc, taken);
+}
 #else /* #ifndef CONFIG_USER_ONLY */
 void helper_trace_insn(CPURISCVState *env, uint64_t pc)
 {
@@ -47,4 +54,9 @@ void helper_trace_updiscon(CPURISCVState *env)
 {
     return;
 }
+
+void helper_trace_branch(CPURISCVState *env, target_ulong pc, int taken)
+{
+    return;
+}
 #endif /* #ifndef CONFIG_USER_ONLY*/
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 17/17] hw/riscv/trace: update branch bit in sync messages
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (15 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 16/17] hw/riscv/trace-encoder: send branches info Daniel Henrique Barboza
@ 2025-11-11 11:46 ` Daniel Henrique Barboza
  2025-11-20 12:14 ` [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Konstantin Semichastnov
  17 siblings, 0 replies; 21+ messages in thread
From: Daniel Henrique Barboza @ 2025-11-11 11:46 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	Daniel Henrique Barboza

Now that we have a working branch map update the branch bit in sync
messages by checking if the sync address is a branch address that was
taken (or not).

Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/rv-trace-messages.c |  6 +++---
 hw/riscv/rv-trace-messages.h |  3 ++-
 hw/riscv/trace-encoder.c     | 30 ++++++++++++++++++++++++++++--
 3 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/hw/riscv/rv-trace-messages.c b/hw/riscv/rv-trace-messages.c
index a93b0adfc6..a0a0cbb6ce 100644
--- a/hw/riscv/rv-trace-messages.c
+++ b/hw/riscv/rv-trace-messages.c
@@ -115,11 +115,12 @@ static void rv_etrace_write_header(uint8_t *buf, RVTraceMessageHeader header)
 }
 
 size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
-                                      TracePrivLevel priv_level)
+                                      TracePrivLevel priv_level,
+                                      bool pc_is_branch)
 {
     RVTraceSyncPayload payload = {.format = 0b11,
                                   .subformat = 0b00,
-                                  .branch = 1,
+                                  .branch = pc_is_branch,
                                   .privilege = priv_level};
     RVTraceMessageHeader header = {.flow = 0, .extend = 0,
                                    .length = SYNC_PAYLOAD_SIZE_64BITS};
@@ -158,7 +159,6 @@ size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
 {
     RVTraceTrapPayload payload = {.format = 0b11,
                                   .subformat = 0b01,
-                                  .branch = 1,
                                   .privilege = priv_level,
                                   .ecause = ecause};
     RVTraceMessageHeader header = {.flow = 0, .extend = 0,
diff --git a/hw/riscv/rv-trace-messages.h b/hw/riscv/rv-trace-messages.h
index 2b371641a4..fd4bb10860 100644
--- a/hw/riscv/rv-trace-messages.h
+++ b/hw/riscv/rv-trace-messages.h
@@ -20,7 +20,8 @@ typedef enum {
 } TracePrivLevel;
 
 size_t rv_etrace_gen_encoded_sync_msg(uint8_t *buf, uint64_t pc,
-                                      TracePrivLevel priv_level);
+                                      TracePrivLevel priv_level,
+                                      bool pc_is_branch);
 size_t rv_etrace_gen_encoded_trap_msg(uint8_t *buf, uint64_t trap_addr,
                                       TracePrivLevel priv_level,
                                       uint8_t ecause,
diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
index 5b8f773b11..39ed8c8d54 100644
--- a/hw/riscv/trace-encoder.c
+++ b/hw/riscv/trace-encoder.c
@@ -394,15 +394,38 @@ static void trencoder_send_message_smem(TraceEncoder *trencoder,
     trencoder_update_ramsink_writep(trencoder, dest, wrapped);
 }
 
+static bool trencoder_addr_is_branch_taken(TraceEncoder *te, uint64_t addr)
+{
+    uint8_t last_branch;
+
+    if (te->branches == 0) {
+        return false;
+    }
+
+    if (te->last_branch_pc == addr) {
+        last_branch = extract32(te->branches, te->branches - 1, 1);
+
+        /* 0: branch taken, 1: not taken*/
+        if (last_branch == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 static void trencoder_send_sync_msg(Object *trencoder_obj, uint64_t pc)
 {
     TraceEncoder *trencoder = TRACE_ENCODER(trencoder_obj);
     TracePrivLevel priv = trencoder_get_curr_priv_level(trencoder);
     g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
     uint8_t msg_size;
+    bool is_branch_taken;
 
     trencoder->first_pc = pc;
-    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv);
+    is_branch_taken = trencoder_addr_is_branch_taken(trencoder, pc);
+    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv,
+                                              is_branch_taken);
 
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
@@ -440,6 +463,7 @@ void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
     TracePrivLevel priv = trencoder_get_curr_priv_level(trencoder);
     g_autofree uint8_t *msg = g_malloc0(TRACE_MSG_MAX_SIZE);
     uint8_t msg_size;
+    bool is_branch_taken;
 
     if (trencoder->updiscon_pending) {
         trencoder_send_updiscon(trencoder, pc);
@@ -447,7 +471,9 @@ void trencoder_set_first_trace_insn(Object *trencoder_obj, uint64_t pc)
 
     trencoder->first_pc = pc;
     trace_trencoder_first_trace_insn(pc);
-    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv);
+    is_branch_taken = trencoder_addr_is_branch_taken(trencoder, pc);
+    msg_size = rv_etrace_gen_encoded_sync_msg(msg, pc, priv,
+                                              is_branch_taken);
 
     trencoder_send_message_smem(trencoder, msg, msg_size);
 }
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support
  2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
                   ` (16 preceding siblings ...)
  2025-11-11 11:46 ` [PATCH v2 17/17] hw/riscv/trace: update branch bit in sync messages Daniel Henrique Barboza
@ 2025-11-20 12:14 ` Konstantin Semichastnov
  17 siblings, 0 replies; 21+ messages in thread
From: Konstantin Semichastnov @ 2025-11-20 12:14 UTC (permalink / raw)
  To: Daniel Henrique Barboza, qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer,
	k.semichastnov



On 11/11/25 14:46, Daniel Henrique Barboza wrote:
> Hi,
> 
> In this v2 we're addressing a review comment from Konstantin in patch
> 14. We're also doing changes in the FDT (patch 5) to keep up with the
> changes that the kernel support is making [1].
> 
> I've been thinking about adding partial support for N-trace in this work
> as well. From what I can tell the difference between N-trace and E-trace
> are the encoding of the packages, with everything else staying the same.
> In this case we could add hooks in the code to be ready to support
> N-trace encoding in the future.
> 
> No other changes made. Patches based on current master.
> 
> Changes in v2:
> - patch 5:
>    - changed tr_compat from "ventana,veyron-v2-trace" to "qemu,trace-component"
>    - renamed trace encoder prop handler "cpu" to "cpus"
> - patch 14:
>    - removed updiscon generation in gen_jal()
> - v1 link: https://lore.kernel.org/qemu-riscv/20251002112335.2374517-1-dbarboza@ventanamicro.com/
> 
> 
> [1] https://lore.kernel.org/linux-riscv/20251101154245.162492-1-apatel@ventanamicro.com/
> 
> Daniel Henrique Barboza (16):
>    hw/riscv: Trace Encoder initial impl
>    hw/riscv: Trace RAM Sink initial impl
>    hw/riscv/trace-encoder: add trace start/stop logic
>    hw/riscv/virt.c: add trace encoders and trace ram sinks
>    hw/riscv: add e-trace message helpers
>    target/riscv: add initial trace instrumentation
>    hw/riscv/trace-encoder: write e-trace packets to RAM sink
>    test/qtest: add riscv-trace-test.c
>    hw/riscv/rv-trace-messages.c: add encoded trap message
>    hw/riscv, target/riscv: send trace trap messages
>    target/riscv, hw/riscv: send trace ppccd packets
>    hw/riscv/trace: add format2 msg helper
>    hw/riscv, target/riscv: send resync updiscon trace packets
>    hw/riscv/rv-trace-messages: add format 1 msgs with branch info
>    hw/riscv/trace-encoder: send branches info
>    hw/riscv/trace: update branch bit in sync messages
> 
> Mayuresh Chitale (1):
>    hw/riscv/virt.c add trace encoder and ramsink fdt nodes
> 
>   hw/riscv/Kconfig                              |   5 +
>   hw/riscv/meson.build                          |   2 +
>   hw/riscv/rv-trace-messages.c                  | 373 +++++++++++
>   hw/riscv/rv-trace-messages.h                  |  40 ++
>   hw/riscv/trace-encoder.c                      | 609 ++++++++++++++++++
>   hw/riscv/trace-encoder.h                      |  62 ++
>   hw/riscv/trace-events                         |   9 +
>   hw/riscv/trace-ram-sink.c                     | 263 ++++++++
>   hw/riscv/trace-ram-sink.h                     |  83 +++
>   hw/riscv/virt.c                               | 135 ++++
>   include/hw/riscv/virt.h                       |   2 +
>   target/riscv/cpu.h                            |  11 +
>   target/riscv/cpu_helper.c                     |  26 +-
>   target/riscv/helper.h                         |   6 +
>   .../riscv/insn_trans/trans_privileged.c.inc   |  11 +
>   target/riscv/insn_trans/trans_rvi.c.inc       |  15 +
>   target/riscv/meson.build                      |   3 +-
>   target/riscv/tcg/tcg-cpu.c                    |   5 +
>   target/riscv/trace_helper.c                   |  62 ++
>   target/riscv/translate.c                      |  19 +
>   tests/qtest/meson.build                       |   2 +-
>   tests/qtest/riscv-trace-test.c                | 120 ++++
>   22 files changed, 1860 insertions(+), 3 deletions(-)
>   create mode 100644 hw/riscv/rv-trace-messages.c
>   create mode 100644 hw/riscv/rv-trace-messages.h
>   create mode 100644 hw/riscv/trace-encoder.c
>   create mode 100644 hw/riscv/trace-encoder.h
>   create mode 100644 hw/riscv/trace-ram-sink.c
>   create mode 100644 hw/riscv/trace-ram-sink.h
>   create mode 100644 target/riscv/trace_helper.c
>   create mode 100644 tests/qtest/riscv-trace-test.c
> 
Hi, I am currently working on adding the N-Trace message format, based 
on your v1 patch set. Currently I have supported ProgTraceSync, 
ProgTraceCorrelation, ResourceFull, IndirectBranchHist, 
IndirectBranchHistSync, Ownership messages.

After finishing the internal review and rebasing over the v2 patch set, 
I would like to send it here next week.

Konstantin


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl
  2025-11-11 11:46 ` [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl Daniel Henrique Barboza
@ 2025-11-20 15:56   ` Konstantin Semichastnov
  0 siblings, 0 replies; 21+ messages in thread
From: Konstantin Semichastnov @ 2025-11-20 15:56 UTC (permalink / raw)
  To: Daniel Henrique Barboza, qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer



On 11/11/25 14:46, Daniel Henrique Barboza wrote:
> The Trace Encoder is a hardware module that interacts with a CPU,
> gathering execution information (a.k.a instruction delta trace), and
> send it downstream to other storage components such as a RAM Sink (a RAM
> storage). This trace info can then be read via software (e.g. perf) to
> reproduce the behavior of a given binary that ran in the CPU.
> 
> This implementation is based on the Efficient Trace for RISC-V [1] and
> RISC-V Trace Control Interface Specification [2]. It's not intended to
> implement all spec features: the idea is to provide a base where we can
> add extra features on demand.
> 
> We'll get back to the instruction delta trace and how we'll instrument
> TCG to produce it later. For now we'll set the minimal components to get
> the basic framework running.
> 
> This Trace Encoder impl has just the minimal bits specified in [2],
> section "Minimal Implementation". RO and RSVP bits are taken verbatim
> from [2] without considering what we're actually going to support. The
> base impl is heavily inspired by the XLNZ-ZDMA device w.r.t the usage of
> the RegisterInfo and register.h framework.
> 
> Discovery of the Trace Encoder will be made via fdt, a single entry per
> CPU. We'll connect each Trace Encoder to its CPU in the 'virt' board
> later.
> 
> [1] https://github.com/riscv-non-isa/riscv-trace-spec/releases/download/v2.0-20250616/riscv-trace-spec-asciidoc.pdf
> [2] https://github.com/riscv-non-isa/tg-nexus-trace/releases/download/1.0_Ratified/RISC-V-Trace-Control-Interface.pdf
> 
> Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
> ---
>   hw/riscv/Kconfig         |   5 +
>   hw/riscv/meson.build     |   1 +
>   hw/riscv/trace-encoder.c | 216 +++++++++++++++++++++++++++++++++++++++
>   hw/riscv/trace-encoder.h |  42 ++++++++
>   hw/riscv/trace-events    |   4 +
>   5 files changed, 268 insertions(+)
>   create mode 100644 hw/riscv/trace-encoder.c
>   create mode 100644 hw/riscv/trace-encoder.h
> 
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index fc9c35bd98..2de0892496 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -7,6 +7,10 @@ config RISCV_NUMA
>   config IBEX
>       bool
>   
> +config RISCV_TRACE
> +    bool
> +    select REGISTER
> +
>   # RISC-V machines in alphabetical order
>   
>   config MICROCHIP_PFSOC
> @@ -68,6 +72,7 @@ config RISCV_VIRT
>       select PLATFORM_BUS
>       select ACPI
>       select ACPI_PCI
> +    select RISCV_TRACE
>   
>   config SHAKTI_C
>       bool
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index 2a8d5b136c..b4a9988a62 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -14,5 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
>   	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
>   riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
>   riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
> +riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c'))
>   
>   hw_arch += {'riscv': riscv_ss}
> diff --git a/hw/riscv/trace-encoder.c b/hw/riscv/trace-encoder.c
> new file mode 100644
> index 0000000000..21bf650a6a
> --- /dev/null
> +++ b/hw/riscv/trace-encoder.c
> @@ -0,0 +1,216 @@
> +/*
> + * Emulation of a RISC-V Trace Encoder
> + *
> + * Copyright (C) 2025 Ventana Micro Systems Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "trace-encoder.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +#include "system/device_tree.h"
> +#include "hw/register.h"
> +#include "cpu.h"
> +
> +/*
> + * trTeControl register fields
> + */
> +REG32(TR_TE_CONTROL, 0x0)
> +    FIELD(TR_TE_CONTROL, ACTIVE, 0, 1)
> +    FIELD(TR_TE_CONTROL, ENABLE, 1, 1)
> +    FIELD(TR_TE_CONTROL, INST_TRACING, 2, 1)
> +    FIELD(TR_TE_CONTROL, EMPTY, 3, 1)
> +    FIELD(TR_TE_CONTROL, INST_MODE, 4, 3)
> +    FIELD(TR_TE_CONTROL, INST_SYNC_MODE, 16, 2)
> +    FIELD(TR_TE_CONTROL, FORMAT, 24, 3)
> +    /* reserved bits */
> +    FIELD(TR_TE_CONTROL, RSVP1, 7, 2)
> +    FIELD(TR_TE_CONTROL, RSVP2, 10, 1)
> +    FIELD(TR_TE_CONTROL, RSVP3, 14, 1)
> +    FIELD(TR_TE_CONTROL, RSVP4, 18, 2)
> +    FIELD(TR_TE_CONTROL, RSVP5, 27, 4)
RSVP5 should be 5 bits long, as it occupies bits [27:31]

> +
> +#define R_TR_TE_CONTROL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
> +                                   R_TR_TE_CONTROL_RSVP1_MASK | \
> +                                   R_TR_TE_CONTROL_RSVP2_MASK | \
> +                                   R_TR_TE_CONTROL_RSVP3_MASK | \
> +                                   R_TR_TE_CONTROL_RSVP4_MASK | \
> +                                   R_TR_TE_CONTROL_RSVP5_MASK)
> +
> +/* trTeControlEmpty is the only RO field and reset value */
> +#define R_TR_TE_CONTROL_RESET R_TR_TE_CONTROL_EMPTY_MASK
> +#define R_TR_TE_CONTROL_RO_BITS R_TR_TE_CONTROL_EMPTY_MASK
> +
> +/*
> + * trTeImpl register fields
> + */
> +REG32(TR_TE_IMPL, 0x4)
> +    FIELD(TR_TE_IMPL, VER_MAJOR, 0, 4)
> +    FIELD(TR_TE_IMPL, VER_MINOR, 4, 4)
> +    FIELD(TR_TE_IMPL, COMP_TYPE, 8, 4)
> +    FIELD(TR_TE_IMPL, PROTOCOL_MAJOR, 16, 4)
> +    FIELD(TR_TE_IMPL, PROTOCOL_MINOR, 20, 4)
> +    /* reserved bits */
> +    FIELD(TR_TE_IMPL, RSVP1, 12, 4)
> +    FIELD(TR_TE_IMPL, RSVP2, 24, 8)
> +
> +#define R_TR_TE_IMPL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
> +                                R_TR_TE_IMPL_RSVP1_MASK | \
> +                                R_TR_TE_IMPL_RSVP2_MASK)
> +
> +#define R_TR_TE_IMPL_RO_BITS (R_TR_TE_IMPL_VER_MAJOR_MASK | \
> +                              R_TR_TE_IMPL_VER_MINOR_MASK | \
> +                              R_TR_TE_IMPL_COMP_TYPE_MASK | \
> +                              R_TR_TE_IMPL_PROTOCOL_MAJOR_MASK | \
> +                              R_TR_TE_IMPL_PROTOCOL_MINOR_MASK)
> +
> +#define R_TR_TE_IMPL_RESET (BIT(0) | BIT(8))
> +
> +static RegisterAccessInfo trencoder_regs_info[] = {
> +    {   .name = "TR_TE_CONTROL", .addr = A_TR_TE_CONTROL,
> +        .rsvd = R_TR_TE_CONTROL_RSVP_BITS,
> +        .reset = R_TR_TE_CONTROL_RESET,
> +        .ro = R_TR_TE_CONTROL_RO_BITS,
> +    },
> +    {   .name = "TR_TE_IMPL", .addr = A_TR_TE_IMPL,
> +        .rsvd = R_TR_TE_IMPL_RSVP_BITS,
> +        .reset = R_TR_TE_IMPL_RESET,
> +        .ro = R_TR_TE_IMPL_RO_BITS,
> +    },
> +};
> +
> +static uint64_t trencoder_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    TraceEncoder *te = TRACE_ENCODER(opaque);
> +    RegisterInfo *r = &te->regs_info[addr / 4];
> +
> +    if (!r->data) {
> +        trace_trencoder_read_error(addr);
> +        return 0;
> +    }
> +
> +    return register_read(r, ~0, NULL, false);
> +}
> +
> +static void trencoder_write(void *opaque, hwaddr addr,
> +                            uint64_t value, unsigned size)
> +{
> +    TraceEncoder *te = TRACE_ENCODER(opaque);
> +    RegisterInfo *r = &te->regs_info[addr / 4];
> +
> +    if (!r->data) {
> +        trace_trencoder_write_error(addr, value);
> +        return;
> +    }
> +
> +    register_write(r, value, ~0, NULL, false);
> +}
> +
> +static const MemoryRegionOps trencoder_ops = {
> +    .read = trencoder_read,
> +    .write = trencoder_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static void trencoder_reset(DeviceState *dev)
> +{
> +    TraceEncoder *te = TRACE_ENCODER(dev);
> +
> +    for (int i = 0; i < ARRAY_SIZE(te->regs_info); i++) {
> +        register_reset(&te->regs_info[i]);
> +    }
> +}
> +
> +static void trencoder_realize(DeviceState *dev, Error **errp)
> +{
> +    TraceEncoder *te = TRACE_ENCODER(dev);
> +
> +    memory_region_init_io(&te->reg_mem, OBJECT(dev),
> +                          &trencoder_ops, te,
> +                          TYPE_TRACE_ENCODER,
> +                          te->reg_mem_size);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &te->reg_mem);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, te->baseaddr);
> +
> +    /* RegisterInfo init taken from hw/dma/xlnx-zdma.c */
> +    for (int i = 0; i < ARRAY_SIZE(trencoder_regs_info); i++) {
> +        uint32_t reg_idx = trencoder_regs_info[i].addr / 4;
> +        RegisterInfo *r = &te->regs_info[reg_idx];
> +
> +        *r = (RegisterInfo) {
> +            .data = (uint8_t *)&te->regs[reg_idx],
> +            .data_size = sizeof(uint32_t),
> +            .access = &trencoder_regs_info[i],
> +            .opaque = te,
> +        };
> +    }
> +}
> +
> +static const Property trencoder_props[] = {
> +    /*
> +     * We need a link to the associated CPU to
> +     * enable/disable tracing.
> +     */
> +    DEFINE_PROP_LINK("cpu", TraceEncoder, cpu, TYPE_RISCV_CPU, RISCVCPU *),
> +    DEFINE_PROP_UINT64("baseaddr", TraceEncoder, baseaddr, 0),
> +    DEFINE_PROP_UINT64("dest-baseaddr", TraceEncoder, dest_baseaddr, 0),
> +    DEFINE_PROP_UINT64("ramsink-ramstart", TraceEncoder,
> +                       ramsink_ramstart, 0),
> +    DEFINE_PROP_UINT64("ramsink-ramlimit", TraceEncoder,
> +                       ramsink_ramlimit, 0),
> +    DEFINE_PROP_UINT32("reg-mem-size", TraceEncoder,
> +                       reg_mem_size, TRACE_R_MAX * 4),
> +    DEFINE_PROP_INT32("cpu-id", TraceEncoder, cpu_id, 0),
> +};
> +
> +static const VMStateDescription vmstate_trencoder = {
> +    .name = TYPE_TRACE_ENCODER,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, TraceEncoder, TRACE_R_MAX),
> +        VMSTATE_UINT64(baseaddr, TraceEncoder),
> +        VMSTATE_UINT64(dest_baseaddr, TraceEncoder),
> +        VMSTATE_UINT64(ramsink_ramstart, TraceEncoder),
> +        VMSTATE_UINT64(ramsink_ramlimit, TraceEncoder),
> +        VMSTATE_INT32(cpu_id, TraceEncoder),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +static void trencoder_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    device_class_set_legacy_reset(dc, trencoder_reset);
> +    device_class_set_props(dc, trencoder_props);
> +    dc->realize = trencoder_realize;
> +    dc->vmsd = &vmstate_trencoder;
> +}
> +
> +static const TypeInfo trencoder_info = {
> +    .name          = TYPE_TRACE_ENCODER,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(TraceEncoder),
> +    .class_init    = trencoder_class_init,
> +};
> +
> +static void trencoder_register_types(void)
> +{
> +    type_register_static(&trencoder_info);
> +}
> +
> +type_init(trencoder_register_types)
> diff --git a/hw/riscv/trace-encoder.h b/hw/riscv/trace-encoder.h
> new file mode 100644
> index 0000000000..71002f58a4
> --- /dev/null
> +++ b/hw/riscv/trace-encoder.h
> @@ -0,0 +1,42 @@
> +/*
> + * Emulation of a RISC-V Trace Encoder
> + *
> + * Copyright (C) 2025 Ventana Micro Systems Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef RISCV_TRACE_ENCODER_H
> +#define RISCV_TRACE_ENCODER_H
> +
> +#include "hw/sysbus.h"
> +#include "hw/register.h"
> +#include "system/dma.h"
> +#include "qom/object.h"
> +#include "cpu.h"
> +
> +#define TRACE_R_MAX (0xFFF / 4)
> +
> +struct TraceEncoder {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    RISCVCPU *cpu;
> +    int cpu_id;
> +
> +    MemoryRegion reg_mem;
> +    uint32_t reg_mem_size;
> +
> +    hwaddr baseaddr;
> +    hwaddr dest_baseaddr;
> +    hwaddr ramsink_ramstart;
> +    hwaddr ramsink_ramlimit;
> +    uint32_t regs[TRACE_R_MAX];
> +    RegisterInfo regs_info[TRACE_R_MAX];
> +};
> +
> +#define TYPE_TRACE_ENCODER "trace-encoder"
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(TraceEncoder, TRACE_ENCODER)
> +
> +#endif
> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
> index b50b14a654..0cbf6ffcb6 100644
> --- a/hw/riscv/trace-events
> +++ b/hw/riscv/trace-events
> @@ -24,3 +24,7 @@ riscv_iommu_hpm_incr_ctr(uint64_t cntr_val) "cntr_val 0x%"PRIx64
>   riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d"
>   riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64
>   riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64
> +
> +# trace-encoder.c
> +trencoder_read_error(uint64_t addr) "addr 0x%" PRIx64
> +trencoder_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 02/17] hw/riscv: Trace RAM Sink initial impl
  2025-11-11 11:46 ` [PATCH v2 02/17] hw/riscv: Trace RAM Sink " Daniel Henrique Barboza
@ 2025-11-20 18:03   ` Konstantin Semichastnov
  0 siblings, 0 replies; 21+ messages in thread
From: Konstantin Semichastnov @ 2025-11-20 18:03 UTC (permalink / raw)
  To: Daniel Henrique Barboza, qemu-devel
  Cc: qemu-riscv, alistair.francis, liwei1518, zhiwei_liu, palmer



On 11/11/25 14:46, Daniel Henrique Barboza wrote:
> Following the effort to implement the basic support for Efficient Trace
> (e-trace) in QEMU we'll add a Trace RAM Sink implementation.
> 
> Similar to the Trace Encoder, this is inspired in both the Efficient
> Trace for RISC-V [1] and  RISC-V Trace Control Interface Specification
> [2] specs. It implements a minimal set of features to get started - only
> SMEM will be supported for now.
> 
> We'll implement the RAM sink logic in the next patches, although most of
> the work will be done by the trace encoder.
> 
> [1] https://github.com/riscv-non-isa/riscv-trace-spec/releases/download/v2.0-20250616/riscv-trace-spec-asciidoc.pdf
> [2] https://github.com/riscv-non-isa/tg-nexus-trace/releases/download/1.0_Ratified/RISC-V-Trace-Control-Interface.pdf
> 
> Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
> ---
>   hw/riscv/meson.build      |   2 +-
>   hw/riscv/trace-events     |   4 +
>   hw/riscv/trace-ram-sink.c | 263 ++++++++++++++++++++++++++++++++++++++
>   hw/riscv/trace-ram-sink.h |  83 ++++++++++++
>   4 files changed, 351 insertions(+), 1 deletion(-)
>   create mode 100644 hw/riscv/trace-ram-sink.c
>   create mode 100644 hw/riscv/trace-ram-sink.h
> 
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index b4a9988a62..2aadbe1e50 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -14,6 +14,6 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
>   	'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
>   riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
>   riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
> -riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c'))
> +riscv_ss.add(when: 'CONFIG_RISCV_TRACE', if_true: files('trace-encoder.c', 'trace-ram-sink.c'))
>   
>   hw_arch += {'riscv': riscv_ss}
> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
> index 0cbf6ffcb6..14e333fd9e 100644
> --- a/hw/riscv/trace-events
> +++ b/hw/riscv/trace-events
> @@ -28,3 +28,7 @@ riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx
>   # trace-encoder.c
>   trencoder_read_error(uint64_t addr) "addr 0x%" PRIx64
>   trencoder_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
> +
> +# trace-ram-sink.c
> +tr_ramsink_read_error(uint64_t addr) "addr 0x%" PRIx64
> +tr_ramsink_write_error(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
> diff --git a/hw/riscv/trace-ram-sink.c b/hw/riscv/trace-ram-sink.c
> new file mode 100644
> index 0000000000..e00de80c04
> --- /dev/null
> +++ b/hw/riscv/trace-ram-sink.c
> @@ -0,0 +1,263 @@
> +/*
> + * Emulation of a RISC-V Trace RAM Sink
> + *
> + * Copyright (C) 2025 Ventana Micro Systems Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "trace-ram-sink.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +#include "system/device_tree.h"
> +#include "hw/sysbus.h"
> +#include "hw/register.h"
> +
> +#define R_TR_RAM_CONTROL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
> +                                    R_TR_RAM_CONTROL_RSVP1_MASK | \
> +                                    R_TR_RAM_CONTROL_RSVP2_MASK | \
> +                                    R_TR_RAM_CONTROL_RSVP3_MASK | \
> +                                    R_TR_RAM_CONTROL_RSVP4_MASK)
> +
> +/* trRamEmpty is the only RO field and reset value */
> +#define R_TR_RAM_CONTROL_RESET R_TR_RAM_CONTROL_EMPTY_MASK
> +#define R_TR_RAM_CONTROL_RO_BITS R_TR_RAM_CONTROL_EMPTY_MASK
> +
> +#define R_TR_RAM_IMPL_RSVP_BITS (MAKE_64BIT_MASK(32, 32) | \
> +                                 R_TR_RAM_IMPL_RSVP1_MASK)
> +
> +#define R_TR_RAM_IMPL_RO_BITS (R_TR_RAM_IMPL_VER_MAJOR_MASK | \
> +                               R_TR_RAM_IMPL_VER_MINOR_MASK | \
> +                               R_TR_RAM_IMPL_COMP_TYPE_MASK | \
> +                               R_TR_RAM_IMPL_HAS_SRAM_MASK | \
> +                               R_TR_RAM_IMPL_HAS_SMEM_MASK)
> +
> +#define R_TR_RAM_IMPL_RESET (BIT(0) | 0x9 << 8)
I suggest to set here trRamHasSMEM bit to 1, because we actually do have 
SMEM supported

> +
> +static RegisterAccessInfo tr_ramsink_regs_info[] = {
> +    {   .name = "TR_RAM_CONTROL", .addr = A_TR_RAM_CONTROL,
> +        .rsvd = R_TR_RAM_CONTROL_RSVP_BITS,
> +        .reset = R_TR_RAM_CONTROL_RESET,
> +        .ro = R_TR_RAM_CONTROL_RO_BITS,
> +    },
> +    {   .name = "TR_RAM_IMPL", .addr = A_TR_RAM_IMPL,
> +        .rsvd = R_TR_RAM_IMPL_RSVP_BITS,
> +        .reset = R_TR_RAM_IMPL_RESET,
> +        .ro = R_TR_RAM_IMPL_RO_BITS,
> +    },
> +    {   .name = "TR_RAM_START_LOW", .addr = A_TR_RAM_START_LOW,
> +    },
> +    {   .name = "TR_RAM_START_HIGH", .addr = A_TR_RAM_START_HIGH,
> +    },
> +    {   .name = "TR_RAM_LIMIT_LOW", .addr = A_TR_RAM_LIMIT_LOW,
> +    },
> +    {   .name = "TR_RAM_LIMIT_HIGH", .addr = A_TR_RAM_LIMIT_HIGH,
> +    },
> +    {   .name = "TR_RAM_WP_LOW", .addr = A_TR_RAM_WP_LOW,
> +    },
> +    {   .name = "TR_RAM_WP_HIGH", .addr = A_TR_RAM_WP_HIGH,
> +    },
> +};
> +
> +static uint64_t tr_ramsink_regread(void *opaque, hwaddr addr, unsigned size)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
> +    RegisterInfo *r = &tram->regs_info[addr / 4];
> +
> +    if (!r->data) {
> +        trace_tr_ramsink_read_error(addr);
> +        return 0;
> +    }
> +
> +    return register_read(r, ~0, NULL, false);
> +}
> +
> +static void tr_ramsink_regwrite(void *opaque, hwaddr addr,
> +                            uint64_t value, unsigned size)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
> +    RegisterInfo *r = &tram->regs_info[addr / 4];
> +
> +    if (!r->data) {
> +        trace_tr_ramsink_write_error(addr, value);
> +        return;
> +    }
> +
> +    register_write(r, value, ~0, NULL, false);
> +}
> +
> +static const MemoryRegionOps tr_ramsink_regops = {
> +    .read = tr_ramsink_regread,
> +    .write = tr_ramsink_regwrite,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static uint64_t tr_ramsink_msgread(void *opaque, hwaddr addr, unsigned size)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
> +
> +    switch (size) {
> +    case 1:
> +        return tram->msgs[addr];
> +    case 2:
> +        return (uint16_t)tram->msgs[addr];
> +    case 4:
> +        return (uint32_t)tram->msgs[addr];
> +    default:
> +        g_assert_not_reached();
> +    }
Here we will always return single byte, because tram->msgs has
type (uint8_t *)

So, I suggest to call here little endian load helper
to avoid manually handling size and byte order:

ldn_le_p(tram->msgs + addr, size)

> +}
> +
> +static void tr_ramsink_msgwrite(void *opaque, hwaddr addr,
> +                                uint64_t value, unsigned size)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(opaque);
> +
> +    switch (size) {
> +    case 1:
> +        tram->msgs[addr] = value;
> +        break;
> +    case 2:
> +        tram->msgs[addr] = extract16(value, 0, 8);
> +        tram->msgs[addr + 1] = extract16(value, 8, 8);
> +        break;
> +    case 4:
> +        tram->msgs[addr] = extract32(value, 0, 8);
> +        tram->msgs[addr + 1] = extract32(value, 8, 8);
> +        tram->msgs[addr + 2] = extract32(value, 16, 8);
> +        tram->msgs[addr + 3] = extract32(value, 24, 8);
> +        break;
> +    default:
> +        g_assert_not_reached();
> +    }
Here also we can use little endian store helper to avoid
handling write size and byte order:

stn_le_p(tram->msgs + addr, size, value)

> +}
> +
> +static const MemoryRegionOps tr_ramsink_smemops = {
> +    .read = tr_ramsink_msgread,
> +    .write = tr_ramsink_msgwrite,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static void tr_ramsink_setup_regs(TraceRamSink *tram)
> +{
> +    hwaddr ramlimit = tram->smemaddr + tram->smemsize;
> +
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_START_LOW, ADDR,
> +                     extract64(tram->smemaddr, 2, 30));
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_START_HIGH, ADDR,
> +                     extract64(tram->smemaddr, 32, 32));
> +
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_WP_LOW, ADDR,
> +                     extract64(tram->smemaddr, 2, 30));
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_WP_HIGH, ADDR,
> +                     extract64(tram->smemaddr, 32, 32));
> +
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_LIMIT_LOW, ADDR,
> +                     extract64(ramlimit, 2, 30));
> +    ARRAY_FIELD_DP32(tram->regs, TR_RAM_LIMIT_HIGH, ADDR,
> +                     extract64(ramlimit, 32, 32));
> +}
> +
> +static void tr_ramsink_reset(DeviceState *dev)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(dev);
> +
> +    for (int i = 0; i < ARRAY_SIZE(tram->regs_info); i++) {
> +        register_reset(&tram->regs_info[i]);
> +    }
> +
> +    tr_ramsink_setup_regs(tram);
> +}
> +
> +static void tr_ramsink_realize(DeviceState *dev, Error **errp)
> +{
> +    TraceRamSink *tram = TRACE_RAM_SINK(dev);
> +
> +    memory_region_init_io(&tram->reg_mem, OBJECT(dev),
> +                          &tr_ramsink_regops, tram,
> +                          "trace-ram-sink-regs",
> +                          tram->reg_mem_size);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(tram), &tram->reg_mem);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(tram), 0, tram->baseaddr);
> +
> +    g_assert(tram->smemsize > 0);
> +    tram->msgs = g_malloc0(tram->smemsize);
> +
> +    memory_region_init_io(&tram->smem, OBJECT(dev),
> +                          &tr_ramsink_smemops, tram,
> +                          "trace-ram-sink-smem",
> +                          tram->smemsize);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &tram->smem);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, tram->smemaddr);
> +
> +    /* RegisterInfo init taken from hw/dma/xlnx-zdma.c */
> +    for (int i = 0; i < ARRAY_SIZE(tr_ramsink_regs_info); i++) {
> +        uint32_t reg_idx = tr_ramsink_regs_info[i].addr / 4;
> +        RegisterInfo *r = &tram->regs_info[reg_idx];
> +
> +        *r = (RegisterInfo) {
> +            .data = (uint8_t *)&tram->regs[reg_idx],
> +            .data_size = sizeof(uint32_t),
> +            .access = &tr_ramsink_regs_info[i],
> +            .opaque = tram,
> +        };
> +    }
> +}
> +
> +static const Property tr_ramsink_props[] = {
> +    DEFINE_PROP_UINT64("baseaddr", TraceRamSink, baseaddr, 0),
> +    DEFINE_PROP_UINT64("smemaddr", TraceRamSink, smemaddr, 0),
> +    DEFINE_PROP_UINT32("smemsize", TraceRamSink, smemsize, 0),
> +    DEFINE_PROP_UINT32("reg-mem-size", TraceRamSink,
> +                       reg_mem_size, TR_DEV_REGMAP_SIZE),
> +};
> +
> +static const VMStateDescription vmstate_tr_ramsink = {
> +    .name = TYPE_TRACE_RAM_SINK,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, TraceRamSink, TRACE_R_MAX),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +static void tr_ramsink_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    device_class_set_legacy_reset(dc, tr_ramsink_reset);
> +    device_class_set_props(dc, tr_ramsink_props);
> +    dc->realize = tr_ramsink_realize;
> +    dc->vmsd = &vmstate_tr_ramsink;
> +}
> +
> +static const TypeInfo tr_ramsink_info = {
> +    .name          = TYPE_TRACE_RAM_SINK,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(TraceRamSink),
> +    .class_init    = tr_ramsink_class_init,
> +};
> +
> +static void tr_ramsink_register_types(void)
> +{
> +    type_register_static(&tr_ramsink_info);
> +}
> +
> +type_init(tr_ramsink_register_types)
> diff --git a/hw/riscv/trace-ram-sink.h b/hw/riscv/trace-ram-sink.h
> new file mode 100644
> index 0000000000..cd55145669
> --- /dev/null
> +++ b/hw/riscv/trace-ram-sink.h
> @@ -0,0 +1,83 @@
> +/*
> + * Emulation of a RISC-V Trace RAM Sink
> + *
> + * Copyright (C) 2025 Ventana Micro Systems Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef RISCV_TRACE_RAM_SINK_H
> +#define RISCV_TRACE_RAM_SINK_H
> +
> +#include "hw/register.h"
> +#include "hw/sysbus.h"
> +#include "system/dma.h"
> +#include "qom/object.h"
> +
> +#define TRACE_R_MAX (0xFFF / 4)
> +#define TR_DEV_REGMAP_SIZE 0x1000
> +
> +/*
> + * The Trace Encoder will read/write those regs so put their
> + * declaration in this header.
> + */
> +REG32(TR_RAM_CONTROL, 0x0)
> +    FIELD(TR_RAM_CONTROL, ACTIVE, 0, 1)
> +    FIELD(TR_RAM_CONTROL, ENABLE, 1, 1)
> +    FIELD(TR_RAM_CONTROL, EMPTY, 3, 1)
> +    FIELD(TR_RAM_CONTROL, MODE, 4, 1)
> +    FIELD(TR_RAM_CONTROL, STOP_ON_WRAP, 8, 1)
> +    FIELD(TR_RAM_CONTROL, MEM_FORMAT, 9, 2)
> +    /* reserved bits */
> +    FIELD(TR_RAM_CONTROL, RSVP1, 2, 1)
> +    FIELD(TR_RAM_CONTROL, RSVP2, 5, 2)
RSVP2 should be 3 bits long, as it occupies bits [7:5]

> +    FIELD(TR_RAM_CONTROL, RSVP3, 11, 1)
> +    FIELD(TR_RAM_CONTROL, RSVP4, 15, 16)
Here is the same typo: RSVP4 should be 17 bits long, as it occupies
bits [31:15]

> +
> +REG32(TR_RAM_IMPL, 0x4)
> +    FIELD(TR_RAM_IMPL, VER_MAJOR, 0, 4)
> +    FIELD(TR_RAM_IMPL, VER_MINOR, 4, 4)
> +    FIELD(TR_RAM_IMPL, COMP_TYPE, 8, 4)
> +    FIELD(TR_RAM_IMPL, HAS_SRAM, 12, 1)
> +    FIELD(TR_RAM_IMPL, HAS_SMEM, 13, 1)
> +    /* reserved bits */
> +    FIELD(TR_RAM_IMPL, RSVP1, 14, 18)
> +
> +REG32(TR_RAM_START_LOW, 0x010)
> +    FIELD(TR_RAM_START_LOW, ADDR, 2, 30)
> +REG32(TR_RAM_START_HIGH, 0x014)
> +    FIELD(TR_RAM_START_HIGH, ADDR, 0, 32)
> +
> +REG32(TR_RAM_LIMIT_LOW, 0x018)
> +    FIELD(TR_RAM_LIMIT_LOW, ADDR, 2, 30)
> +REG32(TR_RAM_LIMIT_HIGH, 0x01C)
> +    FIELD(TR_RAM_LIMIT_HIGH, ADDR, 0, 32)
> +
> +REG32(TR_RAM_WP_LOW, 0x020)
> +    FIELD(TR_RAM_WP_LOW, WRAP, 0, 1)
> +    FIELD(TR_RAM_WP_LOW, ADDR, 2, 30)
> +REG32(TR_RAM_WP_HIGH, 0x024)
> +    FIELD(TR_RAM_WP_HIGH, ADDR, 0, 32)
> +
> +struct TraceRamSink {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion reg_mem;
> +    uint32_t reg_mem_size;
> +
> +    hwaddr baseaddr;
> +    uint32_t regs[TRACE_R_MAX];
> +    RegisterInfo regs_info[TRACE_R_MAX];
> +
> +    hwaddr smemaddr;
> +    MemoryRegion smem;
> +    uint32_t smemsize;
> +    uint8_t *msgs;
> +};
> +
> +#define TYPE_TRACE_RAM_SINK "trace-ram-sink"
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(TraceRamSink, TRACE_RAM_SINK)
> +
> +#endif



^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2025-11-20 18:04 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-11 11:46 [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 01/17] hw/riscv: Trace Encoder initial impl Daniel Henrique Barboza
2025-11-20 15:56   ` Konstantin Semichastnov
2025-11-11 11:46 ` [PATCH v2 02/17] hw/riscv: Trace RAM Sink " Daniel Henrique Barboza
2025-11-20 18:03   ` Konstantin Semichastnov
2025-11-11 11:46 ` [PATCH v2 03/17] hw/riscv/trace-encoder: add trace start/stop logic Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 04/17] hw/riscv/virt.c: add trace encoders and trace ram sinks Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 05/17] hw/riscv/virt.c add trace encoder and ramsink fdt nodes Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 06/17] hw/riscv: add e-trace message helpers Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 07/17] target/riscv: add initial trace instrumentation Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 08/17] hw/riscv/trace-encoder: write e-trace packets to RAM sink Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 09/17] test/qtest: add riscv-trace-test.c Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 10/17] hw/riscv/rv-trace-messages.c: add encoded trap message Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 11/17] hw/riscv, target/riscv: send trace trap messages Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 12/17] target/riscv, hw/riscv: send trace ppccd packets Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 13/17] hw/riscv/trace: add format2 msg helper Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 14/17] hw/riscv, target/riscv: send resync updiscon trace packets Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 15/17] hw/riscv/rv-trace-messages: add format 1 msgs with branch info Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 16/17] hw/riscv/trace-encoder: send branches info Daniel Henrique Barboza
2025-11-11 11:46 ` [PATCH v2 17/17] hw/riscv/trace: update branch bit in sync messages Daniel Henrique Barboza
2025-11-20 12:14 ` [PATCH v2 00/17] hw/riscv, target/riscv: initial e-trace support Konstantin Semichastnov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).