From: liangzhen zhen.liang@spacemit.com
This series adds Linux RISC-V trace support via CoreSight, implementing RISC-V trace drivers within the CoreSight framework and integrating them with perf tools. The K3 SoC contains RISC-V Encoder, Funnel, ATB, CoreSight Funnel, and CoreSight TMC components, which can be directly leveraged through the existing CoreSight infrastructure.
Linux RISC-V trace support form Anup Patel: (https://patchwork.kernel.org/project/linux-riscv/cover/20260225062448.402794...) which currently lacks ATB component support and guidance on reusing CoreSight components.
The series includes: - RISC-V trace driver implementation within the CoreSight framework - RISC-V Trace Encoder, Funnel, and ATB Bridge drivers as CoreSight devices - RISC-V trace PMU record capabilities and parsing events in perf. - RISC-V Nexus Trace decoder for perf tools
Any comments or suggestions are welcome.
Verification on K3 SoC: To verify this patch series on K3 hardware, the following device tree are required: 1. RISC-V Trace Encoder node (8) 2. RISC-V ATB Bridge node (8) 3. RISC-V Trace Funnel node (2) 3. CoreSight Funnel configuration for RISC-V (1) 4. CoreSight TMC configuration for trace buffer (1)
/{ dummy_clk: apb-pclk { compatible = "fixed-clock"; #clock-cells = <0x0>; clock-output-names = "clk14mhz"; clock-frequency = <14000000>; };
soc: soc { #address-cells = <2>; #size-cells = <2>;
encoder0: encoder@d9002000 { compatible = "riscv,trace-encoder"; reg = <0x0 0xd9002000 0x0 0x1000>; cpus = <&cpu_0>; out-ports { port { cluster0_encoder0_out_port: endpoint { remote-endpoint = <&cluster0_bridge0_in_port>; }; }; }; };
bridge0: bridge@d9003000 { compatible = "riscv,trace-atbbridge"; reg = <0x0 0xd9003000 0x0 0x1000>; cpus = <&cpu_0>; out-ports { port { cluster0_bridge0_out_port: endpoint { remote-endpoint = <&cluster0_funnel_in_port0>; }; }; }; in-ports { port { cluster0_bridge0_in_port: endpoint { remote-endpoint = <&cluster0_encoder0_out_port>; }; }; }; };
...
cluster0_funnel: funnel@d9000000 { compatible = "riscv,trace-funnel"; reg = <0x0 0xd9000000 0x0 0x1000>; cpus = <&cpu_0>, <&cpu_1>, <&cpu_2>, <&cpu_3>; riscv,timestamp-present; out-ports { port { cluster0_funnel_out_port: endpoint { remote-endpoint = <&main_funnel_in_port0>; }; }; }; in-ports { #address-cells = <1>; #size-cells = <0>;
port@0 { reg = <0>; cluster0_funnel_in_port0: endpoint { remote-endpoint = <&cluster0_bridge0_out_port>; }; };
port@1 { reg = <1>; cluster0_funnel_in_port1: endpoint { remote-endpoint = <&cluster0_bridge1_out_port>; }; };
port@2 { reg = <2>; cluster0_funnel_in_port2: endpoint { remote-endpoint = <&cluster0_bridge2_out_port>; }; };
port@3 { reg = <3>; cluster0_funnel_in_port3: endpoint { remote-endpoint = <&cluster0_bridge3_out_port>; }; }; }; };
cluster1_funnel: funnel@d9010000 { compatible = "riscv,trace-funnel"; reg = <0x0 0xd9010000 0x0 0x1000>; cpus = <&cpu_4>, <&cpu_5>, <&cpu_6>, <&cpu_7>; riscv,timestamp-present; out-ports { port { cluster1_funnel_out_port: endpoint { remote-endpoint = <&main_funnel_in_port1>; }; }; }; in-ports { #address-cells = <1>; #size-cells = <0>;
port@0 { reg = <0>; cluster1_funnel_in_port0: endpoint { remote-endpoint = <&cluster1_bridge0_out_port>; }; };
port@1 { reg = <1>; cluster1_funnel_in_port1: endpoint { remote-endpoint = <&cluster1_bridge1_out_port>; }; };
port@2 { reg = <2>; cluster1_funnel_in_port2: endpoint { remote-endpoint = <&cluster1_bridge2_out_port>; }; };
port@3 { reg = <3>; cluster1_funnel_in_port3: endpoint { remote-endpoint = <&cluster1_bridge3_out_port>; }; }; }; };
main_funnel: funnel@d9042000 { compatible = "arm,coresight-dynamic-funnel", "arm,primecell"; reg = <0x0 0xd9042000 0x0 0x1000>; clocks = <&dummy_clk>; clock-names = "apb_pclk"; out-ports { port { main_funnel_out_port: endpoint { remote-endpoint = <&etf_in_port>; }; }; }; in-ports { #address-cells = <1>; #size-cells = <0>;
port@0 { reg = <0>; main_funnel_in_port0: endpoint { remote-endpoint = <&cluster0_funnel_out_port>; }; };
port@1 { reg = <1>; main_funnel_in_port1: endpoint { remote-endpoint = <&cluster1_funnel_out_port>; }; }; }; };
etf: etf@d9043000 { compatible = "arm,coresight-tmc", "arm,primecell"; reg = <0x0 0xd9043000 0x0 0x1000>; clocks = <&dummy_clk>; clock-names = "apb_pclk"; out-ports { port { etf_out_port: endpoint { remote-endpoint = <&etr_in_port>; }; }; }; in-ports { port { etf_in_port: endpoint { remote-endpoint = <&main_funnel_out_port>; }; }; }; };
etr: etr@d9044000 { compatible = "arm,coresight-tmc", "arm,primecell"; reg = <0x0 0xd9044000 0x0 0x1000>; clocks = <&dummy_clk>; clock-names = "apb_pclk"; arm,scatter-gather; in-ports { port { etr_in_port: endpoint { remote-endpoint = <&etf_out_port>; }; }; }; }; }; };
Verification case:
~ # perf list pmu rvtrace// [Kernel PMU event]
~ # perf record -e rvtrace/@tmc_etr0/ --per-thread uname Linux [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.191 MB perf.data ] ~ # perf script uname 137 [003] 1 branches: ffffffff80931470 rvtrace_poll_bit+0x38 ([kernel.kallsyms]) => ffffffff80931492 rvtrace_poll_bit+0x5a ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff809328a6 encoder_enable_hw+0x252 ([kernel.kallsyms]) => ffffffff809328ba encoder_enable_hw+0x266 ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff80932c4a encoder_enable+0x82 ([kernel.kallsyms]) => ffffffff80932c36 encoder_enable+0x6e ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff80928198 etm_event_start+0xf0 ([kernel.kallsyms]) => ffffffff809281aa etm_event_start+0x102 ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff809281e6 etm_event_start+0x13e ([kernel.kallsyms]) => ffffffff8092755e coresight_get_sink_id+0x16 ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff8092820e etm_event_start+0x166 ([kernel.kallsyms]) => ffffffff80928226 etm_event_start+0x17e ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff801c3bb4 perf_report_aux_output_id+0x0 ([kernel.kallsyms]) => ffffffff801c3bd6 perf_report_aux_output_id+0x22 ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff801c3c5a perf_report_aux_output_id+0xa6 ([kernel.kallsyms]) => ffffffff801c3bf0 perf_report_aux_output_id+0x3c ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff801c3c40 perf_report_aux_output_id+0x8c ([kernel.kallsyms]) => ffffffff801c3aea __perf_event_header__init_id+0x2a ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff801c3b42 __perf_event_header__init_id+0x82 ([kernel.kallsyms]) => ffffffff801c3b4a __perf_event_header__init_id+0x8a ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff801c3bb0 __perf_event_header__init_id+0xf0 ([kernel.kallsyms]) => ffffffff801c3b58 __perf_event_header__init_id+0x98 ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff8004c658 __task_pid_nr_ns+0x0 ([kernel.kallsyms]) => ffffffff8004c696 __task_pid_nr_ns+0x3e ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff8004c71e __task_pid_nr_ns+0xc6 ([kernel.kallsyms]) => ffffffff8004c6a4 __task_pid_nr_ns+0x4c ([kernel.kallsyms]) uname 137 [003] 1 branches: ffffffff8004c6e4 __task_pid_nr_ns+0x8c ([kernel.kallsyms]) => ffffffff8004c6e4 __task_pid_nr_ns+0x8c ([kernel.kallsyms]) ...
liangzhen (12): coresight: Add RISC-V support to CoreSight tracing coresight: Initial implementation of RISC-V trace driver coresight: Add RISC-V Trace Encoder driver coresight: Add RISC-V Trace Funnel driver coresight: Add RISC-V Trace ATB Bridge driver coresight rvtrace: Add timestamp component support for encoder and funnel coresight: Add RISC-V PMU name support perf tools: riscv: making rvtrace PMU listable perf tools: Add RISC-V trace PMU record capabilities perf tools: Add Nexus RISC-V Trace decoder perf symbols: Add RISC-V PLT entry sizes perf tools: Integrate RISC-V trace decoder into auxtrace
drivers/hwtracing/Kconfig | 2 + drivers/hwtracing/coresight/Kconfig | 46 +- drivers/hwtracing/coresight/Makefile | 6 + drivers/hwtracing/coresight/coresight-core.c | 8 + .../hwtracing/coresight/coresight-etm-perf.c | 1 - .../hwtracing/coresight/coresight-etm-perf.h | 21 + .../hwtracing/coresight/coresight-platform.c | 1 - .../hwtracing/coresight/coresight-tmc-etf.c | 4 + .../hwtracing/coresight/coresight-tmc-etr.c | 4 + .../hwtracing/coresight/rvtrace-atbbridge.c | 239 +++ drivers/hwtracing/coresight/rvtrace-core.c | 135 ++ .../coresight/rvtrace-encoder-core.c | 562 +++++++ .../coresight/rvtrace-encoder-sysfs.c | 363 +++++ drivers/hwtracing/coresight/rvtrace-encoder.h | 151 ++ drivers/hwtracing/coresight/rvtrace-funnel.c | 337 ++++ drivers/hwtracing/coresight/rvtrace-funnel.h | 39 + .../hwtracing/coresight/rvtrace-timestamp.c | 278 ++++ .../hwtracing/coresight/rvtrace-timestamp.h | 64 + include/linux/coresight-pmu.h | 4 + include/linux/rvtrace.h | 116 ++ tools/arch/riscv/include/asm/insn.h | 645 ++++++++ tools/perf/arch/riscv/util/Build | 2 + tools/perf/arch/riscv/util/auxtrace.c | 490 ++++++ tools/perf/arch/riscv/util/pmu.c | 20 + tools/perf/util/Build | 3 + tools/perf/util/auxtrace.c | 4 + tools/perf/util/auxtrace.h | 1 + tools/perf/util/nexus-rv-decoder/Build | 1 + .../util/nexus-rv-decoder/nexus-rv-decoder.c | 1364 +++++++++++++++++ .../util/nexus-rv-decoder/nexus-rv-decoder.h | 139 ++ .../perf/util/nexus-rv-decoder/nexus-rv-msg.h | 190 +++ tools/perf/util/rvtrace-decoder.c | 1039 +++++++++++++ tools/perf/util/rvtrace.h | 40 + tools/perf/util/symbol-elf.c | 4 + 34 files changed, 6320 insertions(+), 3 deletions(-) create mode 100644 drivers/hwtracing/coresight/rvtrace-atbbridge.c create mode 100644 drivers/hwtracing/coresight/rvtrace-core.c create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder-core.c create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder.h create mode 100644 drivers/hwtracing/coresight/rvtrace-funnel.c create mode 100644 drivers/hwtracing/coresight/rvtrace-funnel.h create mode 100644 drivers/hwtracing/coresight/rvtrace-timestamp.c create mode 100644 drivers/hwtracing/coresight/rvtrace-timestamp.h create mode 100644 include/linux/rvtrace.h create mode 100644 tools/arch/riscv/include/asm/insn.h create mode 100644 tools/perf/arch/riscv/util/auxtrace.c create mode 100644 tools/perf/arch/riscv/util/pmu.c create mode 100644 tools/perf/util/nexus-rv-decoder/Build create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.c create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.h create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-msg.h create mode 100644 tools/perf/util/rvtrace-decoder.c create mode 100644 tools/perf/util/rvtrace.h
From: liangzhen zhen.liang@spacemit.com
Enable CoreSight tracing support on RISC-V architecture by: - Adding RISC-V to Kconfig dependencies for CoreSight - Replacing ARM-specific memory barriers (isb, dmb) with RISC-V equivalents (local_flush_icache_all, __mb) - Removing ARM-specific header dependencies: perf/arm_pmu.h, asm/smp_plat.h - Adding PMU format attribute macros for cross-architecture support
This allows CoreSight tracing infrastructure to work on RISC-V systems while maintaining compatibility with existing ARM/ARM64 implementations.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/Kconfig | 2 ++ drivers/hwtracing/coresight/Kconfig | 2 +- drivers/hwtracing/coresight/coresight-core.c | 8 +++++++ .../hwtracing/coresight/coresight-etm-perf.c | 1 - .../hwtracing/coresight/coresight-etm-perf.h | 21 +++++++++++++++++++ .../hwtracing/coresight/coresight-platform.c | 1 - .../hwtracing/coresight/coresight-tmc-etf.c | 4 ++++ .../hwtracing/coresight/coresight-tmc-etr.c | 4 ++++ 8 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig index 911ee977103c..167ff172dd72 100644 --- a/drivers/hwtracing/Kconfig +++ b/drivers/hwtracing/Kconfig @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only menu "HW tracing support"
+source "drivers/hwtracing/coresight/Kconfig" + source "drivers/hwtracing/stm/Kconfig"
source "drivers/hwtracing/intel_th/Kconfig" diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 6a4239ebb582..2b1ebe3f614d 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -4,7 +4,7 @@ # menuconfig CORESIGHT tristate "CoreSight Tracing Support" - depends on ARM || ARM64 + depends on ARM || ARM64 || RISCV depends on OF || ACPI select ARM_AMBA select PERF_EVENTS diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index 80e26396ad0a..1ca202153cc4 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -141,7 +141,11 @@ static void coresight_set_self_claim_tag_unlocked(struct coresight_device *csdev { csdev_access_relaxed_write32(&csdev->access, CORESIGHT_CLAIM_SELF_HOSTED, CORESIGHT_CLAIMSET); +#if defined(__riscv) + local_flush_icache_all(); +#else isb(); +#endif }
void coresight_clear_self_claim_tag(struct csdev_access *csa) @@ -158,7 +162,11 @@ void coresight_clear_self_claim_tag_unlocked(struct csdev_access *csa) { csdev_access_relaxed_write32(csa, CORESIGHT_CLAIM_SELF_HOSTED, CORESIGHT_CLAIMCLR); +#if defined(__riscv) + local_flush_icache_all(); +#else isb(); +#endif } EXPORT_SYMBOL_GPL(coresight_clear_self_claim_tag_unlocked);
diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 72017dcc3b7f..70a6aaffbf9d 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -13,7 +13,6 @@ #include <linux/mm.h> #include <linux/init.h> #include <linux/perf_event.h> -#include <linux/perf/arm_pmu.h> #include <linux/percpu-defs.h> #include <linux/slab.h> #include <linux/stringhash.h> diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.h b/drivers/hwtracing/coresight/coresight-etm-perf.h index 24d929428633..e48c0ad46db1 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.h +++ b/drivers/hwtracing/coresight/coresight-etm-perf.h @@ -58,6 +58,27 @@ struct cscfg_config_desc; #define ATTR_CFG_FLD_cc_threshold_LO 0 #define ATTR_CFG_FLD_cc_threshold_HI 11
+#define __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ + (lo) == (hi) ? #cfg ":" #lo "\n" : #cfg ":" #lo "-" #hi + +#define _GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ + __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) + +#define GEN_PMU_FORMAT_ATTR(name) \ + PMU_FORMAT_ATTR(name, \ + _GEN_PMU_FORMAT_ATTR(ATTR_CFG_FLD_##name##_CFG, \ + ATTR_CFG_FLD_##name##_LO, \ + ATTR_CFG_FLD_##name##_HI)) + +#define _ATTR_CFG_GET_FLD(attr, cfg, lo, hi) \ + ((((attr)->cfg) >> lo) & GENMASK_ULL(hi - lo, 0)) + +#define ATTR_CFG_GET_FLD(attr, name) \ + _ATTR_CFG_GET_FLD(attr, \ + ATTR_CFG_FLD_##name##_CFG, \ + ATTR_CFG_FLD_##name##_LO, \ + ATTR_CFG_FLD_##name##_HI) + /** * struct etm_filter - single instruction range or start/stop configuration. * @start_addr: The address to start tracing on. diff --git a/drivers/hwtracing/coresight/coresight-platform.c b/drivers/hwtracing/coresight/coresight-platform.c index 0db64c5f4995..261ba6a75b86 100644 --- a/drivers/hwtracing/coresight/coresight-platform.c +++ b/drivers/hwtracing/coresight/coresight-platform.c @@ -14,7 +14,6 @@ #include <linux/amba/bus.h> #include <linux/coresight.h> #include <linux/cpumask.h> -#include <asm/smp_plat.h>
#include "coresight-priv.h"
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c index 8882b1c4cdc0..dc366f4a5ca8 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etf.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c @@ -661,7 +661,11 @@ static int tmc_panic_sync_etf(struct coresight_device *csdev) * Make sure all previous writes are ordered, * before we mark valid */ +#if defined(__riscv) + __mb(); +#else dmb(sy); +#endif mdata->valid = true; /* * Below order need to maintained, since crc of metadata diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 4dc1defe27a5..ac379d1751e6 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1883,7 +1883,11 @@ static int tmc_panic_sync_etr(struct coresight_device *csdev) * Make sure all previous writes are ordered, * before we mark valid */ +#if defined(__riscv) + __mb(); +#else dmb(sy); +#endif mdata->valid = true; /* * Below order need to maintained, since crc of metadata
From: liangzhen zhen.liang@spacemit.com
Implement some common driver interfaces RISC-V trace where RISC-V trace components are instantiated by a common platform driver and a separate RISC-V trace driver for each type of RISC-V trace component.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/coresight/Kconfig | 8 ++ drivers/hwtracing/coresight/Makefile | 2 + drivers/hwtracing/coresight/rvtrace-core.c | 135 +++++++++++++++++++++ include/linux/rvtrace.h | 116 ++++++++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 drivers/hwtracing/coresight/rvtrace-core.c create mode 100644 include/linux/rvtrace.h
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 2b1ebe3f614d..5adeaf78a080 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -280,4 +280,12 @@ config CORESIGHT_TNOC To compile this driver as a module, choose M here: the module will be called coresight-tnoc.
+config RVTRACE + bool "RISC-V Trace Support" + help + This enables support for the RISC-V trace drivers. drivers + (including Trace Encoder, Trace Funnel and ATB Bridge) are + dynamically aggregated with CoreSight trace infrastructure + at run time to form a complete trace path. + endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index ab16d06783a5..c21a9e25e148 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -57,3 +57,5 @@ obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o obj-$(CONFIG_CORESIGHT_CTCU) += coresight-ctcu.o coresight-ctcu-y := coresight-ctcu-core.o obj-$(CONFIG_CORESIGHT_KUNIT_TESTS) += coresight-kunit-tests.o +obj-$(CONFIG_RVTRACE) += rvtrace.o +rvtrace-y := rvtrace-core.o diff --git a/drivers/hwtracing/coresight/rvtrace-core.c b/drivers/hwtracing/coresight/rvtrace-core.c new file mode 100644 index 000000000000..c74f43869d8b --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-core.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/percpu.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/rvtrace.h> + +int rvtrace_poll_bit(struct rvtrace_component *comp, int offset, + int bit, int bitval) +{ + int i = RVTRACE_TIMEOUT_US; + u32 val; + + while (i--) { + val = readl_relaxed(comp->base + offset); + if (((val >> bit) & 0x1) == bitval) + break; + udelay(1); + } + + return (i < 0) ? -ETIMEDOUT : 0; +} +EXPORT_SYMBOL_GPL(rvtrace_poll_bit); + +int rvtrace_enable_component(struct rvtrace_component *comp) +{ + u32 val; + + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val |= BIT(RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT); + writel_relaxed(val, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + return rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT, 1); +} +EXPORT_SYMBOL_GPL(rvtrace_enable_component); + +int rvtrace_disable_component(struct rvtrace_component *comp) +{ + u32 val; + + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val &= ~BIT(RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT); + writel_relaxed(val, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + return rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT, 0); +} +EXPORT_SYMBOL_GPL(rvtrace_disable_component); + +int rvtrace_component_reset(struct rvtrace_component *comp) +{ + int ret; + + writel_relaxed(0, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + ret = rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 0); + if (ret) + return ret; + + writel_relaxed(RVTRACE_COMPONENT_CTRL_ACTIVE_MASK, + comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + return rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT, 1); +} +EXPORT_SYMBOL_GPL(rvtrace_component_reset); + +struct rvtrace_component *rvtrace_register_component(struct platform_device *pdev) +{ + int ret; + void __iomem *base; + struct device *dev = &pdev->dev; + struct rvtrace_component *comp; + struct resource *res; + struct device_node *node; + u32 impl, type, major, minor; + + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + goto err_out; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + ret = -EINVAL; + goto err_out; + } + comp->base = base; + + comp->cpu = -1; + for (int i = 0; ; i++) { + node = of_parse_phandle(dev->of_node, "cpus", i); + if (!node) + break; + + ret = of_cpu_node_to_id(node); + of_node_put(node); + if (ret >= 0 && cpu_online(ret)) { + comp->cpu = ret; + break; + } + } + + if (comp->cpu < 0) { + dev_err(dev, "No valid CPU found in 'cpus' property\n"); + ret = -EINVAL; + goto err_out; + } + + ret = rvtrace_component_reset(comp); + if (ret) + goto err_out; + comp->was_reset = true; + + impl = readl_relaxed(comp->base + RVTRACE_COMPONENT_IMPL_OFFSET); + type = (impl >> RVTRACE_COMPONENT_IMPL_TYPE_SHIFT) & + RVTRACE_COMPONENT_IMPL_TYPE_MASK; + major = (impl >> RVTRACE_COMPONENT_IMPL_VERMAJOR_SHIFT) & + RVTRACE_COMPONENT_IMPL_VERMAJOR_MASK; + minor = (impl >> RVTRACE_COMPONENT_IMPL_VERMINOR_SHIFT) & + RVTRACE_COMPONENT_IMPL_VERMINOR_MASK; + + comp->id.type = type; + comp->id.version = rvtrace_component_mkversion(major, minor); + + return comp; + +err_out: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(rvtrace_register_component); diff --git a/include/linux/rvtrace.h b/include/linux/rvtrace.h new file mode 100644 index 000000000000..e7028d82f8fd --- /dev/null +++ b/include/linux/rvtrace.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#ifndef __LINUX_RVTRACE_H__ +#define __LINUX_RVTRACE_H__ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/coresight.h> +#include <linux/types.h> + +/* Control register common across all RISC-V trace components */ +#define RVTRACE_COMPONENT_CTRL_OFFSET 0x000 +#define RVTRACE_COMPONENT_CTRL_ACTIVE_MASK 0x1 +#define RVTRACE_COMPONENT_CTRL_ACTIVE_SHIFT 0 +#define RVTRACE_COMPONENT_CTRL_ENABLE_MASK 0x1 +#define RVTRACE_COMPONENT_CTRL_ENABLE_SHIFT 1 +#define RVTRACE_COMPONENT_CTRL_EMPTY_SHIFT 3 + +/* Implementation register common across all RISC-V trace components */ +#define RVTRACE_COMPONENT_IMPL_OFFSET 0x004 +#define RVTRACE_COMPONENT_IMPL_VERMAJOR_MASK 0xf +#define RVTRACE_COMPONENT_IMPL_VERMAJOR_SHIFT 0 +#define RVTRACE_COMPONENT_IMPL_VERMINOR_MASK 0xf +#define RVTRACE_COMPONENT_IMPL_VERMINOR_SHIFT 4 +#define RVTRACE_COMPONENT_IMPL_TYPE_MASK 0xf +#define RVTRACE_COMPONENT_IMPL_TYPE_SHIFT 8 + +#define RVTRACE_TIMEOUT_US 100 + +/* Possible component types defined by the RISC-V Trace Control Interface */ +enum rvtrace_component_type { + RVTRACE_COMPONENT_TYPE_RESV0, + RVTRACE_COMPONENT_TYPE_ENCODER, /* 0x1 */ + RVTRACE_COMPONENT_TYPE_RESV2, + RVTRACE_COMPONENT_TYPE_RESV3, + RVTRACE_COMPONENT_TYPE_RESV4, + RVTRACE_COMPONENT_TYPE_RESV5, + RVTRACE_COMPONENT_TYPE_RESV6, + RVTRACE_COMPONENT_TYPE_RESV7, + RVTRACE_COMPONENT_TYPE_FUNNEL, /* 0x8 */ + RVTRACE_COMPONENT_TYPE_RAMSINK, /* 0x9 */ + RVTRACE_COMPONENT_TYPE_PIBSINK, /* 0xA */ + RVTRACE_COMPONENT_TYPE_RESV11, + RVTRACE_COMPONENT_TYPE_RESV12, + RVTRACE_COMPONENT_TYPE_RESV13, + RVTRACE_COMPONENT_TYPE_ATBBRIDGE, /* 0xE */ + RVTRACE_COMPONENT_TYPE_RESV15, + RVTRACE_COMPONENT_TYPE_MAX +}; + +/* Encoding/decoding macros for RISC-V trace component version */ +#define rvtrace_component_version_major(__version) \ + (((__version) >> 16) & 0xffff) +#define rvtrace_component_version_minor(__version) \ + ((__version) & 0xffff) +#define rvtrace_component_mkversion(__major, __minor) \ + ((((__major) & 0xffff) << 16) | ((__minor) & 0xffff)) + +/** + * struct rvtrace_component_id - Details to identify or match a RISC-V trace component + * @type: Type of the component + * @version: Version of the component + * @data: Data pointer for driver use + */ +struct rvtrace_component_id { + enum rvtrace_component_type type; + u32 version; + void *data; +}; + +/** + * struct rvtrace_component - Representation of a RISC-V trace component + * base: Memory mapped base address for the component + * id: Details to match the component + * dev: Device instance + * cpu: The cpu this component is affined to + * was_reset: Flag showing whether RISC-V trace driver was reset successfully + */ +struct rvtrace_component { + void __iomem *base; + struct rvtrace_component_id id; + struct device *dev; + int cpu; + bool was_reset; +}; + +struct component_enable_arg { + struct rvtrace_component *comp; + int rc; +}; + +struct rvtrace_component *rvtrace_register_component(struct platform_device *pdev); + +int rvtrace_poll_bit(struct rvtrace_component *comp, int offset, + int bit, int bitval); + +int rvtrace_enable_component(struct rvtrace_component *comp); +int rvtrace_disable_component(struct rvtrace_component *comp); +int rvtrace_component_reset(struct rvtrace_component *comp); + +static inline void *rvtrace_component_data(struct rvtrace_component *comp) +{ + return comp->id.data; +} + +static inline int rvtrace_comp_is_empty(struct rvtrace_component *comp) +{ + return rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_EMPTY_SHIFT, 1); +} + +#endif
From: liangzhen zhen.liang@spacemit.com
Add initial implementation of RISC-V trace encoder driver. The encoder is defined in the RISC-V Trace Control Interface specification.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/coresight/Kconfig | 12 + drivers/hwtracing/coresight/Makefile | 2 + .../coresight/rvtrace-encoder-core.c | 524 ++++++++++++++++++ .../coresight/rvtrace-encoder-sysfs.c | 313 +++++++++++ drivers/hwtracing/coresight/rvtrace-encoder.h | 143 +++++ 5 files changed, 994 insertions(+) create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder-core.c create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c create mode 100644 drivers/hwtracing/coresight/rvtrace-encoder.h
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 5adeaf78a080..3e46728f2482 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -288,4 +288,16 @@ config RVTRACE dynamically aggregated with CoreSight trace infrastructure at run time to form a complete trace path.
+config RVTRACE_ENCODER + tristate "RISCV Trace Encoder driver" + depends on RVTRACE + help + This driver provides support for the Trace Encoder module, tracing + the instructions that a processor is executing. This is primarily + useful for instruction level tracing. Depending on the implemented + version data tracing may also be available. + + To compile this driver as a module, choose M here: the module + will be called rvtrace-encoder. + endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index c21a9e25e148..9a526b1fb95a 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -59,3 +59,5 @@ coresight-ctcu-y := coresight-ctcu-core.o obj-$(CONFIG_CORESIGHT_KUNIT_TESTS) += coresight-kunit-tests.o obj-$(CONFIG_RVTRACE) += rvtrace.o rvtrace-y := rvtrace-core.o +obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o +rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o diff --git a/drivers/hwtracing/coresight/rvtrace-encoder-core.c b/drivers/hwtracing/coresight/rvtrace-encoder-core.c new file mode 100644 index 000000000000..149a3cd97602 --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-encoder-core.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/bitfield.h> +#include <linux/smp.h> +#include <linux/coresight.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/rvtrace.h> + +#include "rvtrace-encoder.h" +#include "coresight-etm-perf.h" + +static int boot_enable; +module_param_named(boot_enable, boot_enable, int, 0444); + +static struct rvtrace_component *rvtrace_cpu_encoder[NR_CPUS]; + +static int encoder_cpu_id(struct coresight_device *csdev) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + + return comp->cpu; +} + +static void encoder_set_config(struct rvtrace_component *comp) +{ + u32 val; + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct device *dev = &encoder_data->csdev->dev; + struct encoder_config *config = &encoder_data->config; + + /* Configure Trace Encoder features register */ + val = (FIELD_PREP(RVTRACE_ENCODER_INST_NO_ADDR_DIFF, config->inst_na_diff) | + FIELD_PREP(RVTRACE_ENCODER_INST_NO_TRAP_ADDR, config->inst_nt_addr) | + FIELD_PREP(RVTRACE_ENCODER_INST_EN_SEQUENTIAL_JUMP, config->seq_jump) | + FIELD_PREP(RVTRACE_ENCODER_INST_EN_IMPLICIT_RETURN, config->impl_ret) | + FIELD_PREP(RVTRACE_ENCODER_INST_EN_BRANCH_PREDICTION, config->branch_pred) | + FIELD_PREP(RVTRACE_ENCODER_INST_IMPLICIT_RETURN_MODE, config->impl_ret_mode) | + FIELD_PREP(RVTRACE_ENCODER_INST_EN_REPEQTED_HISTORT, config->rep_hist) | + FIELD_PREP(RVTRACE_ENCODER_INST_EN_ALL_JUMPS, config->all_jumps) | + FIELD_PREP(RVTRACE_ENCODER_INST_EXTEND_ADDR_MSB, config->ext_msb) | + FIELD_PREP(RVTRACE_ENCODER_SRCID, config->srcid) | + FIELD_PREP(RVTRACE_ENCODER_SRCBITS, config->srcb)); + + writel_relaxed(val, comp->base + RVTRACE_ENCODER_INST_FTRS_OFFSET); + + /* Check Trace Encoder features register WARL bits could be set */ + val = readl_relaxed(comp->base + RVTRACE_ENCODER_INST_FTRS_OFFSET); + if (BMVAL(val, 0, 0) != config->inst_na_diff) { + dev_warn(dev, "trTeInstNoAddrDiff %#x is not supported\n", + config->inst_na_diff); + config->inst_na_diff = BMVAL(val, 0, 0); + } + + if (BMVAL(val, 1, 1) != config->inst_nt_addr) { + dev_warn(dev, "trTeInstNoTrapAddr %#x is not supported\n", + config->inst_nt_addr); + config->inst_nt_addr = BMVAL(val, 1, 1); + } + + if (BMVAL(val, 2, 2) != config->seq_jump) { + dev_warn(dev, "trTeInstEnSequentialJump %#x is not supported\n", + config->seq_jump); + config->seq_jump = BMVAL(val, 2, 2); + } + + if (BMVAL(val, 3, 3) != config->impl_ret) { + dev_warn(dev, "trTeInstEnImplicitReturn %#x is not supported\n", + config->impl_ret); + config->impl_ret = BMVAL(val, 3, 3); + } + + if (BMVAL(val, 4, 4) != config->branch_pred) { + dev_warn(dev, "trTeInstEnBranchPrediction %#x is not supported\n", + config->branch_pred); + config->branch_pred = BMVAL(val, 4, 4); + } + + if (BMVAL(val, 5, 5) != config->jump_target_cache) { + dev_warn(dev, "trTeInstEnJumpTargetCache %#x is not supported\n", + config->jump_target_cache); + config->jump_target_cache = BMVAL(val, 5, 5); + } + + if (BMVAL(val, 6, 7) != config->impl_ret_mode) { + dev_warn(dev, "trTeInstImplicitReturnMode %#x is not supported\n", + config->impl_ret_mode); + config->impl_ret_mode = BMVAL(val, 6, 7); + } + + if (BMVAL(val, 8, 8) != config->rep_hist) { + dev_warn(dev, "trTeInstEnRepeatedHistory %#x is not supported\n", + config->rep_hist); + config->rep_hist = BMVAL(val, 8, 8); + } + + if (BMVAL(val, 9, 9) != config->all_jumps) { + dev_warn(dev, "trTeInstEnAllJumps %#x is not supported\n", + config->all_jumps); + config->all_jumps = BMVAL(val, 9, 9); + } + + if (BMVAL(val, 10, 10) != config->ext_msb) { + dev_warn(dev, " trTeInstExtendAddrMSB %#x is not supported\n", + config->ext_msb); + config->ext_msb = BMVAL(val, 10, 10); + } + + if (BMVAL(val, 16, 27) != config->srcid) { + dev_warn(dev, "trTeSrcID %#x is not supported\n", + config->srcid); + config->srcid = BMVAL(val, 16, 27); + } + + if (BMVAL(val, 28, 31) != config->srcb) { + dev_warn(dev, "trTeSrcBits %#x is not supported\n", + config->srcb); + config->srcb = BMVAL(val, 28, 31); + } + + /* Configure Trace Encoder control register */ + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val |= (FIELD_PREP(RVTRACE_ENCODER_INSTMODE, ENCODER_INSTMODE_HTM) | + FIELD_PREP(RVTRACE_ENCODER_CONTEXT, config->context) | + FIELD_PREP(RVTRACE_ENCODER_INSTTRIGEN, config->inst_trigger) | + FIELD_PREP(RVTRACE_ENCODER_INST_STALL_EN, config->inst_stall) | + FIELD_PREP(RVTRACE_ENCODER_INHBSRC, config->inhb_src) | + FIELD_PREP(RVTRACE_ENCODER_INSTSYNC_MODE, config->inst_syncmode) | + FIELD_PREP(RVTRACE_ENCODER_INSTSYNC_MAX, config->inst_syncmax)); + + + writel_relaxed(val, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + + /* Check Trace Encoder control register WARL bits could be set */ + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + + if (BMVAL(val, 9, 9) != config->context) { + dev_warn(dev, "trTeContext %#x is not supported\n", + config->context); + config->context = BMVAL(val, 9, 9); + } + + if (BMVAL(val, 11, 11) != config->inst_trigger) { + dev_warn(dev, "trTeInstTrigEnable %#x is not supported\n", + config->inst_trigger); + config->inst_trigger = BMVAL(val, 11, 11); + } + + if (BMVAL(val, 13, 13) != config->inst_stall) { + dev_warn(dev, "trTeInstStallEna %#x is not supported\n", + config->inst_stall); + config->inst_stall = BMVAL(val, 13, 13); + } + + if (BMVAL(val, 15, 15) != config->inhb_src) { + dev_warn(dev, "trTeInhibitSrc %#x is not supported\n", + config->inhb_src); + config->inhb_src = BMVAL(val, 15, 15); + } + + if (BMVAL(val, 20, 23) != config->inst_syncmax) { + dev_warn(dev, "trTeInstSyncMax %#x is not supported\n", + config->inst_syncmax); + config->inst_syncmax = BMVAL(val, 20, 23); + } +} + +static int encoder_enable_hw(struct rvtrace_component *comp) +{ + u32 val; + int ret; + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (!comp->was_reset) { + ret = rvtrace_component_reset(comp); + if (ret) + goto done; + } + + encoder_set_config(comp); + + ret = rvtrace_enable_component(comp); + if (ret) + goto done; + + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val |= RVTRACE_ENCODER_ITRACE; + writel_relaxed(val, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + ret = rvtrace_poll_bit(comp, RVTRACE_COMPONENT_CTRL_OFFSET, + RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT, 1); + +done: + dev_dbg(&encoder_data->csdev->dev, "cpu: %d enable smp call done: %d\n", + comp->cpu, ret); + return ret; +} + +static void encoder_enable_hw_smp_call(void *info) +{ + struct component_enable_arg *arg = info; + + if (WARN_ON(!arg)) + return; + arg->rc = encoder_enable_hw(arg->comp); +} + +static int encoder_enable_perf(struct coresight_device *csdev) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + + if (WARN_ON_ONCE(comp->cpu != smp_processor_id())) + return -EINVAL; + + return encoder_enable_hw(comp); +} + +static int encoder_enable_sysfs(struct coresight_device *csdev) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct component_enable_arg arg = { }; + int ret; + + spin_lock(&encoder_data->spinlock); + + /* + * Executing encoder_enable_hw on the cpu whose trace encoder is being + * enabled ensures that register writes occur when cpu is powered. + */ + arg.comp = comp; + ret = smp_call_function_single(comp->cpu, + encoder_enable_hw_smp_call, &arg, 1); + if (!ret) + ret = arg.rc; + if (!ret) + encoder_data->sticky_enable = true; + + spin_unlock(&encoder_data->spinlock); + + if (!ret) + dev_dbg(&csdev->dev, "Trace Encoder tracing enabled\n"); + return ret; +} + +static int encoder_enable(struct coresight_device *csdev, struct perf_event *event, + enum cs_mode mode, __maybe_unused struct coresight_path *path) +{ + int ret; + + if (!coresight_take_mode(csdev, mode)) { + /* Someone is already using the tracer */ + return -EBUSY; + } + + switch (mode) { + case CS_MODE_SYSFS: + ret = encoder_enable_sysfs(csdev); + break; + case CS_MODE_PERF: + ret = encoder_enable_perf(csdev); + break; + default: + ret = -EINVAL; + } + + /* The tracer didn't start */ + if (ret) + coresight_set_mode(csdev, CS_MODE_DISABLED); + + return ret; +} + +static void encoder_disable_hw(struct rvtrace_component *comp) +{ + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (rvtrace_disable_component(comp)) + dev_err(&encoder_data->csdev->dev, + "timeout while waiting for Trace Encoder become disabled\n"); + + if (rvtrace_comp_is_empty(comp)) + dev_err(&encoder_data->csdev->dev, + "timeout while waiting for all generated trace have been emitted\n"); + + dev_dbg(&encoder_data->csdev->dev, "cpu: %d disable smp call done\n", comp->cpu); +} + +static void encoder_disable_sysfs_smp_call(void *info) +{ + struct rvtrace_component *comp = info; + + encoder_disable_hw(comp); +} + +static void encoder_disable_sysfs(struct coresight_device *csdev) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + /* + * Taking hotplug lock here protects from clocks getting disabled + * with tracing being left on (crash scenario) if user disable occurs + * after cpu online mask indicates the cpu is offline but before the + * DYING hotplug callback is serviced by the trace encoder driver. + */ + cpus_read_lock(); + spin_lock(&encoder_data->spinlock); + + /* + * Executing encoder_disable_hw on the cpu whose trace encoder is being + * disabled ensures that register writes occur when cpu is powered. + */ + smp_call_function_single(comp->cpu, encoder_disable_sysfs_smp_call, comp, 1); + + spin_unlock(&encoder_data->spinlock); + cpus_read_unlock(); + + dev_dbg(&csdev->dev, "Trace Encoder tracing disabled\n"); +} + +static void encoder_disable_perf(struct coresight_device *csdev) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + + if (WARN_ON_ONCE(comp->cpu != smp_processor_id())) + return; + + encoder_disable_hw(comp); +} + +static void encoder_disable(struct coresight_device *csdev, + struct perf_event *event) +{ + enum cs_mode mode; + + /* + * For as long as the tracer isn't disabled another entity can't + * change its status. As such we can read the status here without + * fearing it will change under us. + */ + mode = coresight_get_mode(csdev); + + switch (mode) { + case CS_MODE_DISABLED: + break; + case CS_MODE_SYSFS: + encoder_disable_sysfs(csdev); + break; + case CS_MODE_PERF: + encoder_disable_perf(csdev); + break; + default: + WARN_ON_ONCE(mode); + return; + } + + if (mode) + coresight_set_mode(csdev, CS_MODE_DISABLED); +} + +static const struct coresight_ops_source encoder_source_ops = { + .cpu_id = encoder_cpu_id, + .enable = encoder_enable, + .disable = encoder_disable, +}; + +static const struct coresight_ops encoder_cs_ops = { + .source_ops = &encoder_source_ops, +}; + +void encoder_set_default(struct rvtrace_component *comp) +{ + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (WARN_ON_ONCE(!encoder_data)) + return; + + struct encoder_config *config = &encoder_data->config; + + /* Enable sending trace messages/fields with scontext/mcontext values + * and/or privilege levels + */ + config->context = true; + + /* Allows trTeInstTracing to be set or cleared by Trace-on and Trace- + * off signals generated by the corresponding trigger module. + */ + config->inst_trigger = true; + + /* Enable periodic instruction trace synchronization */ + config->inst_syncmode = ENCODER_SYNCMODE_CLOCK; + config->inst_syncmax = 0x6; + + /* trace source ID*/ + config->srcid = encoder_cpu_id(encoder_data->csdev); + config->srcb = 0xc; +} + +static int encoder_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct encoder_data *encoder_data; + struct rvtrace_component *comp; + struct coresight_desc desc = { 0 }; + + comp = rvtrace_register_component(pdev); + if (IS_ERR(comp)) + return PTR_ERR(comp); + + encoder_data = devm_kzalloc(dev, sizeof(*encoder_data), GFP_KERNEL); + if (!encoder_data) + return -ENOMEM; + + spin_lock_init(&encoder_data->spinlock); + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + pdev->dev.platform_data = pdata; + + platform_set_drvdata(pdev, comp); + + desc.name = devm_kasprintf(dev, GFP_KERNEL, "encoder%d", comp->cpu); + if (!desc.name) + return -ENOMEM; + + desc.access = CSDEV_ACCESS_IOMEM(comp->base); + desc.type = CORESIGHT_DEV_TYPE_SOURCE; + desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; + desc.ops = &encoder_cs_ops; + desc.pdata = pdata; + desc.dev = dev; + desc.groups = trace_encoder_groups; + encoder_data->csdev = coresight_register(&desc); + if (IS_ERR(encoder_data->csdev)) + return PTR_ERR(encoder_data->csdev); + + ret = etm_perf_symlink(encoder_data->csdev, true); + if (ret) { + coresight_unregister(encoder_data->csdev); + return ret; + } + + comp->id.data = encoder_data; + + rvtrace_cpu_encoder[comp->cpu] = comp; + + encoder_set_default(comp); + + dev_info(dev, "CPU%d: Trace Encoder initialized\n", comp->cpu); + + if (boot_enable) { + coresight_enable_sysfs(encoder_data->csdev); + encoder_data->boot_enable = true; + } + + return 0; +} + +static void clear_encodata(void *info) +{ + int cpu = *(int *)info; + + rvtrace_cpu_encoder[cpu]->id.data = NULL; + rvtrace_cpu_encoder[cpu] = NULL; +} + +static void encoder_remove(struct platform_device *pdev) +{ + struct rvtrace_component *comp = platform_get_drvdata(pdev); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + etm_perf_symlink(encoder_data->csdev, false); + + /* + * Taking hotplug lock here to avoid racing between encoder_remove and + * CPU hotplug call backs. + */ + cpus_read_lock(); + /* + * The readers for encodata[] are CPU hotplug call backs + * and PM notification call backs. Change encodata[i] on + * CPU i ensures these call backs has consistent view + * inside one call back function. + */ + if (smp_call_function_single(comp->cpu, clear_encodata, &comp->cpu, 1)) { + rvtrace_cpu_encoder[comp->cpu]->id.data = NULL; + rvtrace_cpu_encoder[comp->cpu] = NULL; + } + + cpus_read_unlock(); + + coresight_unregister(encoder_data->csdev); +} + +static const struct of_device_id encoder_match[] = { + {.compatible = "riscv,trace-encoder"}, + {}, +}; + +static struct platform_driver encoder_driver = { + .probe = encoder_probe, + .remove = encoder_remove, + .driver = { + .name = "trace-encoder", + .of_match_table = encoder_match, + }, +}; + +module_platform_driver(encoder_driver); + +MODULE_DESCRIPTION("RISC-V Trace Encoder Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c b/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c new file mode 100644 index 000000000000..c89af4cb591a --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/pm_runtime.h> +#include <linux/sysfs.h> +#include <linux/rvtrace.h> +#include "rvtrace-encoder.h" +#include "coresight-priv.h" + +static ssize_t cpu_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + + val = comp->cpu; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +static DEVICE_ATTR_RO(cpu); + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + spin_lock(&encoder_data->spinlock); + + if (val) { + encoder_set_default(comp); + if (rvtrace_component_reset(comp)) { + comp->was_reset = false; + spin_unlock(&encoder_data->spinlock); + return -EINVAL; + } + } + + spin_unlock(&encoder_data->spinlock); + + return size; +} +static DEVICE_ATTR_WO(reset); + +static struct attribute *trace_encoder_attrs[] = { + &dev_attr_cpu.attr, + &dev_attr_reset.attr, + NULL, +}; + +static struct attribute *trace_encoder_mgmt_attrs[] = { + coresight_simple_reg32(control, RVTRACE_COMPONENT_CTRL_OFFSET), + coresight_simple_reg32(impl, RVTRACE_COMPONENT_IMPL_OFFSET), + coresight_simple_reg32(features, RVTRACE_ENCODER_INST_FTRS_OFFSET), + NULL, +}; + +#define encoder_simple_rw(name) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + unsigned long val; \ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); \ + struct encoder_data *encoder_data = rvtrace_component_data(comp); \ + struct encoder_config *config = &encoder_data->config; \ + \ + val = config->name; \ + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); \ +} \ + \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + unsigned long val; \ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); \ + struct encoder_data *encoder_data = rvtrace_component_data(comp); \ + struct encoder_config *config = &encoder_data->config; \ + \ + if (kstrtoul(buf, 16, &val)) \ + return -EINVAL; \ + \ + spin_lock(&encoder_data->spinlock); \ + config->name = val; \ + spin_unlock(&encoder_data->spinlock); \ + \ + return size; \ +} \ +static DEVICE_ATTR_RW(name) + +static const char *const instmodes_str[] = { + [ENCODER_INSTMODE_OFF] = "off", + [ENCODER_INSTMODE_RESV1] = "resv1", + [ENCODER_INSTMODE_BTM] = "btm", + [ENCODER_INSTMODE_RESV4] = "resv4", + [ENCODER_INSTMODE_RESV5] = "resv5", + [ENCODER_INSTMODE_HTM] = "htm", + [ENCODER_INSTMODE_RESV7] = "resv7", +}; + +static ssize_t instmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val = BMVAL(val, 4, 6); + + return sysfs_emit(buf, "%s\n", instmodes_str[val]); +} +static DEVICE_ATTR_RO(instmode); + +static const char *const inst_syncmodes_str[] = { + [ENCODER_SYNCMODE_OFF] = "off", + [ENCODER_SYNCMODE_MESSAGES] = "messages", + [ENCODER_SYNCMODE_CLOCK] = "clock", + [ENCODER_SYNCMODE_INSTRUCTION] = "instruction" +}; + +static ssize_t inst_syncmodes_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s %s %s %s\n", + inst_syncmodes_str[ENCODER_SYNCMODE_OFF], + inst_syncmodes_str[ENCODER_SYNCMODE_MESSAGES], + inst_syncmodes_str[ENCODER_SYNCMODE_CLOCK], + inst_syncmodes_str[ENCODER_SYNCMODE_INSTRUCTION]); +} +static DEVICE_ATTR_RO(inst_syncmodes_available); + +static ssize_t inst_syncmode_preferred_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct encoder_config *config = &encoder_data->config; + + return sysfs_emit(buf, "%s\n", inst_syncmodes_str[config->inst_syncmode]); +} + +static ssize_t inst_syncmode_preferred_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct encoder_config *config = &encoder_data->config; + + if (sysfs_streq(buf, inst_syncmodes_str[ENCODER_SYNCMODE_OFF])) + config->inst_syncmode = ENCODER_SYNCMODE_OFF; + else if (sysfs_streq(buf, inst_syncmodes_str[ENCODER_SYNCMODE_MESSAGES])) + config->inst_syncmode = ENCODER_SYNCMODE_MESSAGES; + else if (sysfs_streq(buf, inst_syncmodes_str[ENCODER_SYNCMODE_CLOCK])) + config->inst_syncmode = ENCODER_SYNCMODE_CLOCK; + else if (sysfs_streq(buf, inst_syncmodes_str[ENCODER_SYNCMODE_INSTRUCTION])) + config->inst_syncmode = ENCODER_SYNCMODE_INSTRUCTION; + else + return -EINVAL; + + return size; +} +static DEVICE_ATTR_RW(inst_syncmode_preferred); + +static ssize_t format_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + val = BMVAL(val, 24, 26); + + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +static DEVICE_ATTR_RO(format); + +encoder_simple_rw(context); +encoder_simple_rw(inst_trigger); +encoder_simple_rw(inst_stall); +encoder_simple_rw(inhb_src); +encoder_simple_rw(inst_syncmax); + +static struct attribute *trace_encoder_control_attrs[] = { + &dev_attr_instmode.attr, + &dev_attr_context.attr, + &dev_attr_inst_trigger.attr, + &dev_attr_inst_stall.attr, + &dev_attr_inhb_src.attr, + &dev_attr_inst_syncmodes_available.attr, + &dev_attr_inst_syncmode_preferred.attr, + &dev_attr_inst_syncmax.attr, + &dev_attr_format.attr, + NULL, +}; + +static const char *const inst_implicit_return_modes_str[] = { + [ENCODER_IMPLICITRETURNMODE_NOT_SUPPORTED] = "off", + [ENCODER_IMPLICITRETURNMODE_SIMPLE_COUNTING] = "simple", + [ENCODER_IMPLICITRETURNMODE_PARTIAL_ADDRESS] = "partial", + [ENCODER_IMPLICITRETURNMODE_FULL_ADDRESS] = "full" +}; + +static ssize_t inst_implicit_return_modes_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s %s %s %s\n", + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_NOT_SUPPORTED], + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_SIMPLE_COUNTING], + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_PARTIAL_ADDRESS], + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_FULL_ADDRESS]); +} +static DEVICE_ATTR_RO(inst_implicit_return_modes_available); + + +static ssize_t inst_implicit_return_mode_preferred_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct encoder_config *config = &encoder_data->config; + + return sysfs_emit(buf, "%s\n", inst_implicit_return_modes_str[config->impl_ret_mode]); +} + +static ssize_t inst_implicit_return_mode_preferred_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + struct encoder_config *config = &encoder_data->config; + + if (sysfs_streq(buf, + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_NOT_SUPPORTED])) { + config->impl_ret_mode = ENCODER_IMPLICITRETURNMODE_NOT_SUPPORTED; + } else if (sysfs_streq(buf, + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_SIMPLE_COUNTING])) { + config->impl_ret_mode = ENCODER_IMPLICITRETURNMODE_SIMPLE_COUNTING; + } else if (sysfs_streq(buf, + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_PARTIAL_ADDRESS])) { + config->impl_ret_mode = ENCODER_IMPLICITRETURNMODE_PARTIAL_ADDRESS; + } else if (sysfs_streq(buf, + inst_implicit_return_modes_str[ENCODER_IMPLICITRETURNMODE_FULL_ADDRESS])) { + config->impl_ret_mode = ENCODER_IMPLICITRETURNMODE_FULL_ADDRESS; + } else { + return -EINVAL; + } + return size; +} +static DEVICE_ATTR_RW(inst_implicit_return_mode_preferred); + +encoder_simple_rw(inst_na_diff); +encoder_simple_rw(inst_nt_addr); +encoder_simple_rw(seq_jump); +encoder_simple_rw(impl_ret); +encoder_simple_rw(branch_pred); +encoder_simple_rw(jump_target_cache); +encoder_simple_rw(rep_hist); +encoder_simple_rw(all_jumps); +encoder_simple_rw(ext_msb); +encoder_simple_rw(srcid); +encoder_simple_rw(srcb); + +static struct attribute *trace_encoder_features_attrs[] = { + &dev_attr_inst_na_diff.attr, + &dev_attr_inst_nt_addr.attr, + &dev_attr_seq_jump.attr, + &dev_attr_impl_ret.attr, + &dev_attr_branch_pred.attr, + &dev_attr_jump_target_cache.attr, + &dev_attr_inst_implicit_return_modes_available.attr, + &dev_attr_inst_implicit_return_mode_preferred.attr, + &dev_attr_rep_hist.attr, + &dev_attr_all_jumps.attr, + &dev_attr_ext_msb.attr, + &dev_attr_srcid.attr, + &dev_attr_srcb.attr, + NULL, +}; + +static const struct attribute_group trace_encoder_group = { + .attrs = trace_encoder_attrs, +}; + +static const struct attribute_group trace_encoder_mgmt_group = { + .attrs = trace_encoder_mgmt_attrs, + .name = "mgmt", +}; + +static const struct attribute_group trace_encoder_control_group = { + .attrs = trace_encoder_control_attrs, + .name = "control", +}; + +static const struct attribute_group trace_encoder_features_group = { + .attrs = trace_encoder_features_attrs, + .name = "features", +}; + +const struct attribute_group *trace_encoder_groups[] = { + &trace_encoder_group, + &trace_encoder_mgmt_group, + &trace_encoder_control_group, + &trace_encoder_features_group, + NULL, +}; diff --git a/drivers/hwtracing/coresight/rvtrace-encoder.h b/drivers/hwtracing/coresight/rvtrace-encoder.h new file mode 100644 index 000000000000..44f2066ad869 --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-encoder.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#ifndef _RVTRACE_ENCODER_H +#define _RVTRACE_ENCODER_H + +#include <asm/local.h> +#include <linux/spinlock.h> +#include "coresight-priv.h" + +/* Trace Encoder Control Register */ +#define RVTRACE_ENCODER_ITRACE BIT(2) +#define RVTRACE_ENCODER_INSTMODE GENMASK(6, 4) +#define RVTRACE_ENCODER_CONTEXT BIT(9) +#define RVTRACE_ENCODER_INSTTRIGEN BIT(11) +#define RVTRACE_ENCODER_INST_STALL_OR_OVERFLOW BIT(12) +#define RVTRACE_ENCODER_INST_STALL_EN BIT(13) +#define RVTRACE_ENCODER_INHBSRC BIT(15) +#define RVTRACE_ENCODER_INSTSYNC_MODE GENMASK(17, 16) +#define RVTRACE_ENCODER_INSTSYNC_MAX GENMASK(23, 20) + +#define RVTRACE_COMPONENT_CTRL_ITRACE_SHIFT 2 + +/* Trace Encoder Implementation Register */ + +/* Trace Instruction Features Register */ +#define RVTRACE_ENCODER_INST_FTRS_OFFSET 0x008 +#define RVTRACE_ENCODER_INST_NO_ADDR_DIFF BIT(0) +#define RVTRACE_ENCODER_INST_NO_TRAP_ADDR BIT(1) +#define RVTRACE_ENCODER_INST_EN_SEQUENTIAL_JUMP BIT(2) +#define RVTRACE_ENCODER_INST_EN_IMPLICIT_RETURN BIT(3) +#define RVTRACE_ENCODER_INST_EN_BRANCH_PREDICTION BIT(4) +#define RVTRACE_ENCODER_INST_JUMP_TARGET_CACHE BIT(5) +#define RVTRACE_ENCODER_INST_IMPLICIT_RETURN_MODE GENMASK(7, 6) +#define RVTRACE_ENCODER_INST_EN_REPEQTED_HISTORT BIT(8) +#define RVTRACE_ENCODER_INST_EN_ALL_JUMPS BIT(9) +#define RVTRACE_ENCODER_INST_EXTEND_ADDR_MSB BIT(10) +#define RVTRACE_ENCODER_SRCID GENMASK(27, 16) +#define RVTRACE_ENCODER_SRCBITS GENMASK(31, 28) + +enum encoder_format { + ENCODER_FORMAT_ETRACE, + ENCODER_FORMAT_NTRACE, + ENCODER_FORMAT_UNKNOWN = 7 +}; + +enum encoder_instmode { + ENCODER_INSTMODE_OFF, + ENCODER_INSTMODE_RESV1, + ENCODER_INSTMODE_RESV2, + ENCODER_INSTMODE_BTM, + ENCODER_INSTMODE_RESV4, + ENCODER_INSTMODE_RESV5, + ENCODER_INSTMODE_HTM, + ENCODER_INSTMODE_RESV7 +}; + +enum encoder_syncmode { + ENCODER_SYNCMODE_OFF, + ENCODER_SYNCMODE_MESSAGES, + ENCODER_SYNCMODE_CLOCK, + ENCODER_SYNCMODE_INSTRUCTION +}; + +enum encoder_implicit_return_mode { + ENCODER_IMPLICITRETURNMODE_NOT_SUPPORTED, + ENCODER_IMPLICITRETURNMODE_SIMPLE_COUNTING, + ENCODER_IMPLICITRETURNMODE_PARTIAL_ADDRESS, + ENCODER_IMPLICITRETURNMODE_FULL_ADDRESS +}; + +/** + * struct encoder_config - configuration information related to an trace encoder + * @context: Controls sending trace messages/fields with scontext/mcontext + * values and/or privilege levels. + * @inst_trig: Controls trTeInstTracing to be set or cleared by Trace-on and + * Trace-off signals generated by the corresponding trigger module. + * @inst_stall: Controls whether hart is stalled until TE can send a message. + * @inhb_src: Controls whether include source field in trace messages/packets. + * @inst_syncmode: Periodic instruction trace synchronization message/packet + * generation mechanism. + * @inst_syncmax: The maximum interval between instruction trace synchronization + * messages/packets. + * @trc_fmt: Trace recording/protocol format. + * @inst_na_diff: Controls whether trace messages/packets always carry a full address. + * @inst_nt_addr: Controls whether include trap handler address in trap messages/packets. + * @seq_jump: Controls the sequential jump optimization. + * @impl_ret: Controls the implicit return optimization. + * @branch_pred: Branch Predictor based compression. + * @jump_target_cache: Jump Target Cache based compression. + * @impl_ret_mode: Controls how the decoder is handling stack of return addresses. + * @rep_hist: Controls the repeated history optimization. + * @all_jumps: Controls the emitting of trace message or add history/map bit for + * direct unconditional/inferable control flow changes. + * @ext_msb: Controls the extended handing of MSB address bits. + * @srcid: Trace source ID assigned to this trace encoder. + * @srcb: The number of bits in the trace source field, unless disabled by + * inhibit_src. + */ +struct encoder_config { + bool context; + bool inst_trigger; + bool inst_stall; + bool inhb_src; + u8 inst_syncmode; + u8 inst_syncmax; + bool inst_na_diff; + bool inst_nt_addr; + bool seq_jump; + bool impl_ret; + bool branch_pred; + bool jump_target_cache; + u8 impl_ret_mode; + bool rep_hist; + bool all_jumps; + bool ext_msb; + u16 srcid; + u8 srcb; +}; + +/** + * struct encoder_drvdata - specifics associated to an Trace Encoder component + * @csdev: Component vitals needed by the framework. + * @spinlock: Only one at a time pls. + * @sticky_enable: True if trace encoder base configuration has been done. + * @boot_enable: True if we should start tracing at boot time. + * @config: Structure holding configuration parameters. + */ + +struct encoder_data { + struct coresight_device *csdev; + spinlock_t spinlock; + bool sticky_enable; + bool boot_enable; + struct encoder_config config; +}; + +extern const struct attribute_group *trace_encoder_groups[]; +void encoder_set_default(struct rvtrace_component *comp); + +#endif
From: liangzhen zhen.liang@spacemit.com
Add initial implementation of RISC-V trace funnel driver. The funnel is defined in the RISC-V Trace Control Interface specification.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/coresight/Kconfig | 12 + drivers/hwtracing/coresight/Makefile | 1 + drivers/hwtracing/coresight/rvtrace-funnel.c | 244 +++++++++++++++++++ drivers/hwtracing/coresight/rvtrace-funnel.h | 31 +++ 4 files changed, 288 insertions(+) create mode 100644 drivers/hwtracing/coresight/rvtrace-funnel.c create mode 100644 drivers/hwtracing/coresight/rvtrace-funnel.h
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 3e46728f2482..8381a84e2699 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -288,9 +288,21 @@ config RVTRACE dynamically aggregated with CoreSight trace infrastructure at run time to form a complete trace path.
+config RVTRACE_FUNNEL + tristate "RISCV Trace Funnel driver" + depends on RVTRACE + help + his driver provides support for the Trace Funnel driver. The Funnel + aggregates the trace from each of its inputs and sends the combined + trace stream to its designated Trace Sink or ATB Bridge. + + To compile this driver as a module, choose M here: the module + will be called rvtrace-funnel. + config RVTRACE_ENCODER tristate "RISCV Trace Encoder driver" depends on RVTRACE + select RVTRACE_FUNNEL help This driver provides support for the Trace Encoder module, tracing the instructions that a processor is executing. This is primarily diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 9a526b1fb95a..409a0abf0642 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -59,5 +59,6 @@ coresight-ctcu-y := coresight-ctcu-core.o obj-$(CONFIG_CORESIGHT_KUNIT_TESTS) += coresight-kunit-tests.o obj-$(CONFIG_RVTRACE) += rvtrace.o rvtrace-y := rvtrace-core.o +obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o diff --git a/drivers/hwtracing/coresight/rvtrace-funnel.c b/drivers/hwtracing/coresight/rvtrace-funnel.c new file mode 100644 index 000000000000..0dc7799a64ac --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-funnel.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/coresight.h> +#include <linux/platform_device.h> +#include <linux/rvtrace.h> + +#include "coresight-priv.h" +#include "rvtrace-funnel.h" + +DEFINE_CORESIGHT_DEVLIST(funnel_devs, "rvtrace_funnel"); + +static int funnel_enable_hw(struct rvtrace_component *comp, int port) +{ + u32 disinput; + int ret = 0; + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + if (!comp->was_reset) { + ret = rvtrace_component_reset(comp); + if (ret) + goto done; + } + + disinput = readl_relaxed(comp->base + RVTRACE_FUNNEL_DISINPUT_OFFSET); + disinput &= ~(1 << port); + writel_relaxed(disinput, comp->base + RVTRACE_FUNNEL_DISINPUT_OFFSET); + ret = rvtrace_poll_bit(comp, RVTRACE_FUNNEL_DISINPUT_OFFSET, port, 0); + if (ret) + goto done; + + if (!funnel_data->was_enabled) { + ret = rvtrace_enable_component(comp); + if (ret) + goto done; + } + +done: + return ret; +} + +static int funnel_enable(struct coresight_device *csdev, + struct coresight_connection *in, + struct coresight_connection *out) +{ + int ret = 0; + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + unsigned long flags; + bool first_enable = false; + + spin_lock_irqsave(&funnel_data->spinlock, flags); + if (in->dest_refcnt == 0) { + ret = funnel_enable_hw(comp, in->dest_port); + if (!ret) + first_enable = true; + } + if (!ret) { + in->dest_refcnt++; + funnel_data->input_refcnt++; + } + + if (first_enable) + dev_dbg(&csdev->dev, "Trace funnel inport %d enabled\n", + in->dest_port); + + spin_unlock_irqrestore(&funnel_data->spinlock, flags); + + return ret; +} + +static void funnel_disable_hw(struct rvtrace_component *comp, int inport) +{ + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + if (--funnel_data->input_refcnt != 0) + return; + + writel_relaxed(RVTRACE_FUNNEL_DISINPUT_MASK, comp->base + RVTRACE_FUNNEL_DISINPUT_OFFSET); + + if (rvtrace_disable_component(comp)) + dev_err(&funnel_data->csdev->dev, + "timeout while waiting for Trace Funnel to be disabled\n"); + + if (rvtrace_comp_is_empty(comp)) + dev_err(&funnel_data->csdev->dev, + "timeout while waiting for Trace Funnel internal buffers become empty\n"); +} + +static void funnel_disable(struct coresight_device *csdev, + struct coresight_connection *in, + struct coresight_connection *out) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + unsigned long flags; + bool last_disable = false; + + spin_lock_irqsave(&funnel_data->spinlock, flags); + if (--in->dest_refcnt == 0) { + funnel_disable_hw(comp, in->dest_port); + last_disable = true; + } + spin_unlock_irqrestore(&funnel_data->spinlock, flags); + + if (last_disable) + dev_dbg(&csdev->dev, "Trace funnel inport %d disabled\n", + in->dest_port); + + if (funnel_data->input_refcnt == 0) + dev_dbg(&csdev->dev, "Trace funnel disabled\n"); +} + +static const struct coresight_ops_link funnel_link_ops = { + .enable = funnel_enable, + .disable = funnel_disable, +}; + +static const struct coresight_ops funnel_cs_ops = { + .link_ops = &funnel_link_ops, +}; + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + unsigned long flags; + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + spin_lock_irqsave(&funnel_data->spinlock, flags); + + if (val) { + if (rvtrace_component_reset(comp)) { + comp->was_reset = false; + spin_unlock_irqrestore(&funnel_data->spinlock, flags); + return -EINVAL; + } + } + + spin_unlock_irqrestore(&funnel_data->spinlock, flags); + + return size; +} +static DEVICE_ATTR_WO(reset); + +static ssize_t cpu_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + + val = comp->cpu; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +static DEVICE_ATTR_RO(cpu); + +static struct attribute *trace_funnel_attrs[] = { + coresight_simple_reg32(control, RVTRACE_COMPONENT_CTRL_OFFSET), + coresight_simple_reg32(impl, RVTRACE_COMPONENT_IMPL_OFFSET), + coresight_simple_reg32(disinput, RVTRACE_FUNNEL_DISINPUT_OFFSET), + &dev_attr_reset.attr, + &dev_attr_cpu.attr, + NULL, +}; +ATTRIBUTE_GROUPS(trace_funnel); + +static int funnel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct funnel_data *funnel_data; + struct rvtrace_component *comp; + struct coresight_desc desc = { 0 }; + + comp = rvtrace_register_component(pdev); + if (IS_ERR(comp)) + return PTR_ERR(comp); + + funnel_data = devm_kzalloc(dev, sizeof(*funnel_data), GFP_KERNEL); + if (!funnel_data) + return -ENOMEM; + + spin_lock_init(&funnel_data->spinlock); + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + pdev->dev.platform_data = pdata; + + platform_set_drvdata(pdev, comp); + + desc.name = coresight_alloc_device_name(&funnel_devs, dev); + desc.access = CSDEV_ACCESS_IOMEM(comp->base); + desc.type = CORESIGHT_DEV_TYPE_LINK; + desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; + desc.ops = &funnel_cs_ops; + desc.pdata = pdata; + desc.dev = dev; + desc.groups = trace_funnel_groups; + funnel_data->csdev = coresight_register(&desc); + if (IS_ERR(funnel_data->csdev)) + return PTR_ERR(funnel_data->csdev); + + comp->id.data = funnel_data; + + dev_dbg(dev, "Trace Funnel initialized\n"); + + return 0; +} + +static void funnel_remove(struct platform_device *pdev) +{ + struct rvtrace_component *comp = platform_get_drvdata(pdev); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + coresight_unregister(funnel_data->csdev); +} + +static const struct of_device_id funnel_match[] = { + {.compatible = "riscv,trace-funnel"}, + {}, +}; + +static struct platform_driver funnel_driver = { + .probe = funnel_probe, + .remove = funnel_remove, + .driver = { + .name = "trace-funnel", + .of_match_table = funnel_match, + }, +}; + +module_platform_driver(funnel_driver); + +MODULE_DESCRIPTION("RISC-V Trace funnel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwtracing/coresight/rvtrace-funnel.h b/drivers/hwtracing/coresight/rvtrace-funnel.h new file mode 100644 index 000000000000..e5247e6c1bf5 --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-funnel.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#ifndef _RVTRACE_FUNNEL_H +#define _RVTRACE_FUNNEL_H + +#include <linux/spinlock.h> +#include <linux/coresight.h> + +/* Disable Individual Funnel Inputs */ +#define RVTRACE_FUNNEL_DISINPUT_OFFSET 0x008 +#define RVTRACE_FUNNEL_DISINPUT_MASK 0xffff + +/** + * struct funnel_data - specifics associated to a Trace Funnel component + * @csdev: Component vitals needed by the framework. + * @spinlock: Only one at a time pls. + * @was_enabled: Flag showing whether the Trace Funnel was enabled. + * @input_refcnt: Record the number of funnel inputs + */ +struct funnel_data { + struct coresight_device *csdev; + spinlock_t spinlock; + bool was_enabled; + u32 input_refcnt; + u32 disintput; +}; + +#endif
From: liangzhen zhen.liang@spacemit.com
Add initial implementation of RISC-V trace ATB Bridge driver. The ATB Bridge is defined in the RISC-V Trace Control Interface specification.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/coresight/Kconfig | 12 + drivers/hwtracing/coresight/Makefile | 1 + .../hwtracing/coresight/rvtrace-atbbridge.c | 239 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 drivers/hwtracing/coresight/rvtrace-atbbridge.c
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 8381a84e2699..1cd88a1a7d08 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -288,6 +288,17 @@ config RVTRACE dynamically aggregated with CoreSight trace infrastructure at run time to form a complete trace path.
+config RVTRACE_ATBBRIDGE + tristate "RISCV Trace ATB Bridge driver" + depends on RVTRACE + help + This driver provides support for the ATB Bridge driver. The ATB + Bridge allows sending RISC-V trace to Arm CoreSight infrastructure + (instead of RISC-V compliant sink) as an ATB initiator. + + To compile this driver as a module, choose M here: the module + will be called rvtrace-atbbridge. + config RVTRACE_FUNNEL tristate "RISCV Trace Funnel driver" depends on RVTRACE @@ -303,6 +314,7 @@ config RVTRACE_ENCODER tristate "RISCV Trace Encoder driver" depends on RVTRACE select RVTRACE_FUNNEL + select RVTRACE_ATBBRIDGE help This driver provides support for the Trace Encoder module, tracing the instructions that a processor is executing. This is primarily diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 409a0abf0642..2264a313a773 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -59,6 +59,7 @@ coresight-ctcu-y := coresight-ctcu-core.o obj-$(CONFIG_CORESIGHT_KUNIT_TESTS) += coresight-kunit-tests.o obj-$(CONFIG_RVTRACE) += rvtrace.o rvtrace-y := rvtrace-core.o +obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o rvtrace-encoder-y := rvtrace-encoder-core.o rvtrace-encoder-sysfs.o diff --git a/drivers/hwtracing/coresight/rvtrace-atbbridge.c b/drivers/hwtracing/coresight/rvtrace-atbbridge.c new file mode 100644 index 000000000000..a931be322a2f --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-atbbridge.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/coresight.h> +#include <linux/platform_device.h> +#include <linux/bitfield.h> +#include <linux/rvtrace.h> + +#include "coresight-priv.h" +#include "coresight-trace-id.h" + +#define RVTRACE_ATBBRIDGE_CONTROL_ID_MASK GENMASK(14, 8) +/** + * struct atbbridge_data - specifics associated to a ATB bridge component + * @csdev: Component vitals needed by the framework. + * @spinlock: Only one at a time pls. + * @traceid: Value of the current ID for this component. + */ +struct atbbridge_data { + struct coresight_device *csdev; + spinlock_t spinlock; + u8 traceid; +}; + +DEFINE_CORESIGHT_DEVLIST(atbbridge_devs, "atbbridge"); + +static int atbbridge_enable(struct coresight_device *csdev, + struct coresight_connection *in, + struct coresight_connection *out) +{ + u32 control; + int ret = 0; + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + unsigned long flags; + + spin_lock_irqsave(&atbbridge_data->spinlock, flags); + if (!comp->was_reset) { + ret = rvtrace_component_reset(comp); + if (ret) + goto done; + } + + control = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + control |= FIELD_PREP(RVTRACE_ATBBRIDGE_CONTROL_ID_MASK, atbbridge_data->traceid); + writel_relaxed(control, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); + + ret = rvtrace_enable_component(comp); + if (ret) + goto done; + + spin_unlock_irqrestore(&atbbridge_data->spinlock, flags); + + dev_dbg(&csdev->dev, "Trace ATB bridge enabled\n"); +done: + return ret; +} + +static void atbbridge_disable(struct coresight_device *csdev, + struct coresight_connection *in, + struct coresight_connection *out) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + unsigned long flags; + + spin_lock_irqsave(&atbbridge_data->spinlock, flags); + + if (rvtrace_disable_component(comp)) + dev_err(&csdev->dev, + "timeout while waiting for Trace ATB Bridge to be disabled\n"); + + if (rvtrace_comp_is_empty(comp)) + dev_err(&csdev->dev, + "timeout while waiting for Trace ATB Bridge internal buffers become empty\n"); + + spin_unlock_irqrestore(&atbbridge_data->spinlock, flags); + + dev_dbg(&csdev->dev, "Trace ATB bridge disabled\n"); +} + +static int atbbridge_link_trace_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode, + __maybe_unused struct coresight_device *sink) +{ + struct rvtrace_component *comp = dev_get_drvdata(csdev->dev.parent); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + + return atbbridge_data->traceid; +} + +static const struct coresight_ops_link atbbridge_link_ops = { + .enable = atbbridge_enable, + .disable = atbbridge_disable, +}; + +static const struct coresight_ops atbbridge_cs_ops = { + .trace_id = atbbridge_link_trace_id, + .link_ops = &atbbridge_link_ops, +}; + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + unsigned long flags; + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val) { + if (rvtrace_component_reset(comp)) { + comp->was_reset = false; + spin_unlock_irqrestore(&atbbridge_data->spinlock, flags); + return -EINVAL; + } + } + + spin_unlock_irqrestore(&atbbridge_data->spinlock, flags); + + return size; +} +static DEVICE_ATTR_WO(reset); + +static ssize_t cpu_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + + val = comp->cpu; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +static DEVICE_ATTR_RO(cpu); + +static ssize_t traceid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + + val = atbbridge_data->traceid; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +static DEVICE_ATTR_RO(traceid); + +static struct attribute *trace_atbbridge_attrs[] = { + coresight_simple_reg32(control, RVTRACE_COMPONENT_CTRL_OFFSET), + coresight_simple_reg32(impl, RVTRACE_COMPONENT_IMPL_OFFSET), + &dev_attr_reset.attr, + &dev_attr_cpu.attr, + &dev_attr_traceid.attr, + NULL, +}; +ATTRIBUTE_GROUPS(trace_atbbridge); + +static int atbbridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct atbbridge_data *atbbridge_data; + struct rvtrace_component *comp; + struct coresight_desc desc = { 0 }; + int trace_id = 0; + + comp = rvtrace_register_component(pdev); + if (IS_ERR(comp)) + return PTR_ERR(comp); + + atbbridge_data = devm_kzalloc(dev, sizeof(*atbbridge_data), GFP_KERNEL); + if (!atbbridge_data) + return -ENOMEM; + + spin_lock_init(&atbbridge_data->spinlock); + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + pdev->dev.platform_data = pdata; + + platform_set_drvdata(pdev, comp); + + desc.name = coresight_alloc_device_name(&atbbridge_devs, dev); + desc.access = CSDEV_ACCESS_IOMEM(comp->base); + desc.type = CORESIGHT_DEV_TYPE_LINK; + desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO; + desc.ops = &atbbridge_cs_ops; + desc.pdata = pdata; + desc.dev = dev; + desc.groups = trace_atbbridge_groups; + atbbridge_data->csdev = coresight_register(&desc); + if (IS_ERR(atbbridge_data->csdev)) + return PTR_ERR(atbbridge_data->csdev); + + trace_id = coresight_trace_id_get_system_id(); + if (trace_id < 0) + return -EINVAL; + atbbridge_data->traceid = (u8)trace_id; + + comp->id.data = atbbridge_data; + + dev_dbg(dev, "Trace ATB Bridge initialized\n"); + + return 0; +} + +static void atbbridge_remove(struct platform_device *pdev) +{ + struct rvtrace_component *comp = platform_get_drvdata(pdev); + struct atbbridge_data *atbbridge_data = rvtrace_component_data(comp); + + coresight_trace_id_put_system_id(atbbridge_data->traceid); + coresight_unregister(atbbridge_data->csdev); +} + +static const struct of_device_id atbbridge_match[] = { + {.compatible = "riscv,trace-atbbridge"}, + {}, +}; + +static struct platform_driver atbbridge_driver = { + .probe = atbbridge_probe, + .remove = atbbridge_remove, + .driver = { + .name = "trace-atbbridge", + .of_match_table = atbbridge_match, + }, +}; + +module_platform_driver(atbbridge_driver); + +MODULE_DESCRIPTION("RISC-V Trace ATB Bridge driver"); +MODULE_LICENSE("GPL");
From: liangzhen zhen.liang@spacemit.com
Implement timestamp as a configurable sub-component for both encoder and funnel drivers.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- drivers/hwtracing/coresight/Makefile | 2 +- .../coresight/rvtrace-encoder-core.c | 42 ++- .../coresight/rvtrace-encoder-sysfs.c | 50 ++++ drivers/hwtracing/coresight/rvtrace-encoder.h | 8 + drivers/hwtracing/coresight/rvtrace-funnel.c | 95 +++++- drivers/hwtracing/coresight/rvtrace-funnel.h | 8 + .../hwtracing/coresight/rvtrace-timestamp.c | 278 ++++++++++++++++++ .../hwtracing/coresight/rvtrace-timestamp.h | 64 ++++ 8 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 drivers/hwtracing/coresight/rvtrace-timestamp.c create mode 100644 drivers/hwtracing/coresight/rvtrace-timestamp.h
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 2264a313a773..3ad1b0399f86 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -58,7 +58,7 @@ obj-$(CONFIG_CORESIGHT_CTCU) += coresight-ctcu.o coresight-ctcu-y := coresight-ctcu-core.o obj-$(CONFIG_CORESIGHT_KUNIT_TESTS) += coresight-kunit-tests.o obj-$(CONFIG_RVTRACE) += rvtrace.o -rvtrace-y := rvtrace-core.o +rvtrace-y := rvtrace-core.o rvtrace-timestamp.o obj-$(CONFIG_RVTRACE_ATBBRIDGE) += rvtrace-atbbridge.o obj-$(CONFIG_RVTRACE_FUNNEL) += rvtrace-funnel.o obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o diff --git a/drivers/hwtracing/coresight/rvtrace-encoder-core.c b/drivers/hwtracing/coresight/rvtrace-encoder-core.c index 149a3cd97602..7bd075b8ce4f 100644 --- a/drivers/hwtracing/coresight/rvtrace-encoder-core.c +++ b/drivers/hwtracing/coresight/rvtrace-encoder-core.c @@ -14,9 +14,11 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/rvtrace.h>
#include "rvtrace-encoder.h" +#include "rvtrace-timestamp.h" #include "coresight-etm-perf.h"
static int boot_enable; @@ -172,6 +174,10 @@ static void encoder_set_config(struct rvtrace_component *comp) config->inst_syncmax); config->inst_syncmax = BMVAL(val, 20, 23); } + + /* Configure timestamp only if encoder has timestamp component */ + if (encoder_data->has_timestamp && encoder_data->ts_ctrl) + timestamp_set_config(comp, dev, &encoder_data->ts_config); }
static int encoder_enable_hw(struct rvtrace_component *comp) @@ -192,6 +198,16 @@ static int encoder_enable_hw(struct rvtrace_component *comp) if (ret) goto done;
+ /* Enable timestamp only if encoder has timestamp component */ + if (encoder_data->has_timestamp && encoder_data->ts_ctrl) { + ret = timestamp_enable(comp); + if (ret) { + dev_warn(&encoder_data->csdev->dev, + "Failed to enable timestamp\n"); + ret = 0; /* Don't fail encoder enable if timestamp fails */ + } + } + val = readl_relaxed(comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); val |= RVTRACE_ENCODER_ITRACE; writel_relaxed(val, comp->base + RVTRACE_COMPONENT_CTRL_OFFSET); @@ -283,6 +299,10 @@ static void encoder_disable_hw(struct rvtrace_component *comp) { struct encoder_data *encoder_data = rvtrace_component_data(comp);
+ /* Disable timestamp only if encoder has timestamp component */ + if (encoder_data->has_timestamp && encoder_data->ts_ctrl) + timestamp_disable(comp); + if (rvtrace_disable_component(comp)) dev_err(&encoder_data->csdev->dev, "timeout while waiting for Trace Encoder become disabled\n"); @@ -431,6 +451,26 @@ static int encoder_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, comp);
+ /* Check if encoder has timestamp component from device tree early */ + encoder_data->has_timestamp = fwnode_property_present(dev->fwnode, + "riscv,timestamp-present"); + if (encoder_data->has_timestamp) { + if (rvtrace_init_timestamp(comp, &encoder_data->ts_config)) { + dev_err(dev, "Timestamp initialization failed\n"); + return -EINVAL; + } + + /* TODO: Default to enabling timestamp control if present, as + * encoder_data->ts_ctrl can be configured via sysfs attribute, + * but not through perf_event at this time. Future versions may + * add support for configuring timestamps via perf_event. + */ + encoder_data->ts_ctrl = true; + } + + /* Set component data before registration so is_visible callbacks can access it */ + comp->id.data = encoder_data; + desc.name = devm_kasprintf(dev, GFP_KERNEL, "encoder%d", comp->cpu); if (!desc.name) return -ENOMEM; @@ -452,8 +492,6 @@ static int encoder_probe(struct platform_device *pdev) return ret; }
- comp->id.data = encoder_data; - rvtrace_cpu_encoder[comp->cpu] = comp;
encoder_set_default(comp); diff --git a/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c b/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c index c89af4cb591a..520f5a16f9a0 100644 --- a/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c +++ b/drivers/hwtracing/coresight/rvtrace-encoder-sysfs.c @@ -7,6 +7,7 @@ #include <linux/sysfs.h> #include <linux/rvtrace.h> #include "rvtrace-encoder.h" +#include "rvtrace-timestamp.h" #include "coresight-priv.h"
static ssize_t cpu_show(struct device *dev, @@ -48,9 +49,38 @@ static ssize_t reset_store(struct device *dev, } static DEVICE_ATTR_WO(reset);
+static ssize_t ts_ctrl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + return scnprintf(buf, PAGE_SIZE, "%u\n", encoder_data->ts_ctrl); +} + +static ssize_t ts_ctrl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + spin_lock(&encoder_data->spinlock); + encoder_data->ts_ctrl = !!val; + spin_unlock(&encoder_data->spinlock); + + return size; +} +static DEVICE_ATTR_RW(ts_ctrl); + static struct attribute *trace_encoder_attrs[] = { &dev_attr_cpu.attr, &dev_attr_reset.attr, + &dev_attr_ts_ctrl.attr, NULL, };
@@ -285,6 +315,19 @@ static struct attribute *trace_encoder_features_attrs[] = { NULL, };
+static umode_t timestamp_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + if (encoder_data->has_timestamp) + return attr->mode; + + return 0; +} + static const struct attribute_group trace_encoder_group = { .attrs = trace_encoder_attrs, }; @@ -304,10 +347,17 @@ static const struct attribute_group trace_encoder_features_group = { .name = "features", };
+static const struct attribute_group trace_encoder_timestamp_group = { + .attrs = (struct attribute **)timestamp_attrs, + .name = "timestamp", + .is_visible = timestamp_attr_is_visible, +}; + const struct attribute_group *trace_encoder_groups[] = { &trace_encoder_group, &trace_encoder_mgmt_group, &trace_encoder_control_group, &trace_encoder_features_group, + &trace_encoder_timestamp_group, NULL, }; diff --git a/drivers/hwtracing/coresight/rvtrace-encoder.h b/drivers/hwtracing/coresight/rvtrace-encoder.h index 44f2066ad869..7a0ada11de8d 100644 --- a/drivers/hwtracing/coresight/rvtrace-encoder.h +++ b/drivers/hwtracing/coresight/rvtrace-encoder.h @@ -9,6 +9,7 @@ #include <asm/local.h> #include <linux/spinlock.h> #include "coresight-priv.h" +#include "rvtrace-timestamp.h"
/* Trace Encoder Control Register */ #define RVTRACE_ENCODER_ITRACE BIT(2) @@ -126,7 +127,10 @@ struct encoder_config { * @spinlock: Only one at a time pls. * @sticky_enable: True if trace encoder base configuration has been done. * @boot_enable: True if we should start tracing at boot time. + * @has_timestamp: True if this encoder has timestamp component. + * @ts_ctrl: Controls the insertion of global timestamps in the trace streams. * @config: Structure holding configuration parameters. + * @ts_config: Timestamp configuration. */
struct encoder_data { @@ -134,10 +138,14 @@ struct encoder_data { spinlock_t spinlock; bool sticky_enable; bool boot_enable; + bool has_timestamp; + bool ts_ctrl; struct encoder_config config; + struct timestamp_config ts_config; };
extern const struct attribute_group *trace_encoder_groups[]; +extern const struct attribute *timestamp_attrs[]; void encoder_set_default(struct rvtrace_component *comp);
#endif diff --git a/drivers/hwtracing/coresight/rvtrace-funnel.c b/drivers/hwtracing/coresight/rvtrace-funnel.c index 0dc7799a64ac..ab7cdb29e5fd 100644 --- a/drivers/hwtracing/coresight/rvtrace-funnel.c +++ b/drivers/hwtracing/coresight/rvtrace-funnel.c @@ -6,9 +6,11 @@ #include <linux/kernel.h> #include <linux/coresight.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/rvtrace.h>
#include "coresight-priv.h" +#include "rvtrace-timestamp.h" #include "rvtrace-funnel.h"
DEFINE_CORESIGHT_DEVLIST(funnel_devs, "rvtrace_funnel"); @@ -36,6 +38,16 @@ static int funnel_enable_hw(struct rvtrace_component *comp, int port) ret = rvtrace_enable_component(comp); if (ret) goto done; + + /* Enable timestamp only if funnel has timestamp component */ + if (funnel_data->has_timestamp && funnel_data->ts_ctrl) { + ret = timestamp_enable(comp); + if (ret) { + dev_warn(&funnel_data->csdev->dev, + "Failed to enable timestamp\n"); + ret = 0; /* Don't fail funnel enable if timestamp fails */ + } + } }
done: @@ -79,6 +91,10 @@ static void funnel_disable_hw(struct rvtrace_component *comp, int inport) if (--funnel_data->input_refcnt != 0) return;
+ /* Disable timestamp only if funnel has timestamp component */ + if (funnel_data->has_timestamp && funnel_data->ts_ctrl) + timestamp_disable(comp); + writel_relaxed(RVTRACE_FUNNEL_DISINPUT_MASK, comp->base + RVTRACE_FUNNEL_DISINPUT_OFFSET);
if (rvtrace_disable_component(comp)) @@ -162,15 +178,72 @@ static ssize_t cpu_show(struct device *dev, } static DEVICE_ATTR_RO(cpu);
+static ssize_t ts_ctrl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + return scnprintf(buf, PAGE_SIZE, "%u\n", funnel_data->ts_ctrl); +} + +static ssize_t ts_ctrl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + spin_lock(&funnel_data->spinlock); + funnel_data->ts_ctrl = !!val; + spin_unlock(&funnel_data->spinlock); + + return size; +} +static DEVICE_ATTR_RW(ts_ctrl); + static struct attribute *trace_funnel_attrs[] = { coresight_simple_reg32(control, RVTRACE_COMPONENT_CTRL_OFFSET), coresight_simple_reg32(impl, RVTRACE_COMPONENT_IMPL_OFFSET), coresight_simple_reg32(disinput, RVTRACE_FUNNEL_DISINPUT_OFFSET), &dev_attr_reset.attr, &dev_attr_cpu.attr, + &dev_attr_ts_ctrl.attr, + NULL, +}; + +static umode_t timestamp_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + if (funnel_data->has_timestamp) + return attr->mode; + + return 0; +} + +static struct attribute_group trace_funnel_group = { + .attrs = trace_funnel_attrs, +}; + +static struct attribute_group trace_funnel_timestamp_group = { + .attrs = (struct attribute **)timestamp_attrs, + .name = "timestamp", + .is_visible = timestamp_attr_is_visible, +}; + +const struct attribute_group *trace_funnel_groups[] = { + &trace_funnel_group, + &trace_funnel_timestamp_group, NULL, }; -ATTRIBUTE_GROUPS(trace_funnel);
static int funnel_probe(struct platform_device *pdev) { @@ -197,6 +270,26 @@ static int funnel_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, comp);
+ /* Check if funnel has timestamp component from device tree */ + funnel_data->has_timestamp = fwnode_property_present(dev->fwnode, + "riscv,timestamp-present"); + if (funnel_data->has_timestamp) { + if (rvtrace_init_timestamp(comp, &funnel_data->ts_config)) { + dev_err(dev, "Timestamp initialization failed\n"); + return -EINVAL; + } + + /* TODO: Default to enabling timestamp control if present, as + * encoder_data->ts_ctrl can be configured via sysfs attribute, + * but not through perf_event at this time. Future versions may + * add support for configuring timestamps via perf_event. + */ + funnel_data->ts_ctrl = true; + } + + /* Set component data before registration so is_visible callbacks can access it */ + comp->id.data = funnel_data; + desc.name = coresight_alloc_device_name(&funnel_devs, dev); desc.access = CSDEV_ACCESS_IOMEM(comp->base); desc.type = CORESIGHT_DEV_TYPE_LINK; diff --git a/drivers/hwtracing/coresight/rvtrace-funnel.h b/drivers/hwtracing/coresight/rvtrace-funnel.h index e5247e6c1bf5..34394f775faa 100644 --- a/drivers/hwtracing/coresight/rvtrace-funnel.h +++ b/drivers/hwtracing/coresight/rvtrace-funnel.h @@ -19,6 +19,9 @@ * @spinlock: Only one at a time pls. * @was_enabled: Flag showing whether the Trace Funnel was enabled. * @input_refcnt: Record the number of funnel inputs + * @has_timestamp: True if this funnel has timestamp component. + * @ts_ctrl: Controls the insertion of global timestamps in the trace streams. + * @ts_config: Timestamp configuration. */ struct funnel_data { struct coresight_device *csdev; @@ -26,6 +29,11 @@ struct funnel_data { bool was_enabled; u32 input_refcnt; u32 disintput; + bool has_timestamp; + bool ts_ctrl; + struct timestamp_config ts_config; };
+extern const struct attribute *timestamp_attrs[]; + #endif diff --git a/drivers/hwtracing/coresight/rvtrace-timestamp.c b/drivers/hwtracing/coresight/rvtrace-timestamp.c new file mode 100644 index 000000000000..a82714f43d7c --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-timestamp.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/bitfield.h> +#include <linux/sysfs.h> +#include <linux/rvtrace.h> + +#include "rvtrace-timestamp.h" +#include "rvtrace-encoder.h" +#include "rvtrace-funnel.h" +#include "coresight-priv.h" + +int timestamp_enable(struct rvtrace_component *comp) +{ + u32 val; + + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + val |= (RVTRACE_TIMESTAMP_ENABLE | RVTRACE_TIMESTAMP_COUNT); + + writel_relaxed(val, comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + + return rvtrace_poll_bit(comp, RVTRACE_TIMESTAMP_CTRL_OFFSET, + RVTRACE_TIMESTAMP_ENABLE_SHIFT, 1); +} +EXPORT_SYMBOL_GPL(timestamp_enable); + +void timestamp_disable(struct rvtrace_component *comp) +{ + u32 val; + + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + val &= ~(RVTRACE_TIMESTAMP_ENABLE | RVTRACE_TIMESTAMP_COUNT); + writel_relaxed(val, comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); +} +EXPORT_SYMBOL_GPL(timestamp_disable); + +struct timestamp_config *timestamp_get_config(struct rvtrace_component *comp) +{ + if (comp->id.type == RVTRACE_COMPONENT_TYPE_ENCODER) { + struct encoder_data *encoder_data = rvtrace_component_data(comp); + + return &encoder_data->ts_config; + } else if (comp->id.type == RVTRACE_COMPONENT_TYPE_FUNNEL) { + struct funnel_data *funnel_data = rvtrace_component_data(comp); + + return &funnel_data->ts_config; + } else { + return NULL; + } +} +EXPORT_SYMBOL_GPL(timestamp_get_config); + +void timestamp_set_config(struct rvtrace_component *comp, struct device *dev, + struct timestamp_config *config) +{ + u32 val; + + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + + val |= (FIELD_PREP(RVTRACE_TIMESTAMP_RUN_IN_DEBUG, config->run_in_debug) | + FIELD_PREP(RVTRACE_TIMESTAMP_MODE, config->mode) | + FIELD_PREP(RVTRACE_TIMESTAMP_PRESCALE, config->prescale)); + + writel_relaxed(val, comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + + /* Verify configuration was applied */ + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + + if (BMVAL(val, 3, 3) != config->run_in_debug) { + dev_warn(dev, "timestamp run_in_debug %#x not supported\n", + config->run_in_debug); + config->run_in_debug = BMVAL(val, 3, 3); + } + + if (BMVAL(val, 4, 6) != config->mode) { + dev_warn(dev, "timestamp mode %#x not supported\n", + config->mode); + config->mode = BMVAL(val, 4, 6); + } + + if (BMVAL(val, 8, 9) != config->prescale) { + dev_warn(dev, "timestamp prescale %#x not supported\n", + config->prescale); + config->prescale = BMVAL(val, 8, 9); + } +} +EXPORT_SYMBOL_GPL(timestamp_set_config); + +static int rvtrace_timestamp_reset(struct rvtrace_component *comp) +{ + int ret; + + writel_relaxed(0, comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + ret = rvtrace_poll_bit(comp, RVTRACE_TIMESTAMP_CTRL_OFFSET, + RVTRACE_TIMESTAMP_ACTIVE_SHIFT, 0); + + if (ret) + return ret; + + writel_relaxed(RVTRACE_TIMESTAMP_ACTIVE, + comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + return rvtrace_poll_bit(comp, RVTRACE_TIMESTAMP_CTRL_OFFSET, + RVTRACE_TIMESTAMP_ACTIVE_SHIFT, 1); +} + +int rvtrace_init_timestamp(struct rvtrace_component *comp, + struct timestamp_config *config) +{ + u32 val; + int ret; + + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + if (!FIELD_GET(RVTRACE_TIMESTAMP_ACTIVE, val)) { + ret = rvtrace_timestamp_reset(comp); + if (ret) + return ret; + } + + val = readl_relaxed(comp->base + RVTRACE_TIMESTAMP_CTRL_OFFSET); + + config->run_in_debug = !!(val & RVTRACE_TIMESTAMP_RUN_IN_DEBUG); + config->mode = BMVAL(val, 4, 6); + config->prescale = BMVAL(val, 8, 9); + config->width = BMVAL(val, 24, 29); + + return 0; +} +EXPORT_SYMBOL_GPL(rvtrace_init_timestamp); + +static ssize_t run_in_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%u\n", config->run_in_debug); +} + +static ssize_t run_in_debug_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + unsigned long val; + + if (!config) + return -EINVAL; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + config->run_in_debug = !!val; + + return size; +} +static DEVICE_ATTR_RW(run_in_debug); + +static const char * const modes_str[] = { + [TIMESTAMP_MODE_NONE] = "none", + [TIMESTAMP_MODE_EXTERNAL] = "external", + [TIMESTAMP_MODE_INTERNAL_SYSTEM] = "internal_system", + [TIMESTAMP_MODE_INTERNAL_CORE] = "internal_core", + [TIMESTAMP_MODE_SHARED] = "shared", +}; + +static ssize_t modes_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s %s %s %s %s\n", + modes_str[TIMESTAMP_MODE_NONE], + modes_str[TIMESTAMP_MODE_EXTERNAL], + modes_str[TIMESTAMP_MODE_INTERNAL_SYSTEM], + modes_str[TIMESTAMP_MODE_INTERNAL_CORE], + modes_str[TIMESTAMP_MODE_SHARED]); +} +static DEVICE_ATTR_RO(modes_available); + +static ssize_t mode_preferred_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%s\n", modes_str[config->mode]); +} + +static ssize_t mode_preferred_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + if (sysfs_streq(buf, modes_str[TIMESTAMP_MODE_NONE])) + config->mode = TIMESTAMP_MODE_NONE; + else if (sysfs_streq(buf, modes_str[TIMESTAMP_MODE_EXTERNAL])) + config->mode = TIMESTAMP_MODE_EXTERNAL; + else if (sysfs_streq(buf, modes_str[TIMESTAMP_MODE_INTERNAL_SYSTEM])) + config->mode = TIMESTAMP_MODE_INTERNAL_SYSTEM; + else if (sysfs_streq(buf, modes_str[TIMESTAMP_MODE_INTERNAL_CORE])) + config->mode = TIMESTAMP_MODE_INTERNAL_CORE; + else if (sysfs_streq(buf, modes_str[TIMESTAMP_MODE_SHARED])) + config->mode = TIMESTAMP_MODE_SHARED; + else + return -EINVAL; + return size; +} +static DEVICE_ATTR_RW(mode_preferred); + +static ssize_t prescale_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%u\n", config->prescale); +} + +static ssize_t prescale_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long val; + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + config->prescale = val; + + return size; +} +static DEVICE_ATTR_RW(prescale); + +static ssize_t width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rvtrace_component *comp = dev_get_drvdata(dev->parent); + struct timestamp_config *config = timestamp_get_config(comp); + + if (!config) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%u\n", config->width); +} +static DEVICE_ATTR_RO(width); + +const struct attribute *timestamp_attrs[] = { + &dev_attr_run_in_debug.attr, + &dev_attr_modes_available.attr, + &dev_attr_mode_preferred.attr, + &dev_attr_prescale.attr, + &dev_attr_width.attr, + NULL, +}; +EXPORT_SYMBOL_GPL(timestamp_attrs); diff --git a/drivers/hwtracing/coresight/rvtrace-timestamp.h b/drivers/hwtracing/coresight/rvtrace-timestamp.h new file mode 100644 index 000000000000..13cecddbd82c --- /dev/null +++ b/drivers/hwtracing/coresight/rvtrace-timestamp.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + * Author: liangzhen zhen.liang@spacemit.com + */ + +#ifndef _RVTRACE_TIMESTAMP_H +#define _RVTRACE_TIMESTAMP_H + +#include <linux/types.h> + +/* Timestamp Control Register */ +#define RVTRACE_TIMESTAMP_CTRL_OFFSET 0x040 +#define RVTRACE_TIMESTAMP_ACTIVE BIT(0) +#define RVTRACE_TIMESTAMP_COUNT BIT(1) +#define RVTRACE_TIMESTAMP_RESET BIT(2) +#define RVTRACE_TIMESTAMP_RUN_IN_DEBUG BIT(3) +#define RVTRACE_TIMESTAMP_MODE GENMASK(6, 4) +#define RVTRACE_TIMESTAMP_PRESCALE GENMASK(9, 8) +#define RVTRACE_TIMESTAMP_ENABLE BIT(15) +#define RVTRACE_TIMESTAMP_WIDTH GENMASK(29, 24) + +#define RVTRACE_TIMESTAMP_ACTIVE_SHIFT 0 +#define RVTRACE_TIMESTAMP_ENABLE_SHIFT 15 + +/* Timestamp Counter Lower Bits */ +#define RVTRACE_TIMESTAMP_COUNTER_LOW 0x048 +/* Timestamp Counter Upper Bits */ +#define RVTRACE_TIMESTAMP_COUNTER_HIGH 0x04C + +enum timestamp_mode { + TIMESTAMP_MODE_NONE = 0, + TIMESTAMP_MODE_EXTERNAL = 1, + TIMESTAMP_MODE_INTERNAL_SYSTEM = 2, + TIMESTAMP_MODE_INTERNAL_CORE = 3, + TIMESTAMP_MODE_SHARED = 4 +}; + +/** + * struct timestamp_config - timestamp configuration for encoder/funnel + * @run_in_debug: Continue timestamp counting in debug mode + * @mode: Timestamp generation mode (periodic, event-triggered) + * @prescale: Clock prescale factor (1, 4, 16, 64) + * @width: Timestamp counter width in bits (0-63) + */ +struct timestamp_config { + bool run_in_debug; + u8 mode; + u8 prescale; + u8 width; +}; + +struct rvtrace_component; + +/* Timestamp control functions */ +int timestamp_enable(struct rvtrace_component *comp); +void timestamp_disable(struct rvtrace_component *comp); +struct timestamp_config *timestamp_get_config(struct rvtrace_component *comp); +void timestamp_set_config(struct rvtrace_component *comp, struct device *dev, + struct timestamp_config *config); +int rvtrace_init_timestamp(struct rvtrace_component *comp, + struct timestamp_config *config); + +#endif
From: liangzhen zhen.liang@spacemit.com
Define CORESIGHT_ETM_PMU_NAME based on architecture: - Set to "rvtrace" when CONFIG_RVTRACE is enabled - Default to "cs_etm" for ARM/ARM64 systems
Signed-off-by: liangzhen zhen.liang@spacemit.com --- include/linux/coresight-pmu.h | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/include/linux/coresight-pmu.h b/include/linux/coresight-pmu.h index 2e179abe472a..119565500ec5 100644 --- a/include/linux/coresight-pmu.h +++ b/include/linux/coresight-pmu.h @@ -9,7 +9,11 @@
#include <linux/bits.h>
+#if IS_ENABLED(CONFIG_RVTRACE) +#define CORESIGHT_ETM_PMU_NAME "rvtrace" +#else #define CORESIGHT_ETM_PMU_NAME "cs_etm" +#endif
/* * The legacy Trace ID system based on fixed calculation from the cpu
From: liangzhen zhen.liang@spacemit.com
This commit enhances the RISC-V performance tools by allowing the rvtrace PMU to be listed and selected when auxiliary trace support is enabled.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- tools/perf/arch/riscv/util/Build | 2 ++ tools/perf/arch/riscv/util/pmu.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tools/perf/arch/riscv/util/pmu.c
diff --git a/tools/perf/arch/riscv/util/Build b/tools/perf/arch/riscv/util/Build index 2328fb9a30a3..748068a3a5c5 100644 --- a/tools/perf/arch/riscv/util/Build +++ b/tools/perf/arch/riscv/util/Build @@ -1 +1,3 @@ perf-util-y += header.o + +perf-util-y += pmu.o diff --git a/tools/perf/arch/riscv/util/pmu.c b/tools/perf/arch/riscv/util/pmu.c new file mode 100644 index 000000000000..71ad1c8884d0 --- /dev/null +++ b/tools/perf/arch/riscv/util/pmu.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <string.h> +#include <linux/perf_event.h> + +#include "../../../util/pmu.h" + +#define RVTRACE_PMU_NAME "rvtrace" + +void perf_pmu__arch_init(struct perf_pmu *pmu) +{ + if (!strcmp(pmu->name, RVTRACE_PMU_NAME)) { + pmu->auxtrace = true; + pmu->selectable = true; + } +} +
From: liangzhen zhen.liang@spacemit.com
Introduce the required auxiliary API functions allowing the perf core to interact with RISC-V trace perf driver.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- tools/perf/arch/riscv/util/Build | 2 +- tools/perf/arch/riscv/util/auxtrace.c | 489 ++++++++++++++++++++++++++ tools/perf/util/auxtrace.c | 1 + tools/perf/util/auxtrace.h | 1 + tools/perf/util/rvtrace.h | 38 ++ 5 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 tools/perf/arch/riscv/util/auxtrace.c create mode 100644 tools/perf/util/rvtrace.h
diff --git a/tools/perf/arch/riscv/util/Build b/tools/perf/arch/riscv/util/Build index 748068a3a5c5..10f41f97bc2e 100644 --- a/tools/perf/arch/riscv/util/Build +++ b/tools/perf/arch/riscv/util/Build @@ -1,3 +1,3 @@ perf-util-y += header.o
-perf-util-y += pmu.o +perf-util-y += pmu.o auxtrace.o diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c new file mode 100644 index 000000000000..5feee198ef97 --- /dev/null +++ b/tools/perf/arch/riscv/util/auxtrace.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/bitops.h> +#include <linux/log2.h> +#include <linux/zalloc.h> +#include <linux/string.h> +#include <time.h> + +#include <internal/lib.h> // page_size +#include "../../../util/auxtrace.h" +#include "../../../util/cpumap.h" +#include "../../../util/debug.h" +#include "../../../util/event.h" +#include "../../../util/evlist.h" +#include "../../../util/evsel.h" +#include "../../../util/rvtrace.h" +#include "../../../util/pmu.h" +#include "../../../util/record.h" +#include "../../../util/session.h" +#include "../../../util/tsc.h" +#include "../../../util/evsel_config.h" + +#define RVTRACE_PMU_NAME "rvtrace" +#define KiB(x) ((x) * 1024) +#define MiB(x) ((x) * 1024 * 1024) + +static const char * const metadata_encoder_ro[] = { + [RVTRACE_ENCODER_FORMAT] = "control/format", + [RVTRACE_ENCODER_CONTEXT] = "control/context", + [RVTRACE_ENCODER_INHB_SRC] = "control/inhb_src", + [RVTRACE_ENCODER_SRCBITS] = "features/srcb", + [RVTRACE_ENCODER_SRCID] = "features/srcid" +}; + +struct rvtrace_recording { + struct auxtrace_record itr; + struct perf_pmu *rvtrace_pmu; + struct evlist *evlist; + bool snapshot_mode; + size_t snapshot_size; +}; + +static int rvtrace_parse_snapshot_options(struct auxtrace_record *itr, + struct record_opts *opts, + const char *str) +{ + struct rvtrace_recording *ptr = + container_of(itr, struct rvtrace_recording, itr); + unsigned long long snapshot_size = 0; + char *endptr; + + if (str) { + snapshot_size = strtoull(str, &endptr, 0); + if (*endptr || snapshot_size > SIZE_MAX) + return -1; + } + + opts->auxtrace_snapshot_mode = true; + opts->auxtrace_snapshot_size = snapshot_size; + ptr->snapshot_size = snapshot_size; + + return 0; +} + +static size_t rvtrace_info_priv_size(struct auxtrace_record *itr __maybe_unused, + struct evlist *evlist __maybe_unused) +{ + int encoder; + struct perf_cpu_map *event_cpus = evlist->core.user_requested_cpus; + struct perf_cpu_map *intersect_cpus; + + if (!perf_cpu_map__has_any_cpu(event_cpus)) { + /* cpu map is not "any" CPU , we have specific CPUs to work with */ + struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus(); + + intersect_cpus = perf_cpu_map__intersect(event_cpus, online_cpus); + perf_cpu_map__put(online_cpus); + } else { + /* Event can be "any" CPU so count all online CPUs. */ + intersect_cpus = perf_cpu_map__new_online_cpus(); + } + + encoder = perf_cpu_map__nr(intersect_cpus); + perf_cpu_map__put(intersect_cpus); + + return (RVTRACE_HEADER_SIZE + encoder * RVTRACE_ENCODER_PRIV_SIZE); +} + +static int rvtrace_get_ro(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path, __u64 *val) +{ + char pmu_path[PATH_MAX]; + int scan; + + /* Get RO metadata from sysfs */ + snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path); + + scan = perf_pmu__scan_file(pmu, pmu_path, "%llx", val); + if (scan != 1) { + pr_err("%s: error reading: %s\n", __func__, pmu_path); + return -EINVAL; + } + + return 0; +} + +static void rvtrace_get_metadata(struct perf_cpu cpu, u32 *offset, + struct auxtrace_record *itr, + struct perf_record_auxtrace_info *info) +{ + struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr); + struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu; + + info->priv[*offset + RVTRACE_ENCODER_CPU] = cpu.cpu; + info->priv[*offset + RVTRACE_ENCODER_NR_TRC_PARAMS] = RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH; + + /* Get read-only information from sysFS */ + rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_FORMAT], + &info->priv[*offset + RVTRACE_ENCODER_FORMAT]); + rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT], + &info->priv[*offset + RVTRACE_ENCODER_CONTEXT]); + rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_INHB_SRC], + &info->priv[*offset + RVTRACE_ENCODER_INHB_SRC]); + rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_SRCBITS], + &info->priv[*offset + RVTRACE_ENCODER_SRCBITS]); + rvtrace_get_ro(rvtrace_pmu, cpu, metadata_encoder_ro[RVTRACE_ENCODER_SRCID], + &info->priv[*offset + RVTRACE_ENCODER_SRCID]); + + /* Where the next CPU entry should start from */ + *offset += RVTRACE_ENCODER_PRIV_MAX; +} + +static int rvtrace_info_fill(struct auxtrace_record *itr, struct perf_session *session, + struct perf_record_auxtrace_info *auxtrace_info, size_t priv_size) +{ + int i; + u32 offset; + u64 nr_cpu, type; + struct perf_cpu_map *cpu_map; + struct perf_cpu_map *event_cpus = session->evlist->core.user_requested_cpus; + struct perf_cpu_map *online_cpus = perf_cpu_map__new_online_cpus(); + struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr); + struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu; + struct perf_cpu cpu; + + if (priv_size != rvtrace_info_priv_size(itr, session->evlist)) + return -EINVAL; + + if (!session->evlist->core.nr_mmaps) + return -EINVAL; + + /* If the cpu_map has the "any" CPU all online CPUs are involved */ + if (perf_cpu_map__has_any_cpu(event_cpus)) { + cpu_map = online_cpus; + } else { + /* Make sure all specified CPUs are online */ + perf_cpu_map__for_each_cpu(cpu, i, event_cpus) { + if (!perf_cpu_map__has(online_cpus, cpu)) + return -EINVAL; + } + + cpu_map = event_cpus; + } + + nr_cpu = perf_cpu_map__nr(cpu_map); + type = rvtrace_pmu->type; + + /* First fill out the session header */ + auxtrace_info->type = PERF_AUXTRACE_RISCV_TRACE; + auxtrace_info->priv[RVTRACE_PMU_TYPE_CPUS] = type << 32; + auxtrace_info->priv[RVTRACE_PMU_TYPE_CPUS] |= nr_cpu; + + offset = RVTRACE_HEADER_MAX; + + perf_cpu_map__for_each_cpu(cpu, i, cpu_map) { + assert(offset < priv_size); + rvtrace_get_metadata(cpu, &offset, itr, auxtrace_info); + } + + perf_cpu_map__put(online_cpus); + + return 0; +} + +static int rvtrace_set_sink_attr(struct perf_pmu *pmu, + struct evsel *evsel) +{ + char msg[BUFSIZ], path[PATH_MAX], *sink; + struct evsel_config_term *term; + int ret = -EINVAL; + u32 hash; + + if (evsel->core.attr.config2 & GENMASK(31, 0)) + return 0; + + list_for_each_entry(term, &evsel->config_terms, list) { + if (term->type != EVSEL__CONFIG_TERM_DRV_CFG) + continue; + + sink = term->val.str; + snprintf(path, PATH_MAX, "sinks/%s", sink); + + ret = perf_pmu__scan_file(pmu, path, "%x", &hash); + if (ret != 1) { + if (errno == ENOENT) + pr_err("Couldn't find sink "%s" on event %s\n" + "Missing kernel or device support?\n\n" + "Hint: An appropriate sink will be picked automatically if one isn't specified.\n", + sink, evsel__name(evsel)); + else + pr_err("Failed to set sink "%s" on event %s with %d (%s)\n", + sink, evsel__name(evsel), errno, + str_error_r(errno, msg, sizeof(msg))); + return ret; + } + + evsel->core.attr.config2 |= hash; + return 0; + } + + /* + * No sink was provided on the command line - allow the CoreSight + * system to look for a default + */ + return 0; +} + +static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist *evlist, + struct record_opts *opts) +{ + struct rvtrace_recording *ptr = container_of(itr, struct rvtrace_recording, itr); + struct perf_pmu *rvtrace_pmu = ptr->rvtrace_pmu; + struct evsel *evsel, *rvtrace_evsel = NULL; + struct perf_cpu_map *cpus = evlist->core.user_requested_cpus; + bool privileged = perf_event_paranoid_check(-1); + struct evsel *tracking_evsel; + int err; + + ptr->evlist = evlist; + ptr->snapshot_mode = opts->auxtrace_snapshot_mode; + evlist__for_each_entry(evlist, evsel) { + if (evsel->core.attr.type == rvtrace_pmu->type) { + if (rvtrace_evsel) { + pr_err("There may be only one " RVTRACE_PMU_NAME "x event\n"); + return -EINVAL; + } + evsel->core.attr.freq = 0; + evsel->core.attr.sample_period = 1; + evsel->needs_auxtrace_mmap = true; + rvtrace_evsel = evsel; + opts->full_auxtrace = true; + } + } + + if (!opts->full_auxtrace) + return 0; + + err = rvtrace_set_sink_attr(rvtrace_pmu, rvtrace_evsel); + if (err) + return err; + + /* we are in snapshot mode */ + if (opts->auxtrace_snapshot_mode) { + /* + * No size were given to '-S' or '-m,', so go with + * the default + */ + if (!opts->auxtrace_snapshot_size && !opts->auxtrace_mmap_pages) { + if (privileged) { + opts->auxtrace_mmap_pages = MiB(4) / page_size; + } else { + opts->auxtrace_mmap_pages = KiB(128) / page_size; + if (opts->mmap_pages == UINT_MAX) + opts->mmap_pages = KiB(256) / page_size; + } + } else if (!opts->auxtrace_mmap_pages && !privileged && + opts->mmap_pages == UINT_MAX) { + opts->mmap_pages = KiB(256) / page_size; + } + + /* + * '-m,xyz' was specified but no snapshot size, so make the + * snapshot size as big as the auxtrace mmap area. + */ + if (!opts->auxtrace_snapshot_size) { + opts->auxtrace_snapshot_size = + opts->auxtrace_mmap_pages * (size_t)page_size; + } + + /* + * -Sxyz was specified but no auxtrace mmap area, so make the + * auxtrace mmap area big enough to fit the requested snapshot + * size. + */ + if (!opts->auxtrace_mmap_pages) { + size_t sz = opts->auxtrace_snapshot_size; + + sz = round_up(sz, page_size) / page_size; + opts->auxtrace_mmap_pages = roundup_pow_of_two(sz); + } + + /* Snapshot size can't be bigger than the auxtrace area */ + if (opts->auxtrace_snapshot_size > + opts->auxtrace_mmap_pages * (size_t)page_size) { + pr_err("Snapshot size %zu must not be greater than AUX area tracing mmap size %zu\n", + opts->auxtrace_snapshot_size, + opts->auxtrace_mmap_pages * (size_t)page_size); + return -EINVAL; + } + + /* Something went wrong somewhere - this shouldn't happen */ + if (!opts->auxtrace_snapshot_size || !opts->auxtrace_mmap_pages) { + pr_err("Failed to calculate default snapshot size and/or AUX area tracing mmap pages\n"); + return -EINVAL; + } + + pr_debug2("%s snapshot size: %zu\n", RVTRACE_PMU_NAME, + opts->auxtrace_snapshot_size); + } + + /* Buffer sizes weren't specified with '-m,xyz' so give some defaults */ + if (!opts->auxtrace_mmap_pages) { + if (privileged) { + opts->auxtrace_mmap_pages = MiB(4) / page_size; + } else { + opts->auxtrace_mmap_pages = KiB(128) / page_size; + if (opts->mmap_pages == UINT_MAX) + opts->mmap_pages = KiB(256) / page_size; + } + } + + /* Validate auxtrace_mmap_pages */ + if (opts->auxtrace_mmap_pages) { + size_t sz = opts->auxtrace_mmap_pages * (size_t)page_size; + size_t min_sz; + + if (opts->auxtrace_snapshot_mode) + min_sz = KiB(4); + else + min_sz = KiB(8); + + if (sz < min_sz || !is_power_of_2(sz)) { + pr_err("Invalid mmap size for Intel Processor Trace: must be at least %zuKiB and a power of 2\n", + min_sz / 1024); + return -EINVAL; + } + } + + /* + * To obtain the auxtrace buffer file descriptor, the auxtrace event + * must come first. + */ + evlist__to_front(evlist, rvtrace_evsel); + + /* + * get the CPU on the sample - need it to associate trace ID in the + * AUX_OUTPUT_HW_ID event, and the AUX event for per-cpu mmaps. + */ + evsel__set_sample_bit(rvtrace_evsel, CPU); + + /* Add dummy event to keep tracking */ + err = parse_event(evlist, "dummy:u"); + if (err) + return err; + + tracking_evsel = evlist__last(evlist); + evlist__set_tracking_event(evlist, tracking_evsel); + + tracking_evsel->core.attr.freq = 0; + tracking_evsel->core.attr.sample_period = 1; + + /* In per-cpu case, always need the time of mmap events etc */ + if (!perf_cpu_map__is_any_cpu_or_is_empty(cpus)) + evsel__set_sample_bit(tracking_evsel, TIME); + + return 0; +} + +static int rvtrace_snapshot_start(struct auxtrace_record *itr) +{ + struct rvtrace_recording *ptr = + container_of(itr, struct rvtrace_recording, itr); + struct evsel *evsel; + + evlist__for_each_entry(ptr->evlist, evsel) { + if (evsel->core.attr.type == ptr->rvtrace_pmu->type) + return evsel__disable(evsel); + } + return -EINVAL; +} + +static int rvtrace_snapshot_finish(struct auxtrace_record *itr) +{ + struct rvtrace_recording *ptr = + container_of(itr, struct rvtrace_recording, itr); + struct evsel *evsel; + + evlist__for_each_entry(ptr->evlist, evsel) { + if (evsel->core.attr.type == ptr->rvtrace_pmu->type) + return evsel__enable(evsel); + } + return -EINVAL; +} + +static u64 rvtrace_reference(struct auxtrace_record *itr __maybe_unused) +{ + return rdtsc(); +} + +static void rvtrace_recording_free(struct auxtrace_record *itr) +{ + struct rvtrace_recording *ptr = + container_of(itr, struct rvtrace_recording, itr); + + free(ptr); +} + +static struct auxtrace_record *rvtrace_recording_init(int *err, struct perf_pmu *rvtrace_pmu) +{ + struct rvtrace_recording *ptr; + + if (!rvtrace_pmu) { + *err = -ENODEV; + return NULL; + } + + ptr = zalloc(sizeof(*ptr)); + if (!ptr) { + *err = -ENOMEM; + return NULL; + } + + ptr->rvtrace_pmu = rvtrace_pmu; + ptr->itr.parse_snapshot_options = rvtrace_parse_snapshot_options; + ptr->itr.recording_options = rvtrace_recording_options; + ptr->itr.info_priv_size = rvtrace_info_priv_size; + ptr->itr.info_fill = rvtrace_info_fill; + ptr->itr.snapshot_start = rvtrace_snapshot_start; + ptr->itr.snapshot_finish = rvtrace_snapshot_finish; + ptr->itr.free = rvtrace_recording_free; + ptr->itr.reference = rvtrace_reference; + ptr->itr.read_finish = auxtrace_record__read_finish; + ptr->itr.alignment = 0; + + *err = 0; + return &ptr->itr; +} + +static struct perf_pmu *find_pmu_for_event(struct perf_pmu **pmus, + int pmu_nr, struct evsel *evsel) +{ + int i; + + if (!pmus) + return NULL; + + for (i = 0; i < pmu_nr; i++) { + if (evsel->core.attr.type == pmus[i]->type) + return pmus[i]; + } + + return NULL; +} + +struct auxtrace_record *auxtrace_record__init(struct evlist *evlist, int *err) +{ + struct perf_pmu *rvtrace_pmu = NULL; + struct perf_pmu *found_rvtrace = NULL; + struct evsel *evsel; + + if (!evlist) + return NULL; + + rvtrace_pmu = perf_pmus__find(RVTRACE_PMU_NAME); + evlist__for_each_entry(evlist, evsel) { + if (rvtrace_pmu && !found_rvtrace) + found_rvtrace = find_pmu_for_event(&rvtrace_pmu, 1, evsel); + } + + if (found_rvtrace) + return rvtrace_recording_init(err, rvtrace_pmu); + + *err = 0; + return NULL; +} diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index a224687ffbc1..944a43d48739 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -1411,6 +1411,7 @@ int perf_event__process_auxtrace_info(const struct perf_tool *tool __maybe_unuse case PERF_AUXTRACE_VPA_DTL: err = powerpc_vpadtl_process_auxtrace_info(event, session); break; + case PERF_AUXTRACE_RISCV_TRACE: case PERF_AUXTRACE_UNKNOWN: default: return -EINVAL; diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 6947f3f284c0..4f4714c1b53f 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -46,6 +46,7 @@ enum auxtrace_type { PERF_AUXTRACE_S390_CPUMSF, PERF_AUXTRACE_HISI_PTT, PERF_AUXTRACE_VPA_DTL, + PERF_AUXTRACE_RISCV_TRACE, };
enum itrace_period_type { diff --git a/tools/perf/util/rvtrace.h b/tools/perf/util/rvtrace.h new file mode 100644 index 000000000000..1e48ed989dd7 --- /dev/null +++ b/tools/perf/util/rvtrace.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#ifndef INCLUDE__UTIL_PERF_RVTRACE_H__ +#define INCLUDE__UTIL_PERF_RVTRACE_H__ + +#include "debug.h" +#include "auxtrace.h" +#include "util/event.h" +#include "util/session.h" +#include <linux/bits.h> + +enum { + /* PMU->type (32 bit), total # of CPUs (32 bit) */ + RVTRACE_PMU_TYPE_CPUS, + RVTRACE_HEADER_MAX, +}; + +/* Trace Encoder metadata */ +enum { + RVTRACE_ENCODER_CPU, + RVTRACE_ENCODER_NR_TRC_PARAMS, + RVTRACE_ENCODER_FORMAT, + RVTRACE_ENCODER_CONTEXT, + RVTRACE_ENCODER_INHB_SRC, + RVTRACE_ENCODER_SRCBITS, + RVTRACE_ENCODER_SRCID, + RVTRACE_ENCODER_PRIV_MAX, +}; + +#define RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH (RVTRACE_ENCODER_PRIV_MAX - RVTRACE_ENCODER_FORMAT) + +#define RVTRACE_HEADER_SIZE (RVTRACE_HEADER_MAX * sizeof(u64)) +#define RVTRACE_ENCODER_PRIV_SIZE (RVTRACE_ENCODER_PRIV_MAX * sizeof(u64)) + +#endif
From: liangzhen zhen.liang@spacemit.com
Add support for RISC-V Nexus Trace decoder based on the reference implementation from the RISC-V Nexus Trace specification. This includes a CoreSight frame deformatter to remove the trace formatter overhead.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- tools/arch/riscv/include/asm/insn.h | 645 ++++++++ tools/perf/arch/riscv/util/auxtrace.c | 1 + tools/perf/util/Build | 2 + tools/perf/util/nexus-rv-decoder/Build | 1 + .../util/nexus-rv-decoder/nexus-rv-decoder.c | 1364 +++++++++++++++++ .../util/nexus-rv-decoder/nexus-rv-decoder.h | 139 ++ .../perf/util/nexus-rv-decoder/nexus-rv-msg.h | 190 +++ 7 files changed, 2342 insertions(+) create mode 100644 tools/arch/riscv/include/asm/insn.h create mode 100644 tools/perf/util/nexus-rv-decoder/Build create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.c create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.h create mode 100644 tools/perf/util/nexus-rv-decoder/nexus-rv-msg.h
diff --git a/tools/arch/riscv/include/asm/insn.h b/tools/arch/riscv/include/asm/insn.h new file mode 100644 index 000000000000..6c020afe926b --- /dev/null +++ b/tools/arch/riscv/include/asm/insn.h @@ -0,0 +1,645 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 SiFive + */ + +#ifndef _ASM_RISCV_INSN_H +#define _ASM_RISCV_INSN_H + +#include <linux/bits.h> + +#define RV_INSN_FUNCT3_MASK GENMASK(14, 12) +#define RV_INSN_FUNCT3_OPOFF 12 +#define RV_INSN_OPCODE_MASK GENMASK(6, 0) +#define RV_INSN_OPCODE_OPOFF 0 +#define RV_INSN_FUNCT12_OPOFF 20 + +#define RV_ENCODE_FUNCT3(f_) (RVG_FUNCT3_##f_ << RV_INSN_FUNCT3_OPOFF) +#define RV_ENCODE_FUNCT12(f_) (RVG_FUNCT12_##f_ << RV_INSN_FUNCT12_OPOFF) + +/* The bit field of immediate value in I-type instruction */ +#define RV_I_IMM_SIGN_OPOFF 31 +#define RV_I_IMM_11_0_OPOFF 20 +#define RV_I_IMM_SIGN_OFF 12 +#define RV_I_IMM_11_0_OFF 0 +#define RV_I_IMM_11_0_MASK GENMASK(11, 0) + +/* The bit field of immediate value in J-type instruction */ +#define RV_J_IMM_SIGN_OPOFF 31 +#define RV_J_IMM_10_1_OPOFF 21 +#define RV_J_IMM_11_OPOFF 20 +#define RV_J_IMM_19_12_OPOFF 12 +#define RV_J_IMM_SIGN_OFF 20 +#define RV_J_IMM_10_1_OFF 1 +#define RV_J_IMM_11_OFF 11 +#define RV_J_IMM_19_12_OFF 12 +#define RV_J_IMM_10_1_MASK GENMASK(9, 0) +#define RV_J_IMM_11_MASK GENMASK(0, 0) +#define RV_J_IMM_19_12_MASK GENMASK(7, 0) + +/* + * U-type IMMs contain the upper 20bits [31:20] of an immediate with + * the rest filled in by zeros, so no shifting required. Similarly, + * bit31 contains the signed state, so no sign extension necessary. + */ +#define RV_U_IMM_SIGN_OPOFF 31 +#define RV_U_IMM_31_12_OPOFF 0 +#define RV_U_IMM_31_12_MASK GENMASK(31, 12) + +/* The bit field of immediate value in B-type instruction */ +#define RV_B_IMM_SIGN_OPOFF 31 +#define RV_B_IMM_10_5_OPOFF 25 +#define RV_B_IMM_4_1_OPOFF 8 +#define RV_B_IMM_11_OPOFF 7 +#define RV_B_IMM_SIGN_OFF 12 +#define RV_B_IMM_10_5_OFF 5 +#define RV_B_IMM_4_1_OFF 1 +#define RV_B_IMM_11_OFF 11 +#define RV_B_IMM_10_5_MASK GENMASK(5, 0) +#define RV_B_IMM_4_1_MASK GENMASK(3, 0) +#define RV_B_IMM_11_MASK GENMASK(0, 0) + +/* The register offset in RVG instruction */ +#define RVG_RS1_OPOFF 15 +#define RVG_RS2_OPOFF 20 +#define RVG_RD_OPOFF 7 +#define RVG_RS1_MASK GENMASK(4, 0) +#define RVG_RS2_MASK GENMASK(4, 0) +#define RVG_RD_MASK GENMASK(4, 0) + +/* The bit field of immediate value in RVC J instruction */ +#define RVC_J_IMM_SIGN_OPOFF 12 +#define RVC_J_IMM_4_OPOFF 11 +#define RVC_J_IMM_9_8_OPOFF 9 +#define RVC_J_IMM_10_OPOFF 8 +#define RVC_J_IMM_6_OPOFF 7 +#define RVC_J_IMM_7_OPOFF 6 +#define RVC_J_IMM_3_1_OPOFF 3 +#define RVC_J_IMM_5_OPOFF 2 +#define RVC_J_IMM_SIGN_OFF 11 +#define RVC_J_IMM_4_OFF 4 +#define RVC_J_IMM_9_8_OFF 8 +#define RVC_J_IMM_10_OFF 10 +#define RVC_J_IMM_6_OFF 6 +#define RVC_J_IMM_7_OFF 7 +#define RVC_J_IMM_3_1_OFF 1 +#define RVC_J_IMM_5_OFF 5 +#define RVC_J_IMM_4_MASK GENMASK(0, 0) +#define RVC_J_IMM_9_8_MASK GENMASK(1, 0) +#define RVC_J_IMM_10_MASK GENMASK(0, 0) +#define RVC_J_IMM_6_MASK GENMASK(0, 0) +#define RVC_J_IMM_7_MASK GENMASK(0, 0) +#define RVC_J_IMM_3_1_MASK GENMASK(2, 0) +#define RVC_J_IMM_5_MASK GENMASK(0, 0) + +/* The bit field of immediate value in RVC B instruction */ +#define RVC_B_IMM_SIGN_OPOFF 12 +#define RVC_B_IMM_4_3_OPOFF 10 +#define RVC_B_IMM_7_6_OPOFF 5 +#define RVC_B_IMM_2_1_OPOFF 3 +#define RVC_B_IMM_5_OPOFF 2 +#define RVC_B_IMM_SIGN_OFF 8 +#define RVC_B_IMM_4_3_OFF 3 +#define RVC_B_IMM_7_6_OFF 6 +#define RVC_B_IMM_2_1_OFF 1 +#define RVC_B_IMM_5_OFF 5 +#define RVC_B_IMM_4_3_MASK GENMASK(1, 0) +#define RVC_B_IMM_7_6_MASK GENMASK(1, 0) +#define RVC_B_IMM_2_1_MASK GENMASK(1, 0) +#define RVC_B_IMM_5_MASK GENMASK(0, 0) + +#define RVC_INSN_FUNCT4_MASK GENMASK(15, 12) +#define RVC_INSN_FUNCT4_OPOFF 12 +#define RVC_INSN_FUNCT3_MASK GENMASK(15, 13) +#define RVC_INSN_FUNCT3_OPOFF 13 +#define RVC_INSN_J_RS1_MASK GENMASK(11, 7) +#define RVC_INSN_J_RS2_MASK GENMASK(6, 2) +#define RVC_INSN_OPCODE_MASK GENMASK(1, 0) +#define RVC_ENCODE_FUNCT3(f_) (RVC_FUNCT3_##f_ << RVC_INSN_FUNCT3_OPOFF) +#define RVC_ENCODE_FUNCT4(f_) (RVC_FUNCT4_##f_ << RVC_INSN_FUNCT4_OPOFF) + +/* The register offset in RVC op=C0 instruction */ +#define RVC_C0_RS1_OPOFF 7 +#define RVC_C0_RS2_OPOFF 2 +#define RVC_C0_RD_OPOFF 2 + +/* The register offset in RVC op=C1 instruction */ +#define RVC_C1_RS1_OPOFF 7 +#define RVC_C1_RS2_OPOFF 2 +#define RVC_C1_RD_OPOFF 7 + +/* The register offset in RVC op=C2 instruction */ +#define RVC_C2_RS1_OPOFF 7 +#define RVC_C2_RS2_OPOFF 2 +#define RVC_C2_RD_OPOFF 7 +#define RVC_C2_RS1_MASK GENMASK(4, 0) + +/* parts of opcode for RVG*/ +#define RVG_OPCODE_FENCE 0x0f +#define RVG_OPCODE_AUIPC 0x17 +#define RVG_OPCODE_BRANCH 0x63 +#define RVG_OPCODE_JALR 0x67 +#define RVG_OPCODE_JAL 0x6f +#define RVG_OPCODE_SYSTEM 0x73 +#define RVG_SYSTEM_CSR_OFF 20 +#define RVG_SYSTEM_CSR_MASK GENMASK(12, 0) + +/* parts of opcode for RVF, RVD and RVQ */ +#define RVFDQ_FL_FS_WIDTH_OFF 12 +#define RVFDQ_FL_FS_WIDTH_MASK GENMASK(2, 0) +#define RVFDQ_FL_FS_WIDTH_W 2 +#define RVFDQ_FL_FS_WIDTH_D 3 +#define RVFDQ_LS_FS_WIDTH_Q 4 +#define RVFDQ_OPCODE_FL 0x07 +#define RVFDQ_OPCODE_FS 0x27 + +/* parts of opcode for RVV */ +#define RVV_OPCODE_VECTOR 0x57 +#define RVV_VL_VS_WIDTH_8 0 +#define RVV_VL_VS_WIDTH_16 5 +#define RVV_VL_VS_WIDTH_32 6 +#define RVV_VL_VS_WIDTH_64 7 +#define RVV_OPCODE_VL RVFDQ_OPCODE_FL +#define RVV_OPCODE_VS RVFDQ_OPCODE_FS + +/* parts of opcode for RVC*/ +#define RVC_OPCODE_C0 0x0 +#define RVC_OPCODE_C1 0x1 +#define RVC_OPCODE_C2 0x2 + +/* parts of funct3 code for I, M, A extension*/ +#define RVG_FUNCT3_JALR 0x0 +#define RVG_FUNCT3_BEQ 0x0 +#define RVG_FUNCT3_BNE 0x1 +#define RVG_FUNCT3_BLT 0x4 +#define RVG_FUNCT3_BGE 0x5 +#define RVG_FUNCT3_BLTU 0x6 +#define RVG_FUNCT3_BGEU 0x7 + +/* parts of funct3 code for C extension*/ +#define RVC_FUNCT3_C_BEQZ 0x6 +#define RVC_FUNCT3_C_BNEZ 0x7 +#define RVC_FUNCT3_C_J 0x5 +#define RVC_FUNCT3_C_JAL 0x1 +#define RVC_FUNCT4_C_JR 0x8 +#define RVC_FUNCT4_C_JALR 0x9 +#define RVC_FUNCT4_C_EBREAK 0x9 + +#define RVG_FUNCT12_ECALL 0x0 +#define RVG_FUNCT12_EBREAK 0x1 +#define RVG_FUNCT12_SRET 0x102 +#define RVG_FUNCT12_MRET 0x303 + +#define RVG_MATCH_AUIPC (RVG_OPCODE_AUIPC) +#define RVG_MATCH_JALR (RV_ENCODE_FUNCT3(JALR) | RVG_OPCODE_JALR) +#define RVG_MATCH_JAL (RVG_OPCODE_JAL) +#define RVG_MATCH_FENCE (RVG_OPCODE_FENCE) +#define RVG_MATCH_BEQ (RV_ENCODE_FUNCT3(BEQ) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_BNE (RV_ENCODE_FUNCT3(BNE) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_BLT (RV_ENCODE_FUNCT3(BLT) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_BGE (RV_ENCODE_FUNCT3(BGE) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_BLTU (RV_ENCODE_FUNCT3(BLTU) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_BGEU (RV_ENCODE_FUNCT3(BGEU) | RVG_OPCODE_BRANCH) +#define RVG_MATCH_ECALL (RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM) +#define RVG_MATCH_EBREAK (RV_ENCODE_FUNCT12(EBREAK) | RVG_OPCODE_SYSTEM) +#define RVG_MATCH_SRET (RV_ENCODE_FUNCT12(SRET) | RVG_OPCODE_SYSTEM) +#define RVG_MATCH_MRET (RV_ENCODE_FUNCT12(MRET) | RVG_OPCODE_SYSTEM) +#define RVC_MATCH_C_BEQZ (RVC_ENCODE_FUNCT3(C_BEQZ) | RVC_OPCODE_C1) +#define RVC_MATCH_C_BNEZ (RVC_ENCODE_FUNCT3(C_BNEZ) | RVC_OPCODE_C1) +#define RVC_MATCH_C_J (RVC_ENCODE_FUNCT3(C_J) | RVC_OPCODE_C1) +#define RVC_MATCH_C_JAL (RVC_ENCODE_FUNCT3(C_JAL) | RVC_OPCODE_C1) +#define RVC_MATCH_C_JR (RVC_ENCODE_FUNCT4(C_JR) | RVC_OPCODE_C2) +#define RVC_MATCH_C_JALR (RVC_ENCODE_FUNCT4(C_JALR) | RVC_OPCODE_C2) +#define RVC_MATCH_C_EBREAK (RVC_ENCODE_FUNCT4(C_EBREAK) | RVC_OPCODE_C2) + +#define RVG_MASK_AUIPC (RV_INSN_OPCODE_MASK) +#define RVG_MASK_JALR (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_JAL (RV_INSN_OPCODE_MASK) +#define RVG_MASK_FENCE (RV_INSN_OPCODE_MASK) +#define RVC_MASK_C_JALR (RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK) +#define RVC_MASK_C_JR (RVC_INSN_FUNCT4_MASK | RVC_INSN_J_RS2_MASK | RVC_INSN_OPCODE_MASK) +#define RVC_MASK_C_JAL (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK) +#define RVC_MASK_C_J (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK) +#define RVG_MASK_BEQ (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_BNE (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_BLT (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_BGE (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_BLTU (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVG_MASK_BGEU (RV_INSN_FUNCT3_MASK | RV_INSN_OPCODE_MASK) +#define RVC_MASK_C_BEQZ (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK) +#define RVC_MASK_C_BNEZ (RVC_INSN_FUNCT3_MASK | RVC_INSN_OPCODE_MASK) +#define RVC_MASK_C_EBREAK 0xffff +#define RVG_MASK_EBREAK 0xffffffff +#define RVG_MASK_ECALL 0xffffffff +#define RVG_MASK_SRET 0xffffffff +#define RVG_MASK_MRET 0xffffffff + +#define __INSN_LENGTH_MASK _UL(0x3) +#define __INSN_LENGTH_GE_32 _UL(0x3) +#define __INSN_OPCODE_MASK _UL(0x7F) +#define __INSN_BRANCH_OPCODE _UL(RVG_OPCODE_BRANCH) + +#define __RISCV_INSN_FUNCS(name, mask, val) \ +static __always_inline bool riscv_insn_is_##name(u32 code) \ +{ \ + BUILD_BUG_ON(~(mask) & (val)); \ + return (code & (mask)) == (val); \ +} \ + +#if __riscv_xlen == 32 +/* C.JAL is an RV32C-only instruction */ +__RISCV_INSN_FUNCS(c_jal, RVC_MASK_C_JAL, RVC_MATCH_C_JAL) +#else +#define riscv_insn_is_c_jal(opcode) 0 +#endif +__RISCV_INSN_FUNCS(auipc, RVG_MASK_AUIPC, RVG_MATCH_AUIPC) +__RISCV_INSN_FUNCS(jalr, RVG_MASK_JALR, RVG_MATCH_JALR) +__RISCV_INSN_FUNCS(jal, RVG_MASK_JAL, RVG_MATCH_JAL) +__RISCV_INSN_FUNCS(c_j, RVC_MASK_C_J, RVC_MATCH_C_J) +__RISCV_INSN_FUNCS(beq, RVG_MASK_BEQ, RVG_MATCH_BEQ) +__RISCV_INSN_FUNCS(bne, RVG_MASK_BNE, RVG_MATCH_BNE) +__RISCV_INSN_FUNCS(blt, RVG_MASK_BLT, RVG_MATCH_BLT) +__RISCV_INSN_FUNCS(bge, RVG_MASK_BGE, RVG_MATCH_BGE) +__RISCV_INSN_FUNCS(bltu, RVG_MASK_BLTU, RVG_MATCH_BLTU) +__RISCV_INSN_FUNCS(bgeu, RVG_MASK_BGEU, RVG_MATCH_BGEU) +__RISCV_INSN_FUNCS(c_beqz, RVC_MASK_C_BEQZ, RVC_MATCH_C_BEQZ) +__RISCV_INSN_FUNCS(c_bnez, RVC_MASK_C_BNEZ, RVC_MATCH_C_BNEZ) +__RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK) +__RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK) +__RISCV_INSN_FUNCS(ecall, RVG_MASK_EBREAK, RVG_MATCH_EBREAK) +__RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET) +__RISCV_INSN_FUNCS(mret, RVG_MASK_MRET, RVG_MATCH_MRET) +__RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE); + +/* special case to catch _any_ system instruction */ +static __always_inline bool riscv_insn_is_system(u32 code) +{ + return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_SYSTEM; +} + +/* special case to catch _any_ branch instruction */ +static __always_inline bool riscv_insn_is_branch(u32 code) +{ + return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_BRANCH; +} + +static __always_inline bool riscv_insn_is_c_jr(u32 code) +{ + return (code & RVC_MASK_C_JR) == RVC_MATCH_C_JR && + (code & RVC_INSN_J_RS1_MASK) != 0; +} + +static __always_inline bool riscv_insn_is_c_jalr(u32 code) +{ + return (code & RVC_MASK_C_JALR) == RVC_MATCH_C_JALR && + (code & RVC_INSN_J_RS1_MASK) != 0; +} + +#define INSN_MATCH_LB 0x3 +#define INSN_MASK_LB 0x707f +#define INSN_MATCH_LH 0x1003 +#define INSN_MASK_LH 0x707f +#define INSN_MATCH_LW 0x2003 +#define INSN_MASK_LW 0x707f +#define INSN_MATCH_LD 0x3003 +#define INSN_MASK_LD 0x707f +#define INSN_MATCH_LBU 0x4003 +#define INSN_MASK_LBU 0x707f +#define INSN_MATCH_LHU 0x5003 +#define INSN_MASK_LHU 0x707f +#define INSN_MATCH_LWU 0x6003 +#define INSN_MASK_LWU 0x707f +#define INSN_MATCH_SB 0x23 +#define INSN_MASK_SB 0x707f +#define INSN_MATCH_SH 0x1023 +#define INSN_MASK_SH 0x707f +#define INSN_MATCH_SW 0x2023 +#define INSN_MASK_SW 0x707f +#define INSN_MATCH_SD 0x3023 +#define INSN_MASK_SD 0x707f + +#define INSN_MATCH_C_LD 0x6000 +#define INSN_MASK_C_LD 0xe003 +#define INSN_MATCH_C_SD 0xe000 +#define INSN_MASK_C_SD 0xe003 +#define INSN_MATCH_C_LW 0x4000 +#define INSN_MASK_C_LW 0xe003 +#define INSN_MATCH_C_SW 0xc000 +#define INSN_MASK_C_SW 0xe003 +#define INSN_MATCH_C_LDSP 0x6002 +#define INSN_MASK_C_LDSP 0xe003 +#define INSN_MATCH_C_SDSP 0xe002 +#define INSN_MASK_C_SDSP 0xe003 +#define INSN_MATCH_C_LWSP 0x4002 +#define INSN_MASK_C_LWSP 0xe003 +#define INSN_MATCH_C_SWSP 0xc002 +#define INSN_MASK_C_SWSP 0xe003 + +#define INSN_OPCODE_MASK 0x007c +#define INSN_OPCODE_SHIFT 2 +#define INSN_OPCODE_SYSTEM 28 + +#define INSN_MASK_WFI 0xffffffff +#define INSN_MATCH_WFI 0x10500073 + +#define INSN_MASK_WRS 0xffffffff +#define INSN_MATCH_WRS 0x00d00073 + +#define INSN_MATCH_CSRRW 0x1073 +#define INSN_MASK_CSRRW 0x707f +#define INSN_MATCH_CSRRS 0x2073 +#define INSN_MASK_CSRRS 0x707f +#define INSN_MATCH_CSRRC 0x3073 +#define INSN_MASK_CSRRC 0x707f +#define INSN_MATCH_CSRRWI 0x5073 +#define INSN_MASK_CSRRWI 0x707f +#define INSN_MATCH_CSRRSI 0x6073 +#define INSN_MASK_CSRRSI 0x707f +#define INSN_MATCH_CSRRCI 0x7073 +#define INSN_MASK_CSRRCI 0x707f + +#define INSN_MATCH_FLW 0x2007 +#define INSN_MASK_FLW 0x707f +#define INSN_MATCH_FLD 0x3007 +#define INSN_MASK_FLD 0x707f +#define INSN_MATCH_FLQ 0x4007 +#define INSN_MASK_FLQ 0x707f +#define INSN_MATCH_FSW 0x2027 +#define INSN_MASK_FSW 0x707f +#define INSN_MATCH_FSD 0x3027 +#define INSN_MASK_FSD 0x707f +#define INSN_MATCH_FSQ 0x4027 +#define INSN_MASK_FSQ 0x707f + +#define INSN_MATCH_C_FLD 0x2000 +#define INSN_MASK_C_FLD 0xe003 +#define INSN_MATCH_C_FLW 0x6000 +#define INSN_MASK_C_FLW 0xe003 +#define INSN_MATCH_C_FSD 0xa000 +#define INSN_MASK_C_FSD 0xe003 +#define INSN_MATCH_C_FSW 0xe000 +#define INSN_MASK_C_FSW 0xe003 +#define INSN_MATCH_C_FLDSP 0x2002 +#define INSN_MASK_C_FLDSP 0xe003 +#define INSN_MATCH_C_FSDSP 0xa002 +#define INSN_MASK_C_FSDSP 0xe003 +#define INSN_MATCH_C_FLWSP 0x6002 +#define INSN_MASK_C_FLWSP 0xe003 +#define INSN_MATCH_C_FSWSP 0xe002 +#define INSN_MASK_C_FSWSP 0xe003 + +#define INSN_MATCH_C_LHU 0x8400 +#define INSN_MASK_C_LHU 0xfc43 +#define INSN_MATCH_C_LH 0x8440 +#define INSN_MASK_C_LH 0xfc43 +#define INSN_MATCH_C_SH 0x8c00 +#define INSN_MASK_C_SH 0xfc43 + +#define INSN_16BIT_MASK 0x3 +#define INSN_IS_16BIT(insn) (((insn) & INSN_16BIT_MASK) != INSN_16BIT_MASK) +#define INSN_LEN(insn) (INSN_IS_16BIT(insn) ? 2 : 4) + +#define SHIFT_RIGHT(x, y) \ + ((y) < 0 ? ((x) << -(y)) : ((x) >> (y))) + +#define REG_MASK \ + ((1 << (5 + LOG_REGBYTES)) - (1 << LOG_REGBYTES)) + +#define REG_OFFSET(insn, pos) \ + (SHIFT_RIGHT((insn), (pos) - LOG_REGBYTES) & REG_MASK) + +#define REG_PTR(insn, pos, regs) \ + ((ulong *)((ulong)(regs) + REG_OFFSET(insn, pos))) + +#define GET_RS1(insn, regs) (*REG_PTR(insn, SH_RS1, regs)) +#define GET_RS2(insn, regs) (*REG_PTR(insn, SH_RS2, regs)) +#define GET_RS1S(insn, regs) (*REG_PTR(RVC_RS1S(insn), 0, regs)) +#define GET_RS2S(insn, regs) (*REG_PTR(RVC_RS2S(insn), 0, regs)) +#define GET_RS2C(insn, regs) (*REG_PTR(insn, SH_RS2C, regs)) +#define GET_SP(regs) (*REG_PTR(2, 0, regs)) +#define SET_RD(insn, regs, val) (*REG_PTR(insn, SH_RD, regs) = (val)) +#define IMM_I(insn) ((s32)(insn) >> 20) +#define IMM_S(insn) (((s32)(insn) >> 25 << 5) | \ + (s32)(((insn) >> 7) & 0x1f)) + +#define SH_RD 7 +#define SH_RS1 15 +#define SH_RS2 20 +#define SH_RS2C 2 +#define MASK_RX 0x1f + +#if defined(CONFIG_64BIT) +#define LOG_REGBYTES 3 +#else +#define LOG_REGBYTES 2 +#endif + +#define MASK_FUNCT3 0x7000 + +#define GET_FUNCT3(insn) (((insn) >> 12) & 7) + +#define RV_IMM_SIGN(x) (-(((x) >> 31) & 1)) +#define RVC_IMM_SIGN(x) (-(((x) >> 12) & 1)) +#define RV_X_MASK(X, s, mask) (((X) >> (s)) & (mask)) +#define RV_X(X, s, n) RV_X_MASK(X, s, ((1 << (n)) - 1)) +#define RVC_LW_IMM(x) ((RV_X(x, 6, 1) << 2) | \ + (RV_X(x, 10, 3) << 3) | \ + (RV_X(x, 5, 1) << 6)) +#define RVC_LD_IMM(x) ((RV_X(x, 10, 3) << 3) | \ + (RV_X(x, 5, 2) << 6)) +#define RVC_LWSP_IMM(x) ((RV_X(x, 4, 3) << 2) | \ + (RV_X(x, 12, 1) << 5) | \ + (RV_X(x, 2, 2) << 6)) +#define RVC_LDSP_IMM(x) ((RV_X(x, 5, 2) << 3) | \ + (RV_X(x, 12, 1) << 5) | \ + (RV_X(x, 2, 3) << 6)) +#define RVC_SWSP_IMM(x) ((RV_X(x, 9, 4) << 2) | \ + (RV_X(x, 7, 2) << 6)) +#define RVC_SDSP_IMM(x) ((RV_X(x, 10, 3) << 3) | \ + (RV_X(x, 7, 3) << 6)) +#define RVC_RS1S(insn) (8 + RV_X(insn, SH_RD, 3)) +#define RVC_RS2S(insn) (8 + RV_X(insn, SH_RS2C, 3)) +#define RVC_RS2(insn) RV_X(insn, SH_RS2C, 5) +#define RVC_X(X, s, mask) RV_X_MASK(X, s, mask) + +#define RV_EXTRACT_FUNCT3(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RV_INSN_FUNCT3_OPOFF, \ + RV_INSN_FUNCT3_MASK >> RV_INSN_FUNCT3_OPOFF)); }) + +#define RV_EXTRACT_RS1_REG(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RVG_RS1_OPOFF, RVG_RS1_MASK)); }) + +#define RV_EXTRACT_RS2_REG(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RVG_RS2_OPOFF, RVG_RS2_MASK)); }) + +#define RV_EXTRACT_RD_REG(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RVG_RD_OPOFF, RVG_RD_MASK)); }) + +#define RV_EXTRACT_UTYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RV_U_IMM_31_12_OPOFF, RV_U_IMM_31_12_MASK)); }) + +#define RV_EXTRACT_JTYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RV_J_IMM_10_1_OPOFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OFF) | \ + (RV_X_MASK(x_, RV_J_IMM_11_OPOFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OFF) | \ + (RV_X_MASK(x_, RV_J_IMM_19_12_OPOFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OFF) | \ + (RV_IMM_SIGN(x_) << RV_J_IMM_SIGN_OFF); }) + +#define RV_EXTRACT_ITYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RV_I_IMM_11_0_OPOFF, RV_I_IMM_11_0_MASK)) | \ + (RV_IMM_SIGN(x_) << RV_I_IMM_SIGN_OFF); }) + +#define RV_EXTRACT_BTYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RV_B_IMM_4_1_OPOFF, RV_B_IMM_4_1_MASK) << RV_B_IMM_4_1_OFF) | \ + (RV_X_MASK(x_, RV_B_IMM_10_5_OPOFF, RV_B_IMM_10_5_MASK) << RV_B_IMM_10_5_OFF) | \ + (RV_X_MASK(x_, RV_B_IMM_11_OPOFF, RV_B_IMM_11_MASK) << RV_B_IMM_11_OFF) | \ + (RV_IMM_SIGN(x_) << RV_B_IMM_SIGN_OFF); }) + +#define RVC_EXTRACT_C2_RS1_REG(x) \ + ({typeof(x) x_ = (x); \ + (RV_X_MASK(x_, RVC_C2_RS1_OPOFF, RVC_C2_RS1_MASK)); }) + +#define RVC_EXTRACT_JTYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RVC_X(x_, RVC_J_IMM_3_1_OPOFF, RVC_J_IMM_3_1_MASK) << RVC_J_IMM_3_1_OFF) | \ + (RVC_X(x_, RVC_J_IMM_4_OPOFF, RVC_J_IMM_4_MASK) << RVC_J_IMM_4_OFF) | \ + (RVC_X(x_, RVC_J_IMM_5_OPOFF, RVC_J_IMM_5_MASK) << RVC_J_IMM_5_OFF) | \ + (RVC_X(x_, RVC_J_IMM_6_OPOFF, RVC_J_IMM_6_MASK) << RVC_J_IMM_6_OFF) | \ + (RVC_X(x_, RVC_J_IMM_7_OPOFF, RVC_J_IMM_7_MASK) << RVC_J_IMM_7_OFF) | \ + (RVC_X(x_, RVC_J_IMM_9_8_OPOFF, RVC_J_IMM_9_8_MASK) << RVC_J_IMM_9_8_OFF) | \ + (RVC_X(x_, RVC_J_IMM_10_OPOFF, RVC_J_IMM_10_MASK) << RVC_J_IMM_10_OFF) | \ + (RVC_IMM_SIGN(x_) << RVC_J_IMM_SIGN_OFF); }) + +#define RVC_EXTRACT_BTYPE_IMM(x) \ + ({typeof(x) x_ = (x); \ + (RVC_X(x_, RVC_B_IMM_2_1_OPOFF, RVC_B_IMM_2_1_MASK) << RVC_B_IMM_2_1_OFF) | \ + (RVC_X(x_, RVC_B_IMM_4_3_OPOFF, RVC_B_IMM_4_3_MASK) << RVC_B_IMM_4_3_OFF) | \ + (RVC_X(x_, RVC_B_IMM_5_OPOFF, RVC_B_IMM_5_MASK) << RVC_B_IMM_5_OFF) | \ + (RVC_X(x_, RVC_B_IMM_7_6_OPOFF, RVC_B_IMM_7_6_MASK) << RVC_B_IMM_7_6_OFF) | \ + (RVC_IMM_SIGN(x_) << RVC_B_IMM_SIGN_OFF); }) + +#define RVG_EXTRACT_SYSTEM_CSR(x) \ + ({typeof(x) x_ = (x); RV_X_MASK(x_, RVG_SYSTEM_CSR_OFF, RVG_SYSTEM_CSR_MASK); }) + +#define RVFDQ_EXTRACT_FL_FS_WIDTH(x) \ + ({typeof(x) x_ = (x); RV_X_MASK(x_, RVFDQ_FL_FS_WIDTH_OFF, \ + RVFDQ_FL_FS_WIDTH_MASK); }) + +#define RVV_EXTRACT_VL_VS_WIDTH(x) RVFDQ_EXTRACT_FL_FS_WIDTH(x) + +/* + * Get the immediate from a J-type instruction. + * + * @insn: instruction to process + * Return: immediate + */ +static inline s32 riscv_insn_extract_jtype_imm(u32 insn) +{ + return RV_EXTRACT_JTYPE_IMM(insn); +} + +/* + * Update a J-type instruction with an immediate value. + * + * @insn: pointer to the jtype instruction + * @imm: the immediate to insert into the instruction + */ +static inline void riscv_insn_insert_jtype_imm(u32 *insn, s32 imm) +{ + /* drop the old IMMs, all jal IMM bits sit at 31:12 */ + *insn &= ~GENMASK(31, 12); + *insn |= (RV_X_MASK(imm, RV_J_IMM_10_1_OFF, RV_J_IMM_10_1_MASK) << RV_J_IMM_10_1_OPOFF) | + (RV_X_MASK(imm, RV_J_IMM_11_OFF, RV_J_IMM_11_MASK) << RV_J_IMM_11_OPOFF) | + (RV_X_MASK(imm, RV_J_IMM_19_12_OFF, RV_J_IMM_19_12_MASK) << RV_J_IMM_19_12_OPOFF) | + (RV_X_MASK(imm, RV_J_IMM_SIGN_OFF, 1) << RV_J_IMM_SIGN_OPOFF); +} + +/* + * Put together one immediate from a U-type and I-type instruction pair. + * + * The U-type contains an upper immediate, meaning bits[31:12] with [11:0] + * being zero, while the I-type contains a 12bit immediate. + * Combined these can encode larger 32bit values and are used for example + * in auipc + jalr pairs to allow larger jumps. + * + * @utype_insn: instruction containing the upper immediate + * @itype_insn: instruction + * Return: combined immediate + */ +static inline s32 riscv_insn_extract_utype_itype_imm(u32 utype_insn, u32 itype_insn) +{ + s32 imm; + + imm = RV_EXTRACT_UTYPE_IMM(utype_insn); + imm += RV_EXTRACT_ITYPE_IMM(itype_insn); + + return imm; +} + +/* + * Update a set of two instructions (U-type + I-type) with an immediate value. + * + * Used for example in auipc+jalrs pairs the U-type instructions contains + * a 20bit upper immediate representing bits[31:12], while the I-type + * instruction contains a 12bit immediate representing bits[11:0]. + * + * This also takes into account that both separate immediates are + * considered as signed values, so if the I-type immediate becomes + * negative (BIT(11) set) the U-type part gets adjusted. + * + * @utype_insn: pointer to the utype instruction of the pair + * @itype_insn: pointer to the itype instruction of the pair + * @imm: the immediate to insert into the two instructions + */ +static inline void riscv_insn_insert_utype_itype_imm(u32 *utype_insn, u32 *itype_insn, s32 imm) +{ + /* drop possible old IMM values */ + *utype_insn &= ~(RV_U_IMM_31_12_MASK); + *itype_insn &= ~(RV_I_IMM_11_0_MASK << RV_I_IMM_11_0_OPOFF); + + /* add the adapted IMMs */ + *utype_insn |= (imm & RV_U_IMM_31_12_MASK) + ((imm & BIT(11)) << 1); + *itype_insn |= ((imm & RV_I_IMM_11_0_MASK) << RV_I_IMM_11_0_OPOFF); +} + +/* + * Get the immediate from a B-type instruction. + * + * @insn: instruction to process + * Return: immediate + */ +static inline s32 riscv_insn_extract_btype_imm(u32 insn) +{ + return RV_EXTRACT_BTYPE_IMM(insn); +} + +/* + * Get the immediate from a RVC B-type instruction. + * + * @insn: instruction to process + * Return: immediate + */ +static inline s32 riscv_insn_extract_cbtype_imm(u32 insn) +{ + return RVC_EXTRACT_BTYPE_IMM(insn); +} + +/* + * Get the immediate from a RVC J-type instruction. + * + * @insn: instruction to process + * Return: immediate + */ +static inline s32 riscv_insn_extract_cjtype_imm(u16 insn) +{ + return RVC_EXTRACT_JTYPE_IMM(insn); +} + +#endif /* _ASM_RISCV_INSN_H */ diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c index 5feee198ef97..a5450bef1a8a 100644 --- a/tools/perf/arch/riscv/util/auxtrace.c +++ b/tools/perf/arch/riscv/util/auxtrace.c @@ -10,6 +10,7 @@ #include <linux/zalloc.h> #include <linux/string.h> #include <time.h> +#include <errno.h>
#include <internal/lib.h> // page_size #include "../../../util/auxtrace.h" diff --git a/tools/perf/util/Build b/tools/perf/util/Build index bcccad7487a9..648a8552df9e 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -148,6 +148,8 @@ perf-util-y += cs-etm-decoder/ endif perf-util-y += cs-etm-base.o
+perf-util-y += nexus-rv-decoder/ + perf-util-y += parse-branch-options.o perf-util-y += dump-insn.o perf-util-y += parse-regs-options.o diff --git a/tools/perf/util/nexus-rv-decoder/Build b/tools/perf/util/nexus-rv-decoder/Build new file mode 100644 index 000000000000..61cc960b1d04 --- /dev/null +++ b/tools/perf/util/nexus-rv-decoder/Build @@ -0,0 +1 @@ +perf-util-y += nexus-rv-decoder.o diff --git a/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.c b/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.c new file mode 100644 index 000000000000..2f41d818ca32 --- /dev/null +++ b/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.c @@ -0,0 +1,1364 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/zalloc.h> +#include <linux/types.h> +#include <errno.h> +#include <api/fs/fs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../debug.h" +#include "../color.h" +#include "../intlist.h" +#include "nexus-rv-decoder.h" +#include "nexus-rv-msg.h" +#include "../../../arch/riscv/include/asm/insn.h" + +#define BUFFER_SIZE 1024 + +#define RV_REGNO_ZERO 0 +#define RV_REGNO_RA 1 + +static struct nexus_rv_defmt_buf *nexus_rv_get_buf(struct nexus_rv_defmt_buf defmt_bufs[], + unsigned char id) +{ + if (defmt_bufs[id].buf != NULL) + return &defmt_bufs[id]; + + defmt_bufs[id].buf = (unsigned char *)malloc(BUFFER_SIZE); + if (!defmt_bufs[id].buf) + return NULL; + + defmt_bufs[id].size = 0; + defmt_bufs[id].capacity = BUFFER_SIZE; + + return &defmt_bufs[id]; +} + +static int nexus_rv_defmt_buf_append(struct nexus_rv_defmt_buf *defmt_buf, FILE *nexus, + unsigned char data) +{ + if (defmt_buf->size == defmt_buf->capacity) { + defmt_buf->capacity *= 2; + defmt_buf->buf = realloc(defmt_buf->buf, defmt_buf->capacity); + if (!defmt_buf->buf) + return -ENOMEM; + } + defmt_buf->buf[defmt_buf->size++] = data; + + if ((data & 0x3) == 0x3) { + size_t n = fwrite(defmt_buf->buf, defmt_buf->size, 1, nexus); + + if (n != 1) { + pr_err("Encoder: failed to write nexus data\n"); + return -EINVAL; + } + defmt_buf->size = 0; + } + + return 0; +} + +static size_t skip_frame_syncs(const unsigned char *buf, size_t len, size_t *fsync_count) +{ + const uint32_t FSYNC_PATTERN = 0x7FFFFFFF; /* LE host pattern for Frame SYNC */ + size_t skipped_bytes = 0; + + while (skipped_bytes + 4 <= len) { + if (*((uint32_t *)(buf + skipped_bytes)) == FSYNC_PATTERN) { + skipped_bytes += 4; + (*fsync_count)++; + } else { + break; + } + } + + return skipped_bytes; +} + +/* remove coresight formatter frame */ +static int nexus_rv_pkt_defmt(struct nexus_rv_defmt_buf defmt_bufs[], FILE *nexus, + const unsigned char *buf, size_t len) +{ + int err; + uint8_t flag_bit; + unsigned char data = 0; + int cur_id = -1, old_id = -1, new_id = -1; + + /* 16 bytes is output by coresight trace formatter */ + while (len >= 16) { + /* some linux drivers (e.g. for perf) will insert FSYNCS to pad or differentiate + * between blocks of aligned data, always in frame aligned complete 16 byte frames. + * we need to skip past these frames, resetting as we go. + */ + size_t fsync_count = 0; + size_t fsync_bytes = skip_frame_syncs(buf, len, &fsync_count); + + if (fsync_bytes > 0) { + if (fsync_count % 4 == 0) { + cur_id = -1; + old_id = -1; + new_id = -1; + } else { + pr_err("Incorrect FSYNC reset pattern\n"); + return -EINVAL; + } + buf += fsync_bytes; + len -= fsync_bytes; + continue; + } + + flag_bit = 0x1; + for (int i = 0; i < 15; i += 2) { + if (buf[i] & 0x1) { + /* it's id */ + old_id = new_id; + new_id = buf[i] >> 1; /* get new_id */ + cur_id = (flag_bit & buf[15]) ? old_id : new_id; + } else { + /* it's data */ + data = buf[i] | ((flag_bit & buf[15]) ? 0x1 : 0x0); + } + + if (IS_VALID_ID(cur_id)) { + struct nexus_rv_defmt_buf *defmt_buf = + nexus_rv_get_buf(defmt_bufs, cur_id); + + if (!defmt_buf) + return -ENOMEM; + + /* it's data */ + if ((buf[i] & 0x1) == 0) { + err = nexus_rv_defmt_buf_append(defmt_buf, nexus, data); + if (err) + return err; + } + + /* buf[i+1] is alway data when i != 14 */ + if (i != 14) { + err = nexus_rv_defmt_buf_append(defmt_buf, nexus, + buf[i + 1]); + if (err) + return err; + } + } + + if (cur_id != new_id) + cur_id = new_id; + + flag_bit <<= 1; + } + buf += 16; + len -= 16; + } + return 0; +} + +/* dump all nexus messages (from nexus file) */ +static int nexus_rv_pkt_dump(struct nexus_rv_pkt_decoder *decoder, FILE *nexus) +{ + const char *color = PERF_COLOR_BLUE; + int fld_def = -1; + int fld_bits = 0; + u64 fld_val = 0; + + int msg_cnt = 0; + int msg_bytes = 0; + int msg_errors = 0; + int idle_cnt = 0; + + bool find_next_package = false; + int error_msgs = 0; + + unsigned int tcode = 0; + unsigned int correlation_hist = 0; + unsigned int resourcefull_hrepeat = 0; + + unsigned char msg_byte = 0; + unsigned char prev_byte = 0; + unsigned int mdo = 0; + unsigned int mseo = 0; + + for (;;) { + prev_byte = msg_byte; + if (fread(&msg_byte, 1, 1, nexus) != 1) + break; /* EOF */ + + if (find_next_package) { + if ((prev_byte & 0x3) == 0x3) { /* last byte */ + error_msgs++; + find_next_package = false; + + /* reinit some val */ + fld_def = -1; + fld_bits = 0; + fld_val = 0; + } else { + continue; + } + } + + /* This will skip long sequnece of idles (visible in true captures ...) */ + if (msg_byte == 0xFF && prev_byte == 0xFF) + continue; + + mdo = msg_byte >> 2; + mseo = msg_byte & 0x3; + + if (mseo == 0x2) { + pr_err(" ERROR: At offset %d: MSEO='10' is not allowed\n", + msg_bytes + idle_cnt); + find_next_package = true; + continue; + } + + if (fld_def < 0) { + if (mseo == 0x3) { + color_fprintf(stdout, color, "MSG #%d +%d - IDLE\n", + msg_cnt, msg_bytes); + msg_cnt++; + msg_bytes++; + idle_cnt++; + continue; + } + + if (mseo != 0x0) { + pr_err(" ERROR: At offset %d: Message must start from MSEO='00'\n", + msg_bytes + idle_cnt); + find_next_package = true; + continue; + } + + for (int d = 0; NEXUS_MSG_DEF[d].def != 0; d++) { + if ((NEXUS_MSG_DEF[d].def & 0x100) == 0) + continue; + if ((NEXUS_MSG_DEF[d].def & 0xFF) == mdo) { + fld_def = d; /* Found TCODE */ + tcode = mdo; + break; + } + } + + if (fld_def < 0) { + pr_err(" ERROR: At offset %d: Message with TCODE=%d is not defined for N-Trace\n", + msg_bytes + idle_cnt, mdo); + find_next_package = true; + continue; + } + + color_fprintf(stdout, color, "MSG #%d +%d - %s TCODE[6]=%d", + msg_cnt, msg_bytes, NEXUS_MSG_DEF[fld_def].name, mdo); + msg_cnt++; + msg_bytes++; + + if (mdo == NEXUS_TCODE_Error) + msg_errors++; + + fld_def++; + fld_bits = 0; + fld_val = 0; + continue; + } + + /* Accumulate 'mdo' to field value */ + fld_val |= (((u64)mdo) << fld_bits); + fld_bits += 6; + + msg_bytes++; + + /* Process fixed size fields (there may be more than one in one MDO record) */ + while (NEXUS_MSG_DEF[fld_def].def & 0x200) { + int fld_size = NEXUS_MSG_DEF[fld_def].def & 0xFF; + + if (fld_size & 0x80) { + /* Size of this field is defined by parameter ... */ + fld_size = decoder->src_bits; + } + if (fld_bits < fld_size) + break; /* Not enough bits for this field */ + color_fprintf(stdout, color, " %s[%d]=0x%lx", + NEXUS_MSG_DEF[fld_def].name, fld_size, + fld_val & ((((u64)1) << fld_size) - 1)); + fld_def++; + fld_val >>= fld_size; + fld_bits -= fld_size; + } + + if (mseo == 0x0) + continue; + + if (NEXUS_MSG_DEF[fld_def].def & 0x400) { + /* Process ResourceFull cfg HREPEAT field */ + if (tcode == NEXUS_TCODE_ResourceFull) { + if (!strcmp(NEXUS_MSG_DEF[fld_def].name, "RCODE") + && fld_val == 0x2) + resourcefull_hrepeat = 1; + + if (!strcmp(NEXUS_MSG_DEF[fld_def].name, "HREPEAT") + && resourcefull_hrepeat != 1) { + /* no HREPEAT field */ + fld_def++; + resourcefull_hrepeat = 0; + } + } + + /* Process ProgTraceCorrelation cfg HIST field */ + if (tcode == NEXUS_TCODE_ProgTraceCorrelation) { + if (!strcmp(NEXUS_MSG_DEF[fld_def].name, "CDF") && fld_val == 0x2) + correlation_hist = 1; + + if (!strcmp(NEXUS_MSG_DEF[fld_def].name, "HIST") + && correlation_hist != 1) { + /* no HIST field */ + fld_def++; + correlation_hist = 0; + } + } + + /* Variable size field */ + color_fprintf(stdout, color, " %s[%d]=0x%lx", + NEXUS_MSG_DEF[fld_def].name, fld_bits, fld_val); + + if (mseo == 3) { + printf("\n"); + fld_def = -1; + } else { + fld_def++; + } + fld_bits = 0; + fld_val = 0; + continue; + } + + if (fld_bits > 0) { + pr_err(" ERROR: At offset %d: Not enough bits for non-variable field\n", + msg_bytes + idle_cnt); + find_next_package = true; + continue; + } + } + + color_fprintf(stdout, color, + "\nStat: %d bytes, %d idles, %d messages, %d error messages, %d invalid messages", + msg_bytes, idle_cnt, msg_cnt, msg_errors, error_msgs); + if (msg_cnt > 0) + color_fprintf(stdout, color, "%.2lf bytes/message", ((double)msg_bytes) / msg_cnt); + + printf("\n"); + + return 0; +} + +struct nexus_rv_pkt_decoder *nexus_rv_pkt_decoder_new(struct nexus_rv_pkt_decoder_params *params) +{ + struct nexus_rv_pkt_decoder *decoder; + + if (!params) + return NULL; + + decoder = zalloc(sizeof(struct nexus_rv_pkt_decoder)); + if (!decoder) + return NULL; + + decoder->formatted = params->formatted; + decoder->src_bits = params->src_bits; + + return decoder; +} + +void nexus_rv_pkt_decoder_free(struct nexus_rv_pkt_decoder *decoder) +{ + for (int i = 0; i < MAX_ID; ++i) + free(decoder->defmt_bufs[i].buf); + + free(decoder); +} + +int nexus_rv_pkt_desc(struct nexus_rv_pkt_decoder *decoder, const unsigned char *buf, size_t len) +{ + int err; + char filename[PATH_MAX]; + FILE *nexus; + char *dir = getenv("PERF_BUILDID_DIR"); + + /* TODO: The filename here is always trace.bin, which results in + * only the last Nexus trace data being retained. A corresponding + * Nexus filename should be generated for each AUX data. + */ + snprintf(filename, sizeof(filename), "%s/trace.bin", dir); + nexus = fopen(filename, "w+"); + + if (decoder->formatted) { + err = nexus_rv_pkt_defmt(decoder->defmt_bufs, nexus, buf, len); + if (err) { + pr_err("Encoder: failed to remove coresight trace formatter\n"); + return err; + } + } else { + size_t n = fwrite(buf, len, 1, nexus); + + if (n != 1) { + pr_err("Encoder: failed to write nexus data\n"); + fclose(nexus); + return -EINVAL; + } + } + + fseek(nexus, 0, SEEK_SET); + err = nexus_rv_pkt_dump(decoder, nexus); + + fclose(nexus); + + return err; +} + +static int nexus_rv_init_packet_buffer(struct nexus_rv_packet_buffer *packet_buffer) +{ + if (!packet_buffer->packets) { + packet_buffer->packets = calloc(BUFFER_SIZE, sizeof(struct nexus_rv_packet)); + if (!packet_buffer->packets) + return -ENOMEM; + } + + packet_buffer->size = 0; + packet_buffer->capacity = BUFFER_SIZE; + + return 0; +} + +static int nexus_rv_packet_buffer_append(struct nexus_rv_packet_buffer *packet_buffer, + struct nexus_rv_packet packet) +{ + if (packet_buffer->size == packet_buffer->capacity) { + packet_buffer->capacity *= 2; + packet_buffer->packets = realloc(packet_buffer->packets, + sizeof(struct nexus_rv_packet) * packet_buffer->capacity); + if (!packet_buffer->packets) + return -ENOMEM; + } + packet_buffer->packets[packet_buffer->size++] = packet; + + return 0; +} + +static void nexus_rv_free_packet_buffer(struct nexus_rv_packet_buffer *packet_buffer) +{ + free(packet_buffer->packets); +} + +static int nexus_rv_init_stack(struct nexus_rv_stack *stack) +{ + if (!stack->data) { + stack->data = (u64 *)malloc(sizeof(u64) * BUFFER_SIZE); + if (!stack->data) + return -ENOMEM; + } + + stack->top = -1; + stack->capacity = BUFFER_SIZE; + + return 0; +} + +static int nexus_rv_stack_push(struct nexus_rv_stack *stack, u64 value) +{ + if (stack->top == stack->capacity - 1) { + stack->capacity *= 2; + stack->data = (u64 *)realloc(stack->data, sizeof(u64) * stack->capacity); + if (!stack->data) + return -ENOMEM; + } + stack->data[++stack->top] = value; + + return 0; +} + +static int nexus_rv_stack_pop(struct nexus_rv_stack *stack) +{ + u64 value; + + if (stack->top == -1) + return 1; /* Empty */ + + value = stack->data[stack->top--]; + return value; +} + +static void nexus_rv_free_stack(struct nexus_rv_stack *stack) +{ + free(stack->data); +} + +static struct nexus_rv_src_context *nexus_rv_get_src_context(struct nexus_rv_insn_decoder *decoder, + int src_id) +{ + struct int_node *node; + struct nexus_rv_src_context *ctx; + + node = intlist__find(decoder->src_contexts, src_id); + if (node) + return (struct nexus_rv_src_context *)node->priv; + + ctx = zalloc(sizeof(struct nexus_rv_src_context)); + if (!ctx) + return NULL; + + ctx->src_id = src_id; + ctx->nexdeco_pc = 1; + ctx->nexdeco_lastaddr = 1; + ctx->prv = 0; + ctx->v = 0; + ctx->timestamp = 0; + ctx->context = -1; + ctx->resourcefull_icnt = 0; + + if (nexus_rv_init_stack(&ctx->stack)) { + free(ctx); + return NULL; + } + + /* Add to RB Tree */ + node = intlist__findnew(decoder->src_contexts, src_id); + if (!node) { + nexus_rv_free_stack(&ctx->stack); + free(ctx); + return NULL; + } + + node->priv = ctx; + + return ctx; +} + +static void nexus_rv_free_src_contexts(struct intlist *src_contexts) +{ + struct int_node *node; + + intlist__for_each_entry(node, src_contexts) { + struct nexus_rv_src_context *ctx = (struct nexus_rv_src_context *)node->priv; + + nexus_rv_free_stack(&ctx->stack); + free(ctx); + } + intlist__delete(src_contexts); +} + +static int handle_error_msg(struct nexus_rv_src_context *ctx, const char *err) +{ + pr_err("\nERROR: %s\n", err); + ctx->nexdeco_pc = 1; /* 1 means, that last address is unknown */ + nexus_rv_init_stack(&ctx->stack); + return -EINVAL; +} + +static int nexus_rv_insn_info_get(struct nexus_rv_insn_decoder *decoder, u8 *info, u64 *dest) +{ + u32 insn; + struct nexus_rv_src_context *ctx = decoder->current_src_ctx; + u64 addr = ctx->nexdeco_pc; + + if (!decoder->mem_access(decoder->data, addr, ctx->prv, ctx->context, + sizeof(insn), (u8 *)&insn)) + return -EINVAL; + + *info = INFO_LINEAR; + if ((insn & (__INSN_LENGTH_MASK)) != (__INSN_LENGTH_GE_32)) { + if (riscv_insn_is_c_beqz(insn) || riscv_insn_is_c_bnez(insn)) { + *info = INFO_BRANCH; + *dest = addr + riscv_insn_extract_cbtype_imm(insn); + } else if (riscv_insn_is_c_j(insn)) { + /* c.j offset */ + *info = INFO_JUMP; + *dest = addr + riscv_insn_extract_cjtype_imm(insn); + } else if (riscv_insn_is_c_jr(insn)) { + *info = INFO_JUMP | INFO_INDIRECT; + /* ret => c.jr x1 */ + if (RVC_EXTRACT_C2_RS1_REG(insn) == RV_REGNO_RA) + *info |= INFO_RET; + } else if (riscv_insn_is_c_jalr(insn)) { + *info = INFO_JUMP | INFO_CALL | INFO_INDIRECT; + } else { + *info = INFO_LINEAR; + } + } else { + if (riscv_insn_is_branch(insn)) { + *info = INFO_BRANCH; + *dest = addr + riscv_insn_extract_btype_imm(insn); + } else if (riscv_insn_is_jalr(insn)) { + *info = INFO_JUMP | INFO_INDIRECT; + if (RV_EXTRACT_RD_REG(insn) == RV_REGNO_ZERO + && riscv_insn_extract_jtype_imm(insn) == 0) { + /* ret => jalr x0,x1,0 */ + if (RV_EXTRACT_RS1_REG(insn) == RV_REGNO_RA) + *info |= INFO_RET; + else + /* jr rs1 => jalr x0,rs1,0 */ + *info |= INFO_CALL; + } + } else if (riscv_insn_is_jal(insn)) { + *info = INFO_JUMP; + /* j offset => jal x0,offset */ + if (RV_EXTRACT_RD_REG(insn) != RV_REGNO_ZERO) + *info |= INFO_CALL; + *dest = addr + riscv_insn_extract_jtype_imm(insn); + } else if (riscv_insn_is_sret(insn) || riscv_insn_is_mret(insn)) { + *info = INFO_JUMP | INFO_INDIRECT | INFO_RET; + } else if (riscv_insn_is_ecall(insn)) { + *info = INFO_JUMP | INFO_INDIRECT | INFO_CALL; + } else { + *info = INFO_LINEAR; + } + *info |= INFO_4; + } + + pr_debug2(".nexdeco_pc=0x%lx .insn=0x%0*x\n", addr, + (*info & INFO_4) ? 8 : 4, + (*info & INFO_4) ? insn : (u16)insn); + return 0; +} + +static int nexus_rv_emit_icnt(struct nexus_rv_insn_decoder *decoder, int n, u32 hist) +{ + u64 a = 0; + u32 hist_mask; + u8 info; + int done_icnt = 0; /* Number of done instruction count */ + struct nexus_rv_src_context *ctx = decoder->current_src_ctx; + + if (ctx->nexdeco_pc & 1) + return 0; /* Not synchronized ... */ + + pr_debug2(".PC=0x%lX, EmitICNT(n=%d,hist=0x%x)\n", ctx->nexdeco_pc, n, hist); + + /* + * Adjust ICNT by what was handled by ResourceFull message[s] + * before this message (message with normal ICNT field). + * NOTE: ctx->resourcefull_icnt maybe positive or negative! + */ + if (n >= 0 && ctx->resourcefull_icnt != 0) { + n += ctx->resourcefull_icnt; + if (n < 0) + return handle_error_msg(ctx, "ICNT adjustment ERROR"); + + ctx->resourcefull_icnt = 0; /* Make adjustment 'consumed' */ + } + + hist_mask = 0; /* MSB is first in history, so we need sliding mask */ + if (hist != 0) { + if (hist & (1 << (sizeof(u32) * 8 - 1))) { + hist_mask = (1 << (sizeof(u32) * 8 - 2)); + } else { + hist_mask = 0x1; + while (hist_mask <= hist) + hist_mask <<= 1; + hist_mask >>= 2; + } + } + + while (n != 0) { + ctx->current_pc = ctx->nexdeco_pc; + if (nexus_rv_insn_info_get(decoder, &info, &a)) + return handle_error_msg(ctx, "failed to get insn info"); + + if (n > 0) { + n -= (info & INFO_4) ? 2 : 1; + if (n < 0) + return handle_error_msg(ctx, "ICNT too small"); + done_icnt += 1; + } + + if (info & INFO_CALL) { + u64 ret = ctx->nexdeco_pc + ((info & INFO_4) ? 4 : 2); + + if (nexus_rv_stack_push(&ctx->stack, ret)) + return handle_error_msg(ctx, "failed to push value to stack"); + } + + if (info & INFO_INDIRECT) { /* Cannot continue over indirect... */ + if (info & INFO_RET) { + u64 ret = nexus_rv_stack_pop(&ctx->stack); + + ctx->nexdeco_pc = ret; + if (n != 0) { + if (ret == 1) + return handle_error_msg(ctx, + "Not enough entries on callstack"); + continue; + } + } + + if (n > 0) + return handle_error_msg(ctx, + "indirect address encountered in ICNT"); + + break; + } + + if (info & INFO_BRANCH) { + if (hist == 0) { + /* This is calling as DirectBranch */ + if (n == 0) + info |= INFO_JUMP; /* Force PC change below */ + } else { + if (hist_mask & hist) + info |= INFO_JUMP; /* Force PC change below */ + hist_mask >>= 1; + if (hist_mask == 0 && n < 0) + n = 0; + } + } + + if (info & INFO_JUMP) + ctx->nexdeco_pc = a; /* Direct jump/call/branch */ + else if (info & INFO_4) + ctx->nexdeco_pc += 4; + else + ctx->nexdeco_pc += 2; + + } + + return done_icnt; +} + +static u64 nexus_rv_field_get(struct nexus_rv_insn_decoder *decoder, const char *name) +{ + for (int d = decoder->msg_field_pos; NEXUS_MSG_DEF[d].def != 1; d++) { + if (strcmp(NEXUS_MSG_DEF[d].name, name) == 0) { + int fi = d - decoder->msg_field_pos; + + if (fi <= decoder->msg_field_cnt) + return decoder->msg_fields[fi]; + return 0; + } + } + return 0; +} + +#define NEX_FLDGET(n) nexus_rv_field_get(decoder, #n) + +static u64 nexus_rv_calculate_addr(u64 fu_addr, int full, u64 prev_addr) +{ + fu_addr <<= 1; /* LSB bit is never sent */ + if ((fu_addr >> 32) & 0x10000) /* Perform MSB extension */ + fu_addr |= 0xFFFF000000000000UL; + /* Update (NEW or XOR) */ + if (!full) { + if (prev_addr & 1) /* Not Sync */ + fu_addr = prev_addr; + else + fu_addr ^= prev_addr; + } + return fu_addr; +} + +static int nexus_rv_msg_handle(struct nexus_rv_insn_decoder *decoder) +{ + int ret = 0; + u64 addr; + struct nexus_rv_packet packet; + int n = 0; + int new_src = 0; + u64 start_addr = 0; + int TCODE = decoder->msg_fields[0]; + + switch (TCODE) { + case NEXUS_TCODE_Ownership: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + decoder->current_src_ctx->prv = NEX_FLDGET(PRV); + decoder->current_src_ctx->v = NEX_FLDGET(V); + if (NEX_FLDGET(FORMAT) && NEX_FLDGET(CONTEXT)) + decoder->current_src_ctx->context = NEX_FLDGET(CONTEXT); + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_DirectBranch: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, 0x0); + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_IndirectBranch: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, 0x0); + + addr = NEX_FLDGET(UADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 0, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_ProgTraceSync: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, 0x0); + + addr = NEX_FLDGET(FADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 1, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_DirectBranchSync: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, 0x0); + + addr = NEX_FLDGET(FADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 1, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_IndirectBranchSync: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, 0x0); + + addr = NEX_FLDGET(FADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 1, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_IndirectBranchHist: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, NEX_FLDGET(HIST)); + + addr = NEX_FLDGET(UADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 0, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_IndirectBranchHistSync: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, NEX_FLDGET(HIST)); + + addr = NEX_FLDGET(FADDR); + decoder->current_src_ctx->nexdeco_lastaddr = nexus_rv_calculate_addr(addr, 1, + decoder->current_src_ctx->nexdeco_lastaddr); + pr_debug2(".nexdeco_lastaddr=0x%lx\n", decoder->current_src_ctx->nexdeco_lastaddr); + decoder->current_src_ctx->nexdeco_pc = decoder->current_src_ctx->nexdeco_lastaddr; + decoder->current_src_ctx->timestamp = NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_ResourceFull: + /* Determine repeat count (for RCODE=2) */ + int hrepeat = 0; + int rcode = NEX_FLDGET(RCODE); + + if (rcode == 2) + hrepeat = NEX_FLDGET(HREPEAT); + + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + if (rcode == 1 || rcode == 2) { + int rdata = NEX_FLDGET(RDATA); + + if (rdata > 1) { + /* Special calling to emit HIST only ... */ + if (decoder->disp_hist_repeat) { + pr_debug2("RepeatHIST,0x%x,%d\n", + rdata, decoder->disp_hist_repeat); + decoder->disp_hist_repeat = 0; + } + do { + /* ICNT is unknown (-1), what will process only HIST bits */ + ret = nexus_rv_emit_icnt(decoder, -1, rdata); + if (ret < 0) + break; + + /* Consume, so next time ICNT will be adjusted */ + decoder->current_src_ctx->resourcefull_icnt -= ret; + hrepeat--; + } while (hrepeat > 0); + } + } else if (rcode == 0) { + decoder->current_src_ctx->resourcefull_icnt += NEX_FLDGET(RDATA); + } + break; + + case NEXUS_TCODE_ProgTraceCorrelation: + int hist = 0; + int cdf = NEX_FLDGET(CDF); + + if (cdf == 1) + hist = NEX_FLDGET(HIST); + + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + + start_addr = decoder->current_src_ctx->nexdeco_pc; + n = NEX_FLDGET(ICNT); + ret = nexus_rv_emit_icnt(decoder, n, hist); + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_Error: + new_src = NEX_FLDGET(SRC); + if (new_src != decoder->current_src) { + struct nexus_rv_src_context *ctx = nexus_rv_get_src_context(decoder, + new_src); + if (!ctx) + return -ENOMEM; + + decoder->current_src = new_src; + decoder->current_src_ctx = ctx; + } + decoder->current_src_ctx->timestamp += NEX_FLDGET(TSTAMP); + break; + + case NEXUS_TCODE_RepeatBranch: /* Handled differently! */ + default: + return -EINVAL; + } + + if (ret >= 0) { + if (NEXUS_TCODE_Error == TCODE) + packet.sample_type = RVTRACE_LOSS; + else + packet.sample_type = ret ? RVTRACE_RANGE : RVTRACE_EMPTY; + packet.start_addr = start_addr; + packet.end_addr = decoder->current_src_ctx->current_pc; + packet.insn_cnt = ret; + packet.cpu = new_src; + packet.prv = decoder->current_src_ctx->prv; + packet.v = decoder->current_src_ctx->v; + packet.context = decoder->current_src_ctx->context; + packet.timestamp = decoder->current_src_ctx->timestamp; + } else { + packet.sample_type = RVTRACE_ERROR; + } + + ret = nexus_rv_packet_buffer_append(&decoder->packet_buffer, packet); + + return ret; +} + +static int nexus_rv_insn_decode(struct nexus_rv_insn_decoder *decoder, FILE *nexus) +{ + int fld_def = -1; + int fld_bits = 0; + u64 fld_val = 0; + + int msg_cnt = 0; + int msg_bytes = 0; + int msg_errors = 0; + + bool find_next_package = false; + + unsigned char msg_byte = 0; + unsigned char prev_byte = 0; + + unsigned int mdo = 0; + unsigned int mseo = 0; + + decoder->msg_field_cnt = 0; /* No fields */ + + for (;;) { + prev_byte = msg_byte; + if (fread(&msg_byte, 1, 1, nexus) != 1) + break; /* EOF */ + + if (find_next_package) { + if ((prev_byte & 0x3) == 0x3) { /* last byte */ + find_next_package = false; + + /* reinit some val */ + fld_def = -1; + fld_bits = 0; + fld_val = 0; + } else { + continue; + } + } + + /* This will skip long sequnece of idles (visible in true captures ...) */ + if (msg_byte == 0xFF && prev_byte == 0xFF) + continue; + + mdo = msg_byte >> 2; + mseo = msg_byte & 0x3; + + if (mseo == 0x2) { + pr_err("ERROR: MSEO='10' is not allowed\n"); + find_next_package = true; + continue; + } + + if (fld_def < 0) { + if (mseo == 0x3) + continue; /* skip idle */ + + if (mseo != 0x0) { + pr_err("ERROR: Message must start from MSEO='00'\n"); + find_next_package = true; + continue; + } + + for (int d = 0; NEXUS_MSG_DEF[d].def != 0; d++) { + if ((NEXUS_MSG_DEF[d].def & 0x100) == 0) + continue; + if ((NEXUS_MSG_DEF[d].def & 0xFF) == mdo) { + fld_def = d; /* Found TCODE */ + break; + } + } + + if (fld_def < 0) { + pr_err("ERROR: Message with TCODE=%d is not defined for RISC-V\n", + mdo); + find_next_package = true; + continue; + } + + /* + * Special handling for RepeatBranch message. + * We want to preserve previous packet, so we can + * repeat it at end of RepeatBranch handling. + */ + if (mdo == NEXUS_TCODE_RepeatBranch) { + /* Save previous message fields */ + decoder->saved_fields[0] = decoder->msg_field_pos; + decoder->saved_fields[1] = decoder->msg_field_cnt; + decoder->saved_fields[2] = decoder->msg_fields[0]; + decoder->saved_fields[3] = decoder->msg_fields[1]; + decoder->saved_fields[4] = decoder->msg_fields[2]; + decoder->saved_fields[5] = decoder->msg_fields[3]; + decoder->saved_fields[6] = decoder->msg_fields[4]; + } + + /* Save to allow later decoding */ + decoder->msg_field_pos = fld_def; + decoder->msg_field_cnt = 0; + decoder->msg_fields[decoder->msg_field_cnt++] = mdo; + + msg_cnt++; + msg_bytes++; + + if (mdo == NEXUS_TCODE_Error) + msg_errors++; + + fld_def++; + fld_bits = 0; + fld_val = 0; + continue; + } + + /* Accumulate 'mdo' to field value */ + fld_val |= (((u64)mdo) << fld_bits); + fld_bits += 6; + + msg_bytes++; + + /* Process fixed size fields (there may be more than one in one MDO record) */ + while (NEXUS_MSG_DEF[fld_def].def & 0x200) { + int fld_size = NEXUS_MSG_DEF[fld_def].def & 0xFF; + + if (fld_size & 0x80) + fld_size = decoder->src_bits; + if (fld_bits < fld_size) + break; /* Not enough bits for this field */ + + /* Save field */ + decoder->msg_fields[decoder->msg_field_cnt++] = + fld_val & ((((u64)1) << fld_size) - 1); + + fld_def++; + fld_val >>= fld_size; + fld_bits -= fld_size; + } + + if (mseo == 0x0) + continue; + + if (NEXUS_MSG_DEF[fld_def].def & 0x400) { + /* Variable size field */ + decoder->msg_fields[decoder->msg_field_cnt++] = fld_val; /* Save fieliid */ + + if (mseo == 3) { + int cnt = 1; + + decoder->disp_hist_repeat = 0; + + if (decoder->msg_fields[0] == NEXUS_TCODE_RepeatBranch) { + /* Special handling for repeat branch (which only + * has 1 field!) + */ + /* Counter set in RepeatBranch message */ + cnt = decoder->msg_fields[1]; + decoder->msg_field_pos = decoder->saved_fields[0]; + decoder->msg_field_cnt = decoder->saved_fields[1]; + decoder->msg_fields[0] = decoder->saved_fields[2]; + decoder->msg_fields[1] = decoder->saved_fields[3]; + decoder->msg_fields[2] = decoder->saved_fields[4]; + decoder->msg_fields[3] = decoder->saved_fields[5]; + decoder->msg_fields[4] = decoder->saved_fields[6]; + decoder->disp_hist_repeat = cnt; + } + + while (cnt > 0) { /* Handle (1 or many times ...) */ + int err = nexus_rv_msg_handle(decoder); + + if (err < 0) + return err; + cnt--; + } + + fld_def = -1; + } else { + fld_def++; + } + fld_bits = 0; + fld_val = 0; + continue; + } + + if (fld_bits > 0) { + pr_err("Decode: Not enough bits for non-variable field\n"); + find_next_package = true; + continue; + } + } + + return 0; +} + +struct nexus_rv_insn_decoder *nexus_rv_insn_decoder_new(struct nexus_rv_insn_decoder_params *params) +{ + int err; + struct nexus_rv_insn_decoder *decoder; + + if (!params) + return NULL; + + decoder = zalloc(sizeof(struct nexus_rv_insn_decoder)); + if (!decoder) + return NULL; + + decoder->mem_access = params->mem_access; + decoder->data = params->data; + decoder->formatted = params->formatted; + decoder->src_bits = params->src_bits; + decoder->current_src = -1; + + decoder->src_contexts = intlist__new(NULL); + if (!decoder->src_contexts) + goto err_out; + + err = nexus_rv_init_packet_buffer(&decoder->packet_buffer); + if (err) + goto err_out; + + return decoder; + +err_out: + nexus_rv_free_src_contexts(decoder->src_contexts); + nexus_rv_free_packet_buffer(&decoder->packet_buffer); + free(decoder); + return NULL; +} + +void nexus_rv_insn_decoder_free(struct nexus_rv_insn_decoder *decoder) +{ + for (int i = 0; i < MAX_ID; ++i) + free(decoder->defmt_bufs[i].buf); + + nexus_rv_free_src_contexts(decoder->src_contexts); + nexus_rv_free_packet_buffer(&decoder->packet_buffer); + free(decoder); +} + +int nexus_rv_insn_decoder_reset(struct nexus_rv_insn_decoder *decoder) +{ + int err; + struct int_node *node; + struct nexus_rv_src_context *ctx; + + if (decoder->src_contexts) { + intlist__for_each_entry(node, decoder->src_contexts) { + ctx = (struct nexus_rv_src_context *)node->priv; + ctx->nexdeco_pc = 1; + ctx->nexdeco_lastaddr = 1; + ctx->prv = 0; + ctx->v = 0; + ctx->timestamp = 0; + ctx->context = -1; + ctx->resourcefull_icnt = 0; + nexus_rv_init_stack(&ctx->stack); + } + } + decoder->current_src_ctx = NULL; + decoder->current_src = -1; + + err = nexus_rv_init_packet_buffer(&decoder->packet_buffer); + if (err) + return err; + + return 0; +} + +int nexus_rv_insn_decode_data(struct nexus_rv_insn_decoder *decoder, + const unsigned char *buf, size_t size) +{ + int err; + char filename[PATH_MAX]; + FILE *nexus; + char *dir = getenv("PERF_BUILDID_DIR"); + + /* TODO: The filename here is always trace.bin, which results in + * only the last Nexus trace data being retained. A corresponding + * Nexus filename should be generated for each AUX data. + */ + snprintf(filename, sizeof(filename), "%s/trace.bin", dir); + nexus = fopen(filename, "w+"); + + if (decoder->formatted) { + err = nexus_rv_pkt_defmt(decoder->defmt_bufs, nexus, buf, size); + if (err) { + pr_err("Encoder: failed to remove coresight trace formatter\n"); + fclose(nexus); + return err; + } + } else { + size_t n = fwrite(buf, size, 1, nexus); + + if (n != 1) { + pr_err("Encoder: failed to write nexus data\n"); + fclose(nexus); + return -EINVAL; + } + } + + fseek(nexus, 0, SEEK_SET); + err = nexus_rv_insn_decode(decoder, nexus); + + fclose(nexus); + + return err; +} diff --git a/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.h b/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.h new file mode 100644 index 000000000000..1595b59f3b3c --- /dev/null +++ b/tools/perf/util/nexus-rv-decoder/nexus-rv-decoder.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + * Author: liangzhen zhen.liang@spacemit.com + */ + +#ifndef INCLUDE__NEXUS_RV_DECODER_H__ +#define INCLUDE__NEXUS_RV_DECODER_H__ + +#define INFO_LINEAR 0x1 // Linear (plain instruction or not taken BRANCH) +#define INFO_4 0x2 // If not 4, it must be 2 on RISC-V +// 0x4 // Reserved (for exception or so ...) +#define INFO_INDIRECT 0x8 // Possible for most types above +#define INFO_BRANCH 0x10 // Always direct on RISC-V (may have LINEAR too) +#define INFO_JUMP 0x20 // Direct or indirect +#define INFO_CALL 0x40 // Direct or indirect (and always a jump) +#define INFO_RET 0x80 // Return (always indirect and always a jump) + +#define MAX_ID 112 // Values of 0x00 and 0x70-0x7F are reserved by the ATB specification +#define MSGFIELDS_MAX 10 +#define INSN_SZ 16 + +/* check an ID is in the valid range */ +#define IS_VALID_ID(id) \ + ((id > 0) && (id < MAX_ID)) + +struct rvtrace_queue; + +struct nexus_rv_defmt_buf { + unsigned char *buf; + size_t size; + size_t capacity; +}; + +struct nexus_rv_pkt_decoder { + bool formatted; + struct nexus_rv_defmt_buf defmt_bufs[MAX_ID]; + u32 src_bits; +}; + +struct nexus_rv_pkt_decoder_params { + bool formatted; + u32 src_bits; +}; + +struct nexus_rv_stack { + u64 *data; + int top; + int capacity; +}; + +enum rvtrace_sample_type { + RVTRACE_EMPTY, + RVTRACE_RANGE, + RVTRACE_LOSS, + RVTRACE_ERROR, +}; + +enum riscv_privilege_mode { + RISCV_PRIV_USER_MODE, + RISCV_PRIV_SUPERVISOR_MODE, + RISCV_PRIV_MACHINE_MODE = 3, +}; + +typedef u32 (*rvtrace_mem_access_type)(void *, u64, enum riscv_privilege_mode, + int, size_t, u8 *); + +struct nexus_rv_packet { + enum rvtrace_sample_type sample_type; + u64 start_addr; + u64 end_addr; + u32 insn_cnt; + int cpu; + enum riscv_privilege_mode prv; + bool v; + int context; + u64 timestamp; +}; + +struct nexus_rv_packet_buffer { + struct nexus_rv_packet *packets; + int size; + int capacity; +}; + +struct nexus_rv_src_context { + int src_id; + u64 nexdeco_pc; + u64 nexdeco_lastaddr; + u64 current_pc; + u64 timestamp; + enum riscv_privilege_mode prv; + bool v; + int context; + int resourcefull_icnt; + struct nexus_rv_stack stack; +}; + +struct nexus_rv_insn_decoder { + rvtrace_mem_access_type mem_access; + void *data; + bool formatted; + struct nexus_rv_defmt_buf defmt_bufs[MAX_ID]; + u32 src_bits; + int msg_field_pos; + u64 msg_fields[MSGFIELDS_MAX]; + u64 saved_fields[MSGFIELDS_MAX]; + int msg_field_cnt; + int disp_hist_repeat; + int current_src; + struct intlist *src_contexts; + struct nexus_rv_src_context *current_src_ctx; + struct nexus_rv_packet_buffer packet_buffer; +}; + +struct nexus_rv_insn_decoder_params { + rvtrace_mem_access_type mem_access; + void *data; + bool formatted; + u32 src_bits; +}; + +struct nexus_rv_pkt_decoder *nexus_rv_pkt_decoder_new(struct nexus_rv_pkt_decoder_params *params); + +void nexus_rv_pkt_decoder_free(struct nexus_rv_pkt_decoder *decoder); + +int nexus_rv_pkt_desc(struct nexus_rv_pkt_decoder *decoder, const unsigned char *buf, size_t len); + +struct nexus_rv_insn_decoder *nexus_rv_insn_decoder_new( + struct nexus_rv_insn_decoder_params *params); + +void nexus_rv_insn_decoder_free(struct nexus_rv_insn_decoder *decoder); + +int nexus_rv_insn_decoder_reset(struct nexus_rv_insn_decoder *decoder); + +int nexus_rv_insn_decode_data(struct nexus_rv_insn_decoder *decoder, + const unsigned char *buf, size_t size); + +#endif diff --git a/tools/perf/util/nexus-rv-decoder/nexus-rv-msg.h b/tools/perf/util/nexus-rv-decoder/nexus-rv-msg.h new file mode 100644 index 000000000000..90d95f2c5f5f --- /dev/null +++ b/tools/perf/util/nexus-rv-decoder/nexus-rv-msg.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 IAR Systems AB. + */ + +#ifndef INCLUDE__NEXUS_RV_MSG_H__ +#define INCLUDE__NEXUS_RV_MSG_H__ + +/****************************************************************************/ +/* Nexus RISC-V Trace header. */ + +// Header with common Nexus Trace definitions + +#include <stdint.h> // For uint32_t and uint64_t + +/****************************************************************************/ +/* Nexus specific values (based on Nexus Standard PDF) */ + +#define NEXUS_FLDSIZE_TCODE 6 // This is standard + +// Nexus TCODE values applicable to RISC-V +#define NEXUS_TCODE_Ownership 2 +#define NEXUS_TCODE_DirectBranch 3 +#define NEXUS_TCODE_IndirectBranch 4 +#define NEXUS_TCODE_Error 8 +#define NEXUS_TCODE_ProgTraceSync 9 +#define NEXUS_TCODE_DirectBranchSync 11 +#define NEXUS_TCODE_IndirectBranchSync 12 +#define NEXUS_TCODE_ResourceFull 27 +#define NEXUS_TCODE_IndirectBranchHist 28 +#define NEXUS_TCODE_IndirectBranchHistSync 29 +#define NEXUS_TCODE_RepeatBranch 30 +#define NEXUS_TCODE_ProgTraceCorrelation 33 + +// End of standard values +/****************************************************************************/ + +/****************************************************************************/ +/* RISC-V Nexus Trace related values (most 'recommended' by Nexus) */ + +// Sizes of fields: +#define NEXUS_FLDSIZE_BTYPE 2 // Branch type +#define NEXUS_FLDSIZE_SYNC 4 // Synchronization code +#define NEXUS_FLDSIZE_ETYPE 4 // Error type +#define NEXUS_FLDSIZE_EVCODE 4 // Event code (correlation) +#define NEXUS_FLDSIZE_CDF 2 +#define NEXUS_FLDSIZE_RCODE 4 // Resource full code size +// from PROCESS fields: +#define NEXUS_FLDSIZE_FORMAT 2 +#define NEXUS_FLDSIZE_PRV 2 +#define NEXUS_FLDSIZE_V 1 + +#define NEXUS_PAR_SIZE_SRC 0 // SRC field size is defined by parameter #0 + +#define NEXUS_HIST_BITS 31 // Number of valid HIST bits + +// Address skipping (on RISC-V LSB of PC is always 0, so it is not encoded) +#define NEXUS_PARAM_AddrSkip 1 +#define NEXUS_PARAM_AddrUnit 1 + +// End of RISC-V related values +/****************************************************************************/ + +// Nexus RISC-V Trace message definitions (dump & decode) + +// Macros to define Nexus Messages (NEXM_...) +// NOTE: These macros refer to 'NEXUS_TCODE_...' and 'NEXUS_FLDSIZE_...' +// It is also possible to NOT do it, but it provides less flexibility. +// +// name def (marker | value) +//#define NEXM_BEG(n, t) {#n, 0x100 | (t) } +#define NEXM_BEG(n) {#n, 0x100 | (NEXUS_TCODE_##n) } \ + // , NEXM_FLD_PAR(SRC) // SRC is always following TCODE +//#define NEXM_FLD(n, s) {#n, 0x200 | (s) } +#define NEXM_FLD(n) {#n, 0x200 | (NEXUS_FLDSIZE_##n) } +// 0x80 means size is a parameter +#define NEXM_FLD_PAR(n) {#n, 0x200 | 0x80 | (NEXUS_PAR_SIZE_##n) } +#define NEXM_VAR(n) {#n, 0x400 } +#define NEXM_ADR(n) {#n, 0xC00 } +#define NEXM_END() {NULL, 1 } + +// Definition of Nexus Messages (subset applicable to RISC-V PC trace) +static struct NEXM_MSGDEF_STRU { + const char *name; // Name of message/field + int def; // Definition of field (see NEXM_... above) +} NEXUS_MSG_DEF[] = { + + NEXM_BEG(Ownership), + NEXM_FLD_PAR(SRC), + //NEXM_VAR(PROCESS), + NEXM_FLD(FORMAT), + NEXM_FLD(PRV), + NEXM_FLD(V), + NEXM_VAR(CONTEXT), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(DirectBranch), + NEXM_FLD_PAR(SRC), + NEXM_VAR(ICNT), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(IndirectBranch), + NEXM_FLD_PAR(SRC), + NEXM_FLD(BTYPE), + NEXM_VAR(ICNT), + NEXM_ADR(UADDR), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(Error), + NEXM_FLD_PAR(SRC), + NEXM_FLD(ETYPE), + NEXM_VAR(PAD), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(ProgTraceSync), + NEXM_FLD_PAR(SRC), + NEXM_FLD(SYNC), + NEXM_VAR(ICNT), + NEXM_ADR(FADDR), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(DirectBranchSync), + NEXM_FLD_PAR(SRC), + NEXM_FLD(SYNC), + NEXM_VAR(ICNT), + NEXM_ADR(FADDR), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(IndirectBranchSync), + NEXM_FLD_PAR(SRC), + NEXM_FLD(SYNC), + NEXM_FLD(BTYPE), + NEXM_VAR(ICNT), + NEXM_ADR(FADDR), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(ResourceFull), + NEXM_FLD_PAR(SRC), + NEXM_FLD(RCODE), + NEXM_VAR(RDATA), + NEXM_VAR(HREPEAT), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(IndirectBranchHist), + NEXM_FLD_PAR(SRC), + NEXM_FLD(BTYPE), + NEXM_VAR(ICNT), + NEXM_ADR(UADDR), + NEXM_VAR(HIST), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(IndirectBranchHistSync), + NEXM_FLD_PAR(SRC), + NEXM_FLD(SYNC), + NEXM_FLD(BTYPE), + // NEXM_FLD(CANCEL), + NEXM_VAR(ICNT), + NEXM_ADR(FADDR), + NEXM_VAR(HIST), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(RepeatBranch), + NEXM_FLD_PAR(SRC), + NEXM_VAR(BCNT), + NEXM_VAR(TSTAMP), + NEXM_END(), + + NEXM_BEG(ProgTraceCorrelation), + NEXM_FLD_PAR(SRC), + NEXM_FLD(EVCODE), + NEXM_FLD(CDF), + NEXM_VAR(ICNT), + NEXM_VAR(HIST), // Only if CDF=1! + NEXM_VAR(TSTAMP), + NEXM_END(), + + { NULL, 0 } // End-marker ('def == 0' is not otherwise used - see NEXM_...) +}; + +#endif
From: liangzhen zhen.liang@spacemit.com
Add PLT header and entry size definitions for RISC-V architecture.
RISC-V uses: - PLT header size: 32 bytes - PLT entry size: 16 bytes
This allows perf to correctly identify and display PLT symbols instead of showing them as [unknown].
Signed-off-by: liangzhen zhen.liang@spacemit.com --- tools/perf/util/symbol-elf.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 76912c62b6a0..e54ff59eb6ef 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -379,6 +379,10 @@ static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt, *plt_header_size = 32; *plt_entry_size = 16; return true; + case EM_RISCV: + *plt_header_size = 32; + *plt_entry_size = 16; + return true; case EM_SPARC: *plt_header_size = 48; *plt_entry_size = 12;
From: liangzhen zhen.liang@spacemit.com
Add RISC-V trace decoder implementation and integrate it into perf's auxtrace infrastructure. This enables processing of RISC-V Nexus trace data through the standard perf auxtrace pipeline.
Signed-off-by: liangzhen zhen.liang@spacemit.com --- tools/perf/util/Build | 1 + tools/perf/util/auxtrace.c | 3 + tools/perf/util/rvtrace-decoder.c | 1039 +++++++++++++++++++++++++++++ tools/perf/util/rvtrace.h | 2 + 4 files changed, 1045 insertions(+) create mode 100644 tools/perf/util/rvtrace-decoder.c
diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 648a8552df9e..5913eec9b847 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -148,6 +148,7 @@ perf-util-y += cs-etm-decoder/ endif perf-util-y += cs-etm-base.o
+perf-util-y += rvtrace-decoder.o perf-util-y += nexus-rv-decoder/
perf-util-y += parse-branch-options.o diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index 944a43d48739..7539252a4543 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -56,6 +56,7 @@ #include "s390-cpumsf.h" #include "util/mmap.h" #include "powerpc-vpadtl.h" +#include "rvtrace.h"
#include <linux/ctype.h> #include "symbol/kallsyms.h" @@ -1412,6 +1413,8 @@ int perf_event__process_auxtrace_info(const struct perf_tool *tool __maybe_unuse err = powerpc_vpadtl_process_auxtrace_info(event, session); break; case PERF_AUXTRACE_RISCV_TRACE: + err = rvtrace_process_auxtrace_info(event, session); + break; case PERF_AUXTRACE_UNKNOWN: default: return -EINVAL; diff --git a/tools/perf/util/rvtrace-decoder.c b/tools/perf/util/rvtrace-decoder.c new file mode 100644 index 000000000000..ce103783d0c3 --- /dev/null +++ b/tools/perf/util/rvtrace-decoder.c @@ -0,0 +1,1039 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2026 Spacemit Limited. All rights reserved. + * Author: liangzhen zhen.liang@spacemit.com + */ + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#include <linux/types.h> +#include <sys/mman.h> + +#include <stdlib.h> + +#include "auxtrace.h" +#include "color.h" +#include "debug.h" +#include "evlist.h" +#include "intlist.h" +#include "machine.h" +#include "map.h" +#include "perf.h" +#include "session.h" +#include "tool.h" +#include "thread.h" +#include "thread_map.h" +#include "thread-stack.h" +#include "util.h" +#include "dso.h" +#include "addr_location.h" +#include <inttypes.h> +#include "util/synthetic-events.h" +#include "rvtrace.h" +#include "nexus-rv-decoder/nexus-rv-decoder.h" +#include "../../arch/riscv/include/asm/insn.h" + +struct rvtrace_auxtrace { + struct auxtrace auxtrace; + struct auxtrace_queues queues; + struct auxtrace_heap heap; + struct itrace_synth_opts synth_opts; + struct perf_session *session; + struct machine *machine; + + u8 timeless_decoding; + u8 snapshot_mode; + u8 data_queued; + + int num_cpu; + u64 latest_kernel_timestamp; + u32 auxtrace_type; + u64 branches_sample_type; + u64 branches_id; + u64 **metadata; + unsigned int pmu_type; +}; + +struct rvtrace_queue { + struct rvtrace_auxtrace *rvtrace; + struct thread *thread; + struct nexus_rv_insn_decoder *decoder; + struct auxtrace_buffer *buffer; + union perf_event *event_buf; + unsigned int queue_nr; + u64 offset; +}; + +static void rvtrace_set_thread(struct rvtrace_queue *rvtraceq, + pid_t tid) +{ + struct rvtrace_auxtrace *rvtrace = rvtraceq->rvtrace; + + if (tid != -1) { + thread__zput(rvtraceq->thread); + rvtraceq->thread = machine__find_thread(rvtrace->machine, -1, tid); + } + + /* Couldn't find a known thread */ + if (!rvtraceq->thread) + rvtraceq->thread = machine__idle_thread(rvtrace->machine); +} + +static u32 rvtrace_devmem_access(u64 address, size_t size, u8 *buffer) +{ + int fd; + void *map_base, *virt_addr; + u64 page_size = 4096, mapped_size = 4096; + u64 page_base = address & ~(page_size - 1); + u64 offset_in_page = address - page_base; + unsigned int width = 8 * size; + + if (offset_in_page + width > page_size) + mapped_size *= 2; + + fd = open("/dev/mem", O_RDONLY | O_SYNC); + if (fd < 0) + return 0; + + map_base = mmap(NULL, mapped_size, PROT_READ, MAP_SHARED, fd, (off_t)(page_base)); + if (map_base == MAP_FAILED) { + pr_debug("failed to mmap device address 0x%lx\n", address); + close(fd); + return 0; + } + + virt_addr = (char *)map_base + offset_in_page; + + /* + * Note: higher versions of glibc use automatic vectorization by + * default for memcpy, which can lead to incorrect memory results. + */ + /* memcpy(buffer, virt_addr, size); */ + for (size_t i = 0; i < size; i++) + buffer[i] = ((volatile u8 *)virt_addr)[i]; + + munmap(map_base, mapped_size); + close(fd); + return size; +} + +static u32 rvtrace_mem_access(void *data, u64 address, enum riscv_privilege_mode prv, + int context, size_t size, u8 *buffer) +{ + u8 cpumode; + u64 offset; + int len; + struct machine *machine; + struct addr_location al; + struct dso *dso; + int ret = 0; + struct rvtrace_queue *rvtraceq = data; + + if (!rvtraceq) + goto out; + + addr_location__init(&al); + + /* If the riscv_privilege_mode is machine mode, access physical address by /dev/mem */ + if (prv == RISCV_PRIV_MACHINE_MODE) + return rvtrace_devmem_access(address, size, buffer); + + machine = rvtraceq->rvtrace->machine; + if (address >= machine__kernel_start(machine)) + cpumode = PERF_RECORD_MISC_KERNEL; + else + cpumode = PERF_RECORD_MISC_USER; + + if (thread__tid(rvtraceq->thread) != context) + rvtrace_set_thread(rvtraceq, context); + + if (!thread__find_map(rvtraceq->thread, cpumode, address, &al)) + goto out; + + dso = map__dso(al.map); + if (!dso) + goto out; + + if (dso__data(dso)->status == DSO_DATA_STATUS_ERROR && + dso__data_status_seen(dso, DSO_DATA_STATUS_SEEN_ITRACE)) + goto out; + + offset = map__map_ip(al.map, address); + + map__load(al.map); + + len = dso__data_read_offset(dso, machine, offset, buffer, size); + if (len <= 0) { + ui__warning_once("RISC-V Nexus Trace: Missing DSO. Use 'perf archive' or debuginfod to export data from the traced system.\n" + " Enable CONFIG_PROC_KCORE or use option '-k /path/to/vmlinux' for kernel symbols.\n"); + if (!dso__auxtrace_warned(dso)) { + pr_err("RISC-V Nexus Trace: Debug data not found for address %#"PRIx64" in %s\n", + address, + dso__long_name(dso) ? dso__long_name(dso) : "Unknown"); + dso__set_auxtrace_warned(dso); + } + goto out; + } + ret = len; +out: + addr_location__exit(&al); + return ret; +} + + +static u8 rvtrace_cpu_mode(enum riscv_privilege_mode prv) +{ + u8 cpumode; + + switch (prv) { + case RISCV_PRIV_USER_MODE: + cpumode = PERF_RECORD_MISC_USER; + break; + case RISCV_PRIV_SUPERVISOR_MODE: + case RISCV_PRIV_MACHINE_MODE: + default: + cpumode = PERF_RECORD_MISC_KERNEL; + break; + } + return cpumode; +} + +static void rvtrace_synth_copy_insn(struct rvtrace_queue *rvtraceq, + struct nexus_rv_packet *packet, + struct perf_sample *sample) +{ + int ret; + u32 insn; + + ret = rvtrace_mem_access(rvtraceq, sample->ip, packet->prv, packet->context, + sizeof(insn), (u8 *)&insn); + if (!ret) + return; + + sample->insn_len = ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_GE_32) ? 4 : 2; + memcpy(sample->insn, &insn, sample->insn_len); +} + +static inline u64 rvtrace_resolve_sample_time(struct rvtrace_queue *rvtraceq, + struct nexus_rv_packet *packet) +{ + struct rvtrace_auxtrace *rvtrace = rvtraceq->rvtrace; + + if (!rvtrace->timeless_decoding) + return packet->timestamp; + else + return rvtrace->latest_kernel_timestamp; +} + +static int rvtrace_synth_branch_sample(struct rvtrace_queue *rvtraceq, + struct nexus_rv_packet *packet) +{ + int ret = 0; + struct rvtrace_auxtrace *rvtrace = rvtraceq->rvtrace; + struct perf_sample sample; + union perf_event *event = rvtraceq->event_buf; + + perf_sample__init(&sample, /*all=*/true); + event->sample.header.type = PERF_RECORD_SAMPLE; + event->sample.header.misc = rvtrace_cpu_mode(packet->prv); + event->sample.header.size = sizeof(struct perf_event_header); + + if (thread__tid(rvtraceq->thread) != packet->context) + rvtrace_set_thread(rvtraceq, packet->context); + + /* Set time field based on rvtrace auxtrace config. */ + sample.time = rvtrace_resolve_sample_time(rvtraceq, packet); + + sample.ip = packet->start_addr; + sample.pid = thread__pid(rvtraceq->thread); + sample.tid = thread__tid(rvtraceq->thread); + sample.addr = packet->end_addr; + sample.insn_cnt = packet->insn_cnt; + sample.id = rvtraceq->rvtrace->branches_id; + sample.stream_id = rvtraceq->rvtrace->branches_id; + sample.period = 1; + sample.cpu = packet->cpu; + sample.flags = 0; + sample.cpumode = event->sample.header.misc; + + rvtrace_synth_copy_insn(rvtraceq, packet, &sample); + + ret = perf_session__deliver_synth_event(rvtrace->session, event, &sample); + if (ret) + pr_err( + "RISC-V Trace: failed to deliver instruction event, error %d\n", + ret); + perf_sample__exit(&sample); + + return ret; +} + +static int rvtrace_synth_events(struct rvtrace_auxtrace *rvtrace, + struct perf_session *session) +{ + struct evlist *evlist = session->evlist; + struct evsel *evsel; + struct perf_event_attr attr; + bool found = false; + u64 id; + int err; + + evlist__for_each_entry(evlist, evsel) { + if (evsel->core.attr.type == rvtrace->pmu_type) { + found = true; + break; + } + } + + if (!found) { + pr_debug("No selected events with RISC-V Trace data\n"); + return 0; + } + + memset(&attr, 0, sizeof(struct perf_event_attr)); + attr.size = sizeof(struct perf_event_attr); + attr.type = PERF_TYPE_HARDWARE; + attr.sample_type = evsel->core.attr.sample_type & PERF_SAMPLE_MASK; + attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | + PERF_SAMPLE_PERIOD; + if (rvtrace->timeless_decoding) + attr.sample_type &= ~(u64)PERF_SAMPLE_TIME; + else + attr.sample_type |= PERF_SAMPLE_TIME; + + attr.exclude_user = evsel->core.attr.exclude_user; + attr.exclude_kernel = evsel->core.attr.exclude_kernel; + attr.exclude_hv = evsel->core.attr.exclude_hv; + attr.exclude_host = evsel->core.attr.exclude_host; + attr.exclude_guest = evsel->core.attr.exclude_guest; + attr.sample_id_all = evsel->core.attr.sample_id_all; + attr.read_format = evsel->core.attr.read_format; + + /* create new id val to be a fixed offset from evsel id */ + id = evsel->core.id[0] + 1000000000; + if (!id) + id = 1; + + if (rvtrace->synth_opts.branches) { + attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; + attr.sample_period = 1; + attr.sample_type |= PERF_SAMPLE_ADDR; + + err = perf_session__deliver_synth_attr_event(session, &attr, id); + if (err) + return err; + rvtrace->branches_sample_type = attr.sample_type; + rvtrace->branches_id = id; + id += 1; + attr.sample_type &= ~(u64)PERF_SAMPLE_ADDR; + } + + return 0; +} + +static int rvtrace_process_queue(struct rvtrace_queue *rvtraceq) +{ + struct nexus_rv_packet_buffer *packet_buffer = &rvtraceq->decoder->packet_buffer; + + for (int i = 0; i < packet_buffer->size; i++) { + struct nexus_rv_packet packet = packet_buffer->packets[i]; + + if (packet.sample_type == RVTRACE_RANGE) + rvtrace_synth_branch_sample(rvtraceq, &packet); + else if (packet.sample_type == RVTRACE_LOSS) + fprintf(stdout, "RISC-V Trace: A FIFO overrun has resulted in the loss of one or more messages\n"); + } + + return 0; +} + +static int rvtrace_get_trace(struct rvtrace_queue *rvtraceq) +{ + struct auxtrace_buffer *aux_buffer = rvtraceq->buffer; + struct auxtrace_buffer *old_buffer = aux_buffer; + struct auxtrace_queue *queue; + + queue = &rvtraceq->rvtrace->queues.queue_array[rvtraceq->queue_nr]; + + aux_buffer = auxtrace_buffer__next(queue, aux_buffer); + + /* If no more data, drop the previous auxtrace_buffer and return */ + if (!aux_buffer) { + if (old_buffer) + auxtrace_buffer__drop_data(old_buffer); + return 0; + } + + rvtraceq->buffer = aux_buffer; + + /* If the aux_buffer doesn't have data associated, try to load it */ + if (!aux_buffer->data) { + /* get the file desc associated with the perf data file */ + int fd = perf_data__fd(rvtraceq->rvtrace->session->data); + + aux_buffer->data = auxtrace_buffer__get_data(aux_buffer, fd); + if (!aux_buffer->data) + return -ENOMEM; + } + + /* If valid, drop the previous buffer */ + if (old_buffer) + auxtrace_buffer__drop_data(old_buffer); + + return aux_buffer->size; +} + +/* + * rvtrace_get_data_block: Fetch a block from the auxtrace_buffer queue + * if need be. + * Returns: < 0 if error + * = 0 if no more auxtrace_buffer to read + * > 0 if the current buffer isn't empty yet + */ +static int rvtrace_get_data_block(struct rvtrace_queue *rvtraceq) +{ + int ret; + + ret = rvtrace_get_trace(rvtraceq); + if (ret <= 0) + return ret; + + /* + * We cannot assume consecutive blocks in the data file + * are contiguous, reset the decoder to force re-sync. + */ + ret = nexus_rv_insn_decoder_reset(rvtraceq->decoder); + if (ret) + return ret; + + return rvtraceq->buffer->size; +} + +static int rvtrace_run_timeless_decoder(struct rvtrace_queue *rvtraceq) +{ + int ret; + + while (1) { + ret = rvtrace_get_data_block(rvtraceq); + if (ret < 0) + return ret; + + if (ret == 0) + break; + + ret = nexus_rv_insn_decode_data(rvtraceq->decoder, + rvtraceq->buffer->data, + rvtraceq->buffer->size); + if (ret) + return ret; + + ret = rvtrace_process_queue(rvtraceq); + if (ret) + return ret; + } + + return ret; +} + +static int rvtrace_process_timeless_queues(struct rvtrace_auxtrace *rvtrace, + pid_t tid) +{ + unsigned int i; + struct auxtrace_queues *queues = &rvtrace->queues; + + for (i = 0; i < queues->nr_queues; i++) { + struct auxtrace_queue *queue = &rvtrace->queues.queue_array[i]; + struct rvtrace_queue *rvtraceq = queue->priv; + + if (rvtraceq && ((tid == -1) || (queue->tid == tid))) { + rvtrace_set_thread(rvtraceq, queue->tid); + rvtrace_run_timeless_decoder(rvtraceq); + } + } + + return 0; +} + +static u64 rvtrace_queue_get_timestamp(struct rvtrace_queue *rvtraceq) +{ + struct nexus_rv_packet_buffer *packet_buffer = &rvtraceq->decoder->packet_buffer; + + for (int i = 0; i < packet_buffer->size; i++) { + struct nexus_rv_packet packet = packet_buffer->packets[i]; + + if (packet.sample_type == RVTRACE_RANGE) + return packet.timestamp; + } + + return 0; +} + +static int rvtrace_queue_first_timestamp(struct rvtrace_auxtrace *rvtrace, + struct rvtrace_queue *rvtraceq, + unsigned int queue_nr) +{ + int ret; + u64 timestamp = 0; + + /* Decode the first segment of data until a timestamp is found, + * then add it to the heap for sorting by time across multiple + * queues. + */ + while (1) { + /* + * Fetch an aux_buffer from this rvtraceq. Bail if no more + * blocks or an error has been encountered. + */ + ret = rvtrace_get_data_block(rvtraceq); + if (ret <= 0) + goto out; + + ret = nexus_rv_insn_decode_data(rvtraceq->decoder, + rvtraceq->buffer->data, + rvtraceq->buffer->size); + if (ret) + goto out; + + timestamp = rvtrace_queue_get_timestamp(rvtraceq); + + /* We found a timestamp, no need to continue. */ + if (timestamp) + break; + } + + /* We have a timestamp and add it to the min heap */ + ret = auxtrace_heap__add(&rvtrace->heap, queue_nr, timestamp); +out: + return ret; +} + +static int rvtrace_process_timestamped_queues(struct rvtrace_auxtrace *rvtrace) +{ + int ret = 0; + unsigned int queue_nr, i; + struct auxtrace_queue *queue; + struct rvtrace_queue *rvtraceq; + + /* First, find the first timestamp for each queue and add it to the heap. */ + for (i = 0; i < rvtrace->queues.nr_queues; i++) { + queue = &rvtrace->queues.queue_array[i]; + rvtraceq = queue->priv; + if (!rvtraceq) + continue; + + rvtrace_set_thread(rvtraceq, queue->tid); + + ret = rvtrace_queue_first_timestamp(rvtrace, rvtraceq, i); + if (ret) + return ret; + } + + /* Process queues in the heap in timestamp order */ + while (1) { + if (!rvtrace->heap.heap_cnt) + break; + + /* Take the entry at the top of the min heap */ + queue_nr = rvtrace->heap.heap_array[0].queue_nr; + queue = &rvtrace->queues.queue_array[queue_nr]; + rvtraceq = queue->priv; + + /* + * Remove the top entry from the heap since we are about + * to process it. + */ + auxtrace_heap__pop(&rvtrace->heap); + + /* + * Packets associated with this timestamp are already in + * the rvtraceq->packet_buffer, so process them. + */ + ret = rvtrace_process_queue(rvtraceq); + if (ret) + return ret; + + /* + * Packets for this timestamp have been processed, time to + * move on to the next timestamp, find the next timestamp + * for this rvtraceq. + */ + ret = rvtrace_queue_first_timestamp(rvtrace, rvtraceq, queue_nr); + if (ret) + return ret; + } + + return 0; +} + +static struct rvtrace_queue *rvtrace_alloc_queue(struct rvtrace_auxtrace *rvtrace, + unsigned int queue_nr) +{ + struct nexus_rv_insn_decoder_params params; + struct rvtrace_queue *rvtraceq; + + rvtraceq = zalloc(sizeof(*rvtraceq)); + if (!rvtraceq) + return NULL; + + rvtraceq->event_buf = malloc(PERF_SAMPLE_MAX_SIZE); + if (!rvtraceq->event_buf) + goto out_free; + + rvtraceq->rvtrace = rvtrace; + rvtraceq->queue_nr = queue_nr; + + params.mem_access = rvtrace_mem_access; + params.data = rvtraceq; + params.formatted = true; + if (!rvtrace->metadata[0][RVTRACE_ENCODER_INHB_SRC]) + params.src_bits = rvtrace->metadata[0][RVTRACE_ENCODER_SRCBITS]; + + rvtraceq->decoder = nexus_rv_insn_decoder_new(¶ms); + if (!rvtraceq->decoder) + goto out_free; + + rvtraceq->offset = 0; + + return rvtraceq; + +out_free: + zfree(&rvtraceq->event_buf); + free(rvtraceq); + + return NULL; +} + +static int rvtrace_setup_queue(struct rvtrace_auxtrace *rvtrace, + struct auxtrace_queue *queue, + unsigned int queue_nr) +{ + struct rvtrace_queue *rvtraceq = queue->priv; + + if (list_empty(&queue->head) || rvtraceq) + return 0; + + rvtraceq = rvtrace_alloc_queue(rvtrace, queue_nr); + + if (!rvtraceq) + return -ENOMEM; + + queue->priv = rvtraceq; + + return 0; +} + +static int rvtrace_setup_queues(struct rvtrace_auxtrace *rvtrace) +{ + unsigned int i; + int ret; + + for (i = 0; i < rvtrace->queues.nr_queues; i++) { + ret = rvtrace_setup_queue(rvtrace, &rvtrace->queues.queue_array[i], i); + if (ret) + return ret; + } + + return 0; +} + +static int rvtrace_update_queues(struct rvtrace_auxtrace *rvtrace) +{ + if (rvtrace->queues.new_data) { + rvtrace->queues.new_data = false; + return rvtrace_setup_queues(rvtrace); + } + + return 0; +} + +static int rvtrace_flush_events(struct perf_session *session, + const struct perf_tool *tool) +{ + struct rvtrace_auxtrace *rvtrace = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + int ret; + + if (dump_trace) + return 0; + + if (!tool->ordered_events) + return -EINVAL; + + ret = rvtrace_update_queues(rvtrace); + if (ret < 0) + return ret; + + if (rvtrace->timeless_decoding) { + /* + * Pass tid = -1 to process all queues. But likely they will have + * already been processed on PERF_RECORD_EXIT anyway. + */ + return rvtrace_process_timeless_queues(rvtrace, -1); + } + + return rvtrace_process_timestamped_queues(rvtrace); +} + +static void rvtrace_free_queue(void *priv) +{ + struct rvtrace_queue *rvtraceq = priv; + + if (!rvtraceq) + return; + + thread__zput(rvtraceq->thread); + nexus_rv_insn_decoder_free(rvtraceq->decoder); + zfree(&rvtraceq->event_buf); + free(rvtraceq); +} + +static void rvtrace_free_events(struct perf_session *session) +{ + unsigned int i; + struct rvtrace_auxtrace *rvtrace = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + struct auxtrace_queues *queues = &rvtrace->queues; + + for (i = 0; i < queues->nr_queues; i++) { + rvtrace_free_queue(queues->queue_array[i].priv); + queues->queue_array[i].priv = NULL; + } + + auxtrace_queues__free(queues); +} + +static void rvtrace_free(struct perf_session *session) +{ + struct rvtrace_auxtrace *rvtrace = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + rvtrace_free_events(session); + session->auxtrace = NULL; + for (int i = 0; i < rvtrace->num_cpu; i++) + zfree(&rvtrace->metadata[i]); + + zfree(&rvtrace->metadata); + zfree(&rvtrace); +} + +static bool rvtrace_evsel_is_auxtrace(struct perf_session *session, + struct evsel *evsel) +{ + struct rvtrace_auxtrace *aux = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + + return evsel->core.attr.type == aux->pmu_type; +} + +static int rvtrace_process_itrace_start(struct rvtrace_auxtrace *rvtrace, + union perf_event *event) +{ + struct thread *th; + + if (rvtrace->timeless_decoding) + return 0; + + /* + * Add the tid/pid to the log so that we can get a match when + * we get a contextID from the decoder. + */ + th = machine__findnew_thread(rvtrace->machine, + event->itrace_start.pid, + event->itrace_start.tid); + if (!th) + return -ENOMEM; + + thread__put(th); + + return 0; +} + +static int rvtrace_process_event(struct perf_session *session, + union perf_event *event, + struct perf_sample *sample, + const struct perf_tool *tool) +{ + int err = 0; + u64 timestamp; + struct rvtrace_auxtrace *rvtrace = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + + if (dump_trace) + return 0; + + if (!tool->ordered_events) { + pr_err("RISCV Trace requires ordered events\n"); + return -EINVAL; + } + + if (sample->time && (sample->time != (u64) -1)) + timestamp = sample->time; + else + timestamp = 0; + + if (timestamp || rvtrace->timeless_decoding) { + err = rvtrace_update_queues(rvtrace); + if (err) + return err; + } + + switch (event->header.type) { + case PERF_RECORD_EXIT: + /* + * Don't need to wait for rvtrace_flush_events() in per-thread/timeless + * mode to start the decode because we know there will be no more trace + * from this thread. All this does is emit samples earlier than waiting + * for the flush in other modes, but with timestamps it makes sense to + * wait for flush so that events from different threads are interleaved + * properly. + */ + if (rvtrace->timeless_decoding) + return rvtrace_process_timeless_queues(rvtrace, event->fork.tid); + break; + + case PERF_RECORD_ITRACE_START: + return rvtrace_process_itrace_start(rvtrace, event); + + case PERF_RECORD_AUX: + /* + * Record the latest kernel timestamp available for rollback when + * no trace timestamp is available. + */ + if (timestamp) + rvtrace->latest_kernel_timestamp = timestamp; + break; + + default: + break; + } + + return 0; +} + +static void rvtrace_dump(struct rvtrace_auxtrace *rvtrace, + unsigned char *buf, size_t len) +{ + int ret; + struct nexus_rv_pkt_decoder_params params; + struct nexus_rv_pkt_decoder *decoder; + const char *color = PERF_COLOR_BLUE; + + // TODO: Determine whether the trace data contains a CoreSight formatter frame + params.formatted = true; + if (!rvtrace->metadata[0][RVTRACE_ENCODER_INHB_SRC]) + params.src_bits = rvtrace->metadata[0][RVTRACE_ENCODER_SRCBITS]; + + decoder = nexus_rv_pkt_decoder_new(¶ms); + if (!decoder) { + color_fprintf(stdout, color, " Failed to create decoder\n"); + return; + } + + color_fprintf(stdout, color, + ". ... Trace Encoder Trace data: size %#zx bytes\n", + len); + + ret = nexus_rv_pkt_desc(decoder, buf, len); + if (ret) + color_fprintf(stdout, color, " Bad packet!\n"); + + nexus_rv_pkt_decoder_free(decoder); +} + +static void rvtrace_dump_event(struct rvtrace_auxtrace *rvtrace, + unsigned char *buf, size_t len) +{ + printf(".\n"); + rvtrace_dump(rvtrace, buf, len); +} + +static int rvtrace_process_auxtrace_event(struct perf_session *session, + union perf_event *event, + const struct perf_tool *tool __maybe_unused) +{ + struct rvtrace_auxtrace *rvtrace = container_of(session->auxtrace, + struct rvtrace_auxtrace, + auxtrace); + if (!rvtrace->data_queued) { + struct auxtrace_buffer *buffer; + off_t data_offset; + int fd = perf_data__fd(session->data); + bool is_pipe = perf_data__is_pipe(session->data); + int err; + + if (is_pipe) + data_offset = 0; + else { + data_offset = lseek(fd, 0, SEEK_CUR); + if (data_offset == -1) + return -errno; + } + + err = auxtrace_queues__add_event(&rvtrace->queues, session, + event, data_offset, &buffer); + + if (err) + return err; + + if (dump_trace) + if (auxtrace_buffer__get_data(buffer, fd)) { + rvtrace_dump_event(rvtrace, buffer->data, buffer->size); + auxtrace_buffer__put_data(buffer); + } + } + + + return 0; +} + +static bool rvtrace_is_timeless_decoding(struct rvtrace_auxtrace *rvtrace) +{ + struct evsel *evsel; + struct evlist *evlist = rvtrace->session->evlist; + bool timeless_decoding = true; + + /* + * Circle through the list of event and complain if we find one + * with the time bit set. + */ + evlist__for_each_entry(evlist, evsel) { + if ((evsel->core.attr.sample_type & PERF_SAMPLE_TIME)) + timeless_decoding = false; + } + + return timeless_decoding; +} + +static const char * const rvtrace_global_header_fmts[] = { + [RVTRACE_PMU_TYPE_CPUS] = " PMU type/num cpus %llx\n", +}; + +static const char * const rvtrace_encoder_priv_fmts[] = { + [RVTRACE_ENCODER_CPU] = " CPU %lld\n", + [RVTRACE_ENCODER_NR_TRC_PARAMS] = " NR_TRC_PARAMS %lld\n", + [RVTRACE_ENCODER_FORMAT] = " FORMAT %lld\n", + [RVTRACE_ENCODER_CONTEXT] = " CONTEXT %lld\n", + [RVTRACE_ENCODER_INHB_SRC] = " INHB_SRC %lld\n", + [RVTRACE_ENCODER_SRCBITS] = " SRCBITS %lld\n", + [RVTRACE_ENCODER_SRCID] = " SRCID %lld\n", +}; + +static void rvtrace_print_auxtrace_info(u64 *val, int num_cpu) +{ + int i, j, cpu = 0, nr_params = 0, fmt_offset = 0; + + for (i = 0; i < RVTRACE_HEADER_MAX; i++) + fprintf(stdout, rvtrace_global_header_fmts[i], val[i]); + + for (i = RVTRACE_HEADER_MAX; cpu < num_cpu; cpu++) { + fprintf(stdout, rvtrace_encoder_priv_fmts[RVTRACE_ENCODER_CPU], val[i++]); + nr_params = val[i++]; + fmt_offset = RVTRACE_ENCODER_FORMAT; + for (j = fmt_offset; j < nr_params + fmt_offset; j++, i++) + fprintf(stdout, rvtrace_encoder_priv_fmts[j], val[i]); + } +} + +int rvtrace_process_auxtrace_info(union perf_event *event, + struct perf_session *session) +{ + struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info; + struct rvtrace_auxtrace *rvtrace = NULL; + int err = 0; + int i; + int num_cpu = 0; + u64 *ptr = NULL; + u64 **metadata = NULL; + + /* First the global part */ + ptr = (u64 *) auxtrace_info->priv; + num_cpu = ptr[RVTRACE_PMU_TYPE_CPUS] & 0xffffffff; + metadata = zalloc(sizeof(*metadata) * num_cpu); + if (!metadata) + err = -ENOMEM; + + /* Start parsing after the common part of the header */ + i = RVTRACE_HEADER_MAX; + + for (int j = 0; j < num_cpu; j++) { + metadata[j] = zalloc(sizeof(*metadata[j]) * RVTRACE_ENCODER_PRIV_MAX); + if (!metadata[j]) { + err = -ENOMEM; + goto err_free_metadata; + } + + for (int k = 0; k < RVTRACE_ENCODER_PRIV_MAX; k++) + metadata[j][k] = ptr[i + k]; + i += RVTRACE_ENCODER_PRIV_MAX; + } + + rvtrace = zalloc(sizeof(*rvtrace)); + if (!rvtrace) { + err = -ENOMEM; + goto err_free_metadata; + } + + err = auxtrace_queues__init(&rvtrace->queues); + if (err) + goto err_free_rvtrace; + + if (session->itrace_synth_opts->set) { + rvtrace->synth_opts = *session->itrace_synth_opts; + } else { + itrace_synth_opts__set_default(&rvtrace->synth_opts, + session->itrace_synth_opts->default_no_sample); + rvtrace->synth_opts.callchain = false; + } + + rvtrace->session = session; + rvtrace->machine = &session->machines.host; + rvtrace->num_cpu = num_cpu; + rvtrace->pmu_type = (unsigned int) ((ptr[RVTRACE_PMU_TYPE_CPUS] >> 32) & 0xffffffff); + rvtrace->metadata = metadata; + + rvtrace->auxtrace_type = auxtrace_info->type; + rvtrace->timeless_decoding = rvtrace_is_timeless_decoding(rvtrace); + + rvtrace->auxtrace.process_event = rvtrace_process_event; + rvtrace->auxtrace.process_auxtrace_event = rvtrace_process_auxtrace_event; + rvtrace->auxtrace.flush_events = rvtrace_flush_events; + rvtrace->auxtrace.free_events = rvtrace_free_events; + rvtrace->auxtrace.free = rvtrace_free; + rvtrace->auxtrace.evsel_is_auxtrace = rvtrace_evsel_is_auxtrace; + session->auxtrace = &rvtrace->auxtrace; + + if (dump_trace) { + rvtrace_print_auxtrace_info(ptr, num_cpu); + return 0; + } + + err = rvtrace_synth_events(rvtrace, session); + if (err) + goto err_free_queues; + + err = auxtrace_queues__process_index(&rvtrace->queues, session); + if (err) + goto err_free_queues; + + rvtrace->data_queued = rvtrace->queues.populated; + + return 0; + +err_free_queues: + auxtrace_queues__free(&rvtrace->queues); + session->auxtrace = NULL; +err_free_rvtrace: + zfree(&rvtrace); +err_free_metadata: + for (int j = 0; j < num_cpu; j++) + zfree(&metadata[j]); + zfree(&metadata); + + return -EINVAL; +} diff --git a/tools/perf/util/rvtrace.h b/tools/perf/util/rvtrace.h index 1e48ed989dd7..84a59d3dc296 100644 --- a/tools/perf/util/rvtrace.h +++ b/tools/perf/util/rvtrace.h @@ -35,4 +35,6 @@ enum { #define RVTRACE_HEADER_SIZE (RVTRACE_HEADER_MAX * sizeof(u64)) #define RVTRACE_ENCODER_PRIV_SIZE (RVTRACE_ENCODER_PRIV_MAX * sizeof(u64))
+int rvtrace_process_auxtrace_info(union perf_event *event, struct perf_session *session); + #endif