diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /tools/perf/arch/arm | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/perf/arch/arm')
-rw-r--r-- | tools/perf/arch/arm/Build | 2 | ||||
-rw-r--r-- | tools/perf/arch/arm/Makefile | 5 | ||||
-rw-r--r-- | tools/perf/arch/arm/annotate/instructions.c | 63 | ||||
-rw-r--r-- | tools/perf/arch/arm/include/arch-tests.h | 12 | ||||
-rw-r--r-- | tools/perf/arch/arm/include/dwarf-regs-table.h | 10 | ||||
-rw-r--r-- | tools/perf/arch/arm/include/perf_regs.h | 60 | ||||
-rw-r--r-- | tools/perf/arch/arm/tests/Build | 5 | ||||
-rw-r--r-- | tools/perf/arch/arm/tests/arch-tests.c | 20 | ||||
-rw-r--r-- | tools/perf/arch/arm/tests/dwarf-unwind.c | 63 | ||||
-rw-r--r-- | tools/perf/arch/arm/tests/regs_load.S | 59 | ||||
-rw-r--r-- | tools/perf/arch/arm/tests/vectors-page.c | 24 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/Build | 8 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/auxtrace.c | 109 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/cs-etm.c | 898 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/cs-etm.h | 12 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/dwarf-regs.c | 61 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/perf_regs.c | 6 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/pmu.c | 30 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/unwind-libdw.c | 38 | ||||
-rw-r--r-- | tools/perf/arch/arm/util/unwind-libunwind.c | 50 |
20 files changed, 1535 insertions, 0 deletions
diff --git a/tools/perf/arch/arm/Build b/tools/perf/arch/arm/Build new file mode 100644 index 000000000..36222e64b --- /dev/null +++ b/tools/perf/arch/arm/Build @@ -0,0 +1,2 @@ +perf-y += util/ +perf-$(CONFIG_DWARF_UNWIND) += tests/ diff --git a/tools/perf/arch/arm/Makefile b/tools/perf/arch/arm/Makefile new file mode 100644 index 000000000..1d88fdab1 --- /dev/null +++ b/tools/perf/arch/arm/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +ifndef NO_DWARF +PERF_HAVE_DWARF_REGS := 1 +endif +PERF_HAVE_JITDUMP := 1 diff --git a/tools/perf/arch/arm/annotate/instructions.c b/tools/perf/arch/arm/annotate/instructions.c new file mode 100644 index 000000000..2ff6cedeb --- /dev/null +++ b/tools/perf/arch/arm/annotate/instructions.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <linux/zalloc.h> +#include <sys/types.h> +#include <regex.h> +#include <stdlib.h> + +struct arm_annotate { + regex_t call_insn, + jump_insn; +}; + +static struct ins_ops *arm__associate_instruction_ops(struct arch *arch, const char *name) +{ + struct arm_annotate *arm = arch->priv; + struct ins_ops *ops; + regmatch_t match[2]; + + if (!regexec(&arm->call_insn, name, 2, match, 0)) + ops = &call_ops; + else if (!regexec(&arm->jump_insn, name, 2, match, 0)) + ops = &jump_ops; + else + return NULL; + + arch__associate_ins_ops(arch, name, ops); + return ops; +} + +static int arm__annotate_init(struct arch *arch, char *cpuid __maybe_unused) +{ + struct arm_annotate *arm; + int err; + + if (arch->initialized) + return 0; + + arm = zalloc(sizeof(*arm)); + if (!arm) + return ENOMEM; + +#define ARM_CONDS "(cc|cs|eq|ge|gt|hi|le|ls|lt|mi|ne|pl|vc|vs)" + err = regcomp(&arm->call_insn, "^blx?" ARM_CONDS "?$", REG_EXTENDED); + if (err) + goto out_free_arm; + err = regcomp(&arm->jump_insn, "^bx?" ARM_CONDS "?$", REG_EXTENDED); + if (err) + goto out_free_call; +#undef ARM_CONDS + + arch->initialized = true; + arch->priv = arm; + arch->associate_instruction_ops = arm__associate_instruction_ops; + arch->objdump.comment_char = ';'; + arch->objdump.skip_functions_char = '+'; + return 0; + +out_free_call: + regfree(&arm->call_insn); +out_free_arm: + free(arm); + return SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_REGEXP; +} diff --git a/tools/perf/arch/arm/include/arch-tests.h b/tools/perf/arch/arm/include/arch-tests.h new file mode 100644 index 000000000..90ec4c8cb --- /dev/null +++ b/tools/perf/arch/arm/include/arch-tests.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_TESTS_H +#define ARCH_TESTS_H + +#ifdef HAVE_DWARF_UNWIND_SUPPORT +struct thread; +struct perf_sample; +#endif + +extern struct test arch_tests[]; + +#endif diff --git a/tools/perf/arch/arm/include/dwarf-regs-table.h b/tools/perf/arch/arm/include/dwarf-regs-table.h new file mode 100644 index 000000000..5a45046fa --- /dev/null +++ b/tools/perf/arch/arm/include/dwarf-regs-table.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifdef DEFINE_DWARF_REGSTR_TABLE +/* This is included in perf/util/dwarf-regs.c */ + +static const char * const arm_regstr_tbl[] = { + "%r0", "%r1", "%r2", "%r3", "%r4", + "%r5", "%r6", "%r7", "%r8", "%r9", "%r10", + "%fp", "%ip", "%sp", "%lr", "%pc", +}; +#endif diff --git a/tools/perf/arch/arm/include/perf_regs.h b/tools/perf/arch/arm/include/perf_regs.h new file mode 100644 index 000000000..408541928 --- /dev/null +++ b/tools/perf/arch/arm/include/perf_regs.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_PERF_REGS_H +#define ARCH_PERF_REGS_H + +#include <stdlib.h> +#include <linux/types.h> +#include <asm/perf_regs.h> + +void perf_regs_load(u64 *regs); + +#define PERF_REGS_MASK ((1ULL << PERF_REG_ARM_MAX) - 1) +#define PERF_REGS_MAX PERF_REG_ARM_MAX +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32 + +#define PERF_REG_IP PERF_REG_ARM_PC +#define PERF_REG_SP PERF_REG_ARM_SP + +static inline const char *__perf_reg_name(int id) +{ + switch (id) { + case PERF_REG_ARM_R0: + return "r0"; + case PERF_REG_ARM_R1: + return "r1"; + case PERF_REG_ARM_R2: + return "r2"; + case PERF_REG_ARM_R3: + return "r3"; + case PERF_REG_ARM_R4: + return "r4"; + case PERF_REG_ARM_R5: + return "r5"; + case PERF_REG_ARM_R6: + return "r6"; + case PERF_REG_ARM_R7: + return "r7"; + case PERF_REG_ARM_R8: + return "r8"; + case PERF_REG_ARM_R9: + return "r9"; + case PERF_REG_ARM_R10: + return "r10"; + case PERF_REG_ARM_FP: + return "fp"; + case PERF_REG_ARM_IP: + return "ip"; + case PERF_REG_ARM_SP: + return "sp"; + case PERF_REG_ARM_LR: + return "lr"; + case PERF_REG_ARM_PC: + return "pc"; + default: + return NULL; + } + + return NULL; +} + +#endif /* ARCH_PERF_REGS_H */ diff --git a/tools/perf/arch/arm/tests/Build b/tools/perf/arch/arm/tests/Build new file mode 100644 index 000000000..bc8e97380 --- /dev/null +++ b/tools/perf/arch/arm/tests/Build @@ -0,0 +1,5 @@ +perf-y += regs_load.o +perf-y += dwarf-unwind.o +perf-y += vectors-page.o + +perf-y += arch-tests.o diff --git a/tools/perf/arch/arm/tests/arch-tests.c b/tools/perf/arch/arm/tests/arch-tests.c new file mode 100644 index 000000000..6848101a8 --- /dev/null +++ b/tools/perf/arch/arm/tests/arch-tests.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <string.h> +#include "tests/tests.h" +#include "arch-tests.h" + +struct test arch_tests[] = { +#ifdef HAVE_DWARF_UNWIND_SUPPORT + { + .desc = "DWARF unwind", + .func = test__dwarf_unwind, + }, +#endif + { + .desc = "Vectors page", + .func = test__vectors_page, + }, + { + .func = NULL, + }, +}; diff --git a/tools/perf/arch/arm/tests/dwarf-unwind.c b/tools/perf/arch/arm/tests/dwarf-unwind.c new file mode 100644 index 000000000..ccfa87055 --- /dev/null +++ b/tools/perf/arch/arm/tests/dwarf-unwind.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <string.h> +#include "perf_regs.h" +#include "thread.h" +#include "map.h" +#include "maps.h" +#include "event.h" +#include "debug.h" +#include "tests/tests.h" + +#define STACK_SIZE 8192 + +static int sample_ustack(struct perf_sample *sample, + struct thread *thread, u64 *regs) +{ + struct stack_dump *stack = &sample->user_stack; + struct map *map; + unsigned long sp; + u64 stack_size, *buf; + + buf = malloc(STACK_SIZE); + if (!buf) { + pr_debug("failed to allocate sample uregs data\n"); + return -1; + } + + sp = (unsigned long) regs[PERF_REG_ARM_SP]; + + map = maps__find(thread->maps, (u64)sp); + if (!map) { + pr_debug("failed to get stack map\n"); + free(buf); + return -1; + } + + stack_size = map->end - sp; + stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size; + + memcpy(buf, (void *) sp, stack_size); + stack->data = (char *) buf; + stack->size = stack_size; + return 0; +} + +int test__arch_unwind_sample(struct perf_sample *sample, + struct thread *thread) +{ + struct regs_dump *regs = &sample->user_regs; + u64 *buf; + + buf = calloc(1, sizeof(u64) * PERF_REGS_MAX); + if (!buf) { + pr_debug("failed to allocate sample uregs data\n"); + return -1; + } + + perf_regs_load(buf); + regs->abi = PERF_SAMPLE_REGS_ABI; + regs->regs = buf; + regs->mask = PERF_REGS_MASK; + + return sample_ustack(sample, thread, buf); +} diff --git a/tools/perf/arch/arm/tests/regs_load.S b/tools/perf/arch/arm/tests/regs_load.S new file mode 100644 index 000000000..4284307d7 --- /dev/null +++ b/tools/perf/arch/arm/tests/regs_load.S @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/linkage.h> + +#define R0 0x00 +#define R1 0x08 +#define R2 0x10 +#define R3 0x18 +#define R4 0x20 +#define R5 0x28 +#define R6 0x30 +#define R7 0x38 +#define R8 0x40 +#define R9 0x48 +#define SL 0x50 +#define FP 0x58 +#define IP 0x60 +#define SP 0x68 +#define LR 0x70 +#define PC 0x78 + +/* + * Implementation of void perf_regs_load(u64 *regs); + * + * This functions fills in the 'regs' buffer from the actual registers values, + * in the way the perf built-in unwinding test expects them: + * - the PC at the time at the call to this function. Since this function + * is called using a bl instruction, the PC value is taken from LR. + * The built-in unwinding test then unwinds the call stack from the dwarf + * information in unwind__get_entries. + * + * Notes: + * - the 8 bytes stride in the registers offsets comes from the fact + * that the registers are stored in an u64 array (u64 *regs), + * - the regs buffer needs to be zeroed before the call to this function, + * in this case using a calloc in dwarf-unwind.c. + */ + +.text +.type perf_regs_load,%function +SYM_FUNC_START(perf_regs_load) + str r0, [r0, #R0] + str r1, [r0, #R1] + str r2, [r0, #R2] + str r3, [r0, #R3] + str r4, [r0, #R4] + str r5, [r0, #R5] + str r6, [r0, #R6] + str r7, [r0, #R7] + str r8, [r0, #R8] + str r9, [r0, #R9] + str sl, [r0, #SL] + str fp, [r0, #FP] + str ip, [r0, #IP] + str sp, [r0, #SP] + str lr, [r0, #LR] + str lr, [r0, #PC] // store pc as lr in order to skip the call + // to this function + mov pc, lr +SYM_FUNC_END(perf_regs_load) diff --git a/tools/perf/arch/arm/tests/vectors-page.c b/tools/perf/arch/arm/tests/vectors-page.c new file mode 100644 index 000000000..7ffdd7997 --- /dev/null +++ b/tools/perf/arch/arm/tests/vectors-page.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> +#include <linux/compiler.h> + +#include "debug.h" +#include "tests/tests.h" +#include "util/find-map.c" + +#define VECTORS__MAP_NAME "[vectors]" + +int test__vectors_page(struct test *test __maybe_unused, + int subtest __maybe_unused) +{ + void *start, *end; + + if (find_map(&start, &end, VECTORS__MAP_NAME)) { + pr_err("%s not found, is CONFIG_KUSER_HELPERS enabled?\n", + VECTORS__MAP_NAME); + return TEST_FAIL; + } + + return TEST_OK; +} diff --git a/tools/perf/arch/arm/util/Build b/tools/perf/arch/arm/util/Build new file mode 100644 index 000000000..37fc63708 --- /dev/null +++ b/tools/perf/arch/arm/util/Build @@ -0,0 +1,8 @@ +perf-y += perf_regs.o + +perf-$(CONFIG_DWARF) += dwarf-regs.o + +perf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o +perf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o + +perf-$(CONFIG_AUXTRACE) += pmu.o auxtrace.o cs-etm.o diff --git a/tools/perf/arch/arm/util/auxtrace.c b/tools/perf/arch/arm/util/auxtrace.c new file mode 100644 index 000000000..b187bddbd --- /dev/null +++ b/tools/perf/arch/arm/util/auxtrace.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2015 Linaro Limited. All rights reserved. + * Author: Mathieu Poirier <mathieu.poirier@linaro.org> + */ + +#include <stdbool.h> +#include <linux/coresight-pmu.h> +#include <linux/zalloc.h> + +#include "../../util/auxtrace.h" +#include "../../util/debug.h" +#include "../../util/evlist.h" +#include "../../util/pmu.h" +#include "cs-etm.h" +#include "arm-spe.h" + +static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err) +{ + struct perf_pmu **arm_spe_pmus = NULL; + int ret, i, nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + /* arm_spe_xxxxxxxxx\0 */ + char arm_spe_pmu_name[sizeof(ARM_SPE_PMU_NAME) + 10]; + + arm_spe_pmus = zalloc(sizeof(struct perf_pmu *) * nr_cpus); + if (!arm_spe_pmus) { + pr_err("spes alloc failed\n"); + *err = -ENOMEM; + return NULL; + } + + for (i = 0; i < nr_cpus; i++) { + ret = sprintf(arm_spe_pmu_name, "%s%d", ARM_SPE_PMU_NAME, i); + if (ret < 0) { + pr_err("sprintf failed\n"); + *err = -ENOMEM; + return NULL; + } + + arm_spe_pmus[*nr_spes] = perf_pmu__find(arm_spe_pmu_name); + if (arm_spe_pmus[*nr_spes]) { + pr_debug2("%s %d: arm_spe_pmu %d type %d name %s\n", + __func__, __LINE__, *nr_spes, + arm_spe_pmus[*nr_spes]->type, + arm_spe_pmus[*nr_spes]->name); + (*nr_spes)++; + } + } + + return arm_spe_pmus; +} + +struct auxtrace_record +*auxtrace_record__init(struct evlist *evlist, int *err) +{ + struct perf_pmu *cs_etm_pmu; + struct evsel *evsel; + bool found_etm = false; + struct perf_pmu *found_spe = NULL; + struct perf_pmu **arm_spe_pmus = NULL; + int nr_spes = 0; + int i = 0; + + if (!evlist) + return NULL; + + cs_etm_pmu = perf_pmu__find(CORESIGHT_ETM_PMU_NAME); + arm_spe_pmus = find_all_arm_spe_pmus(&nr_spes, err); + + evlist__for_each_entry(evlist, evsel) { + if (cs_etm_pmu && + evsel->core.attr.type == cs_etm_pmu->type) + found_etm = true; + + if (!nr_spes || found_spe) + continue; + + for (i = 0; i < nr_spes; i++) { + if (evsel->core.attr.type == arm_spe_pmus[i]->type) { + found_spe = arm_spe_pmus[i]; + break; + } + } + } + free(arm_spe_pmus); + + if (found_etm && found_spe) { + pr_err("Concurrent ARM Coresight ETM and SPE operation not currently supported\n"); + *err = -EOPNOTSUPP; + return NULL; + } + + if (found_etm) + return cs_etm_record_init(err); + +#if defined(__aarch64__) + if (found_spe) + return arm_spe_recording_init(err, found_spe); +#endif + + /* + * Clear 'err' even if we haven't found an event - that way perf + * record can still be used even if tracers aren't present. The NULL + * return value will take care of telling the infrastructure HW tracing + * isn't available. + */ + *err = 0; + return NULL; +} diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c new file mode 100644 index 000000000..cad7bf783 --- /dev/null +++ b/tools/perf/arch/arm/util/cs-etm.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2015 Linaro Limited. All rights reserved. + * Author: Mathieu Poirier <mathieu.poirier@linaro.org> + */ + +#include <api/fs/fs.h> +#include <linux/bits.h> +#include <linux/bitops.h> +#include <linux/compiler.h> +#include <linux/coresight-pmu.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/zalloc.h> + +#include "cs-etm.h" +#include "../../util/debug.h" +#include "../../util/record.h" +#include "../../util/auxtrace.h" +#include "../../util/cpumap.h" +#include "../../util/event.h" +#include "../../util/evlist.h" +#include "../../util/evsel.h" +#include "../../util/perf_api_probe.h" +#include "../../util/evsel_config.h" +#include "../../util/pmu.h" +#include "../../util/cs-etm.h" +#include <internal/lib.h> // page_size +#include "../../util/session.h" + +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> + +struct cs_etm_recording { + struct auxtrace_record itr; + struct perf_pmu *cs_etm_pmu; + struct evlist *evlist; + int wrapped_cnt; + bool *wrapped; + bool snapshot_mode; + size_t snapshot_size; +}; + +static const char *metadata_etmv3_ro[CS_ETM_PRIV_MAX] = { + [CS_ETM_ETMCCER] = "mgmt/etmccer", + [CS_ETM_ETMIDR] = "mgmt/etmidr", +}; + +static const char *metadata_etmv4_ro[CS_ETMV4_PRIV_MAX] = { + [CS_ETMV4_TRCIDR0] = "trcidr/trcidr0", + [CS_ETMV4_TRCIDR1] = "trcidr/trcidr1", + [CS_ETMV4_TRCIDR2] = "trcidr/trcidr2", + [CS_ETMV4_TRCIDR8] = "trcidr/trcidr8", + [CS_ETMV4_TRCAUTHSTATUS] = "mgmt/trcauthstatus", +}; + +static bool cs_etm_is_etmv4(struct auxtrace_record *itr, int cpu); + +static int cs_etm_set_context_id(struct auxtrace_record *itr, + struct evsel *evsel, int cpu) +{ + struct cs_etm_recording *ptr; + struct perf_pmu *cs_etm_pmu; + char path[PATH_MAX]; + int err = -EINVAL; + u32 val; + + ptr = container_of(itr, struct cs_etm_recording, itr); + cs_etm_pmu = ptr->cs_etm_pmu; + + if (!cs_etm_is_etmv4(itr, cpu)) + goto out; + + /* Get a handle on TRCIRD2 */ + snprintf(path, PATH_MAX, "cpu%d/%s", + cpu, metadata_etmv4_ro[CS_ETMV4_TRCIDR2]); + err = perf_pmu__scan_file(cs_etm_pmu, path, "%x", &val); + + /* There was a problem reading the file, bailing out */ + if (err != 1) { + pr_err("%s: can't read file %s\n", + CORESIGHT_ETM_PMU_NAME, path); + goto out; + } + + /* + * TRCIDR2.CIDSIZE, bit [9-5], indicates whether contextID tracing + * is supported: + * 0b00000 Context ID tracing is not supported. + * 0b00100 Maximum of 32-bit Context ID size. + * All other values are reserved. + */ + val = BMVAL(val, 5, 9); + if (!val || val != 0x4) { + err = -EINVAL; + goto out; + } + + /* All good, let the kernel know */ + evsel->core.attr.config |= (1 << ETM_OPT_CTXTID); + err = 0; + +out: + + return err; +} + +static int cs_etm_set_timestamp(struct auxtrace_record *itr, + struct evsel *evsel, int cpu) +{ + struct cs_etm_recording *ptr; + struct perf_pmu *cs_etm_pmu; + char path[PATH_MAX]; + int err = -EINVAL; + u32 val; + + ptr = container_of(itr, struct cs_etm_recording, itr); + cs_etm_pmu = ptr->cs_etm_pmu; + + if (!cs_etm_is_etmv4(itr, cpu)) + goto out; + + /* Get a handle on TRCIRD0 */ + snprintf(path, PATH_MAX, "cpu%d/%s", + cpu, metadata_etmv4_ro[CS_ETMV4_TRCIDR0]); + err = perf_pmu__scan_file(cs_etm_pmu, path, "%x", &val); + + /* There was a problem reading the file, bailing out */ + if (err != 1) { + pr_err("%s: can't read file %s\n", + CORESIGHT_ETM_PMU_NAME, path); + goto out; + } + + /* + * TRCIDR0.TSSIZE, bit [28-24], indicates whether global timestamping + * is supported: + * 0b00000 Global timestamping is not implemented + * 0b00110 Implementation supports a maximum timestamp of 48bits. + * 0b01000 Implementation supports a maximum timestamp of 64bits. + */ + val &= GENMASK(28, 24); + if (!val) { + err = -EINVAL; + goto out; + } + + /* All good, let the kernel know */ + evsel->core.attr.config |= (1 << ETM_OPT_TS); + err = 0; + +out: + return err; +} + +static int cs_etm_set_option(struct auxtrace_record *itr, + struct evsel *evsel, u32 option) +{ + int i, err = -EINVAL; + struct perf_cpu_map *event_cpus = evsel->evlist->core.cpus; + struct perf_cpu_map *online_cpus = perf_cpu_map__new(NULL); + + /* Set option of each CPU we have */ + for (i = 0; i < cpu__max_cpu(); i++) { + if (!cpu_map__has(event_cpus, i) || + !cpu_map__has(online_cpus, i)) + continue; + + if (option & ETM_OPT_CTXTID) { + err = cs_etm_set_context_id(itr, evsel, i); + if (err) + goto out; + } + if (option & ETM_OPT_TS) { + err = cs_etm_set_timestamp(itr, evsel, i); + if (err) + goto out; + } + if (option & ~(ETM_OPT_CTXTID | ETM_OPT_TS)) + /* Nothing else is currently supported */ + goto out; + } + + err = 0; +out: + perf_cpu_map__put(online_cpus); + return err; +} + +static int cs_etm_parse_snapshot_options(struct auxtrace_record *itr, + struct record_opts *opts, + const char *str) +{ + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_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 int cs_etm_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) { + 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 cs_etm_recording_options(struct auxtrace_record *itr, + struct evlist *evlist, + struct record_opts *opts) +{ + int ret; + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + struct evsel *evsel, *cs_etm_evsel = NULL; + struct perf_cpu_map *cpus = evlist->core.cpus; + bool privileged = perf_event_paranoid_check(-1); + int err = 0; + + ptr->evlist = evlist; + ptr->snapshot_mode = opts->auxtrace_snapshot_mode; + + if (!record_opts__no_switch_events(opts) && + perf_can_record_switch_events()) + opts->record_switch_events = true; + + evlist__for_each_entry(evlist, evsel) { + if (evsel->core.attr.type == cs_etm_pmu->type) { + if (cs_etm_evsel) { + pr_err("There may be only one %s event\n", + CORESIGHT_ETM_PMU_NAME); + return -EINVAL; + } + evsel->core.attr.freq = 0; + evsel->core.attr.sample_period = 1; + cs_etm_evsel = evsel; + opts->full_auxtrace = true; + } + } + + /* no need to continue if at least one event of interest was found */ + if (!cs_etm_evsel) + return 0; + + ret = cs_etm_set_sink_attr(cs_etm_pmu, cs_etm_evsel); + if (ret) + return ret; + + if (opts->use_clockid) { + pr_err("Cannot use clockid (-k option) with %s\n", + CORESIGHT_ETM_PMU_NAME); + return -EINVAL; + } + + /* 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); + } + + /* Snapshost 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; + } + } + + /* We are in full trace mode but '-m,xyz' wasn't specified */ + if (opts->full_auxtrace && !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 provided by user */ + if (opts->auxtrace_mmap_pages) { + unsigned int max_page = (KiB(128) / page_size); + size_t sz = opts->auxtrace_mmap_pages * (size_t)page_size; + + if (!privileged && + opts->auxtrace_mmap_pages > max_page) { + opts->auxtrace_mmap_pages = max_page; + pr_err("auxtrace too big, truncating to %d\n", + max_page); + } + + if (!is_power_of_2(sz)) { + pr_err("Invalid mmap size for %s: must be a power of 2\n", + CORESIGHT_ETM_PMU_NAME); + return -EINVAL; + } + } + + if (opts->auxtrace_snapshot_mode) + pr_debug2("%s snapshot size: %zu\n", CORESIGHT_ETM_PMU_NAME, + opts->auxtrace_snapshot_size); + + /* + * To obtain the auxtrace buffer file descriptor, the auxtrace + * event must come first. + */ + perf_evlist__to_front(evlist, cs_etm_evsel); + + /* + * In the case of per-cpu mmaps, we need the CPU on the + * AUX event. We also need the contextID in order to be notified + * when a context switch happened. + */ + if (!perf_cpu_map__empty(cpus)) { + evsel__set_sample_bit(cs_etm_evsel, CPU); + + err = cs_etm_set_option(itr, cs_etm_evsel, + ETM_OPT_CTXTID | ETM_OPT_TS); + if (err) + goto out; + } + + /* Add dummy event to keep tracking */ + if (opts->full_auxtrace) { + struct evsel *tracking_evsel; + + err = parse_events(evlist, "dummy:u", NULL); + if (err) + goto out; + + tracking_evsel = evlist__last(evlist); + perf_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__empty(cpus)) + evsel__set_sample_bit(tracking_evsel, TIME); + } + +out: + return err; +} + +static u64 cs_etm_get_config(struct auxtrace_record *itr) +{ + u64 config = 0; + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + struct evlist *evlist = ptr->evlist; + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + if (evsel->core.attr.type == cs_etm_pmu->type) { + /* + * Variable perf_event_attr::config is assigned to + * ETMv3/PTM. The bit fields have been made to match + * the ETMv3.5 ETRMCR register specification. See the + * PMU_FORMAT_ATTR() declarations in + * drivers/hwtracing/coresight/coresight-perf.c for + * details. + */ + config = evsel->core.attr.config; + break; + } + } + + return config; +} + +#ifndef BIT +#define BIT(N) (1UL << (N)) +#endif + +static u64 cs_etmv4_get_config(struct auxtrace_record *itr) +{ + u64 config = 0; + u64 config_opts = 0; + + /* + * The perf event variable config bits represent both + * the command line options and register programming + * bits in ETMv3/PTM. For ETMv4 we must remap options + * to real bits + */ + config_opts = cs_etm_get_config(itr); + if (config_opts & BIT(ETM_OPT_CYCACC)) + config |= BIT(ETM4_CFG_BIT_CYCACC); + if (config_opts & BIT(ETM_OPT_CTXTID)) + config |= BIT(ETM4_CFG_BIT_CTXTID); + if (config_opts & BIT(ETM_OPT_TS)) + config |= BIT(ETM4_CFG_BIT_TS); + if (config_opts & BIT(ETM_OPT_RETSTK)) + config |= BIT(ETM4_CFG_BIT_RETSTK); + + return config; +} + +static size_t +cs_etm_info_priv_size(struct auxtrace_record *itr __maybe_unused, + struct evlist *evlist __maybe_unused) +{ + int i; + int etmv3 = 0, etmv4 = 0; + struct perf_cpu_map *event_cpus = evlist->core.cpus; + struct perf_cpu_map *online_cpus = perf_cpu_map__new(NULL); + + /* cpu map is not empty, we have specific CPUs to work with */ + if (!perf_cpu_map__empty(event_cpus)) { + for (i = 0; i < cpu__max_cpu(); i++) { + if (!cpu_map__has(event_cpus, i) || + !cpu_map__has(online_cpus, i)) + continue; + + if (cs_etm_is_etmv4(itr, i)) + etmv4++; + else + etmv3++; + } + } else { + /* get configuration for all CPUs in the system */ + for (i = 0; i < cpu__max_cpu(); i++) { + if (!cpu_map__has(online_cpus, i)) + continue; + + if (cs_etm_is_etmv4(itr, i)) + etmv4++; + else + etmv3++; + } + } + + perf_cpu_map__put(online_cpus); + + return (CS_ETM_HEADER_SIZE + + (etmv4 * CS_ETMV4_PRIV_SIZE) + + (etmv3 * CS_ETMV3_PRIV_SIZE)); +} + +static bool cs_etm_is_etmv4(struct auxtrace_record *itr, int cpu) +{ + bool ret = false; + char path[PATH_MAX]; + int scan; + unsigned int val; + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + + /* Take any of the RO files for ETMv4 and see if it present */ + snprintf(path, PATH_MAX, "cpu%d/%s", + cpu, metadata_etmv4_ro[CS_ETMV4_TRCIDR0]); + scan = perf_pmu__scan_file(cs_etm_pmu, path, "%x", &val); + + /* The file was read successfully, we have a winner */ + if (scan == 1) + ret = true; + + return ret; +} + +static int cs_etm_get_ro(struct perf_pmu *pmu, int cpu, const char *path) +{ + char pmu_path[PATH_MAX]; + int scan; + unsigned int val = 0; + + /* Get RO metadata from sysfs */ + snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu, path); + + scan = perf_pmu__scan_file(pmu, pmu_path, "%x", &val); + if (scan != 1) + pr_err("%s: error reading: %s\n", __func__, pmu_path); + + return val; +} + +static void cs_etm_get_metadata(int cpu, u32 *offset, + struct auxtrace_record *itr, + struct perf_record_auxtrace_info *info) +{ + u32 increment; + u64 magic; + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + + /* first see what kind of tracer this cpu is affined to */ + if (cs_etm_is_etmv4(itr, cpu)) { + magic = __perf_cs_etmv4_magic; + /* Get trace configuration register */ + info->priv[*offset + CS_ETMV4_TRCCONFIGR] = + cs_etmv4_get_config(itr); + /* Get traceID from the framework */ + info->priv[*offset + CS_ETMV4_TRCTRACEIDR] = + coresight_get_trace_id(cpu); + /* Get read-only information from sysFS */ + info->priv[*offset + CS_ETMV4_TRCIDR0] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv4_ro[CS_ETMV4_TRCIDR0]); + info->priv[*offset + CS_ETMV4_TRCIDR1] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv4_ro[CS_ETMV4_TRCIDR1]); + info->priv[*offset + CS_ETMV4_TRCIDR2] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv4_ro[CS_ETMV4_TRCIDR2]); + info->priv[*offset + CS_ETMV4_TRCIDR8] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv4_ro[CS_ETMV4_TRCIDR8]); + info->priv[*offset + CS_ETMV4_TRCAUTHSTATUS] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv4_ro + [CS_ETMV4_TRCAUTHSTATUS]); + + /* How much space was used */ + increment = CS_ETMV4_PRIV_MAX; + } else { + magic = __perf_cs_etmv3_magic; + /* Get configuration register */ + info->priv[*offset + CS_ETM_ETMCR] = cs_etm_get_config(itr); + /* Get traceID from the framework */ + info->priv[*offset + CS_ETM_ETMTRACEIDR] = + coresight_get_trace_id(cpu); + /* Get read-only information from sysFS */ + info->priv[*offset + CS_ETM_ETMCCER] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv3_ro[CS_ETM_ETMCCER]); + info->priv[*offset + CS_ETM_ETMIDR] = + cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_etmv3_ro[CS_ETM_ETMIDR]); + + /* How much space was used */ + increment = CS_ETM_PRIV_MAX; + } + + /* Build generic header portion */ + info->priv[*offset + CS_ETM_MAGIC] = magic; + info->priv[*offset + CS_ETM_CPU] = cpu; + /* Where the next CPU entry should start from */ + *offset += increment; +} + +static int cs_etm_info_fill(struct auxtrace_record *itr, + struct perf_session *session, + struct perf_record_auxtrace_info *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.cpus; + struct perf_cpu_map *online_cpus = perf_cpu_map__new(NULL); + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + + if (priv_size != cs_etm_info_priv_size(itr, session->evlist)) + return -EINVAL; + + if (!session->evlist->core.nr_mmaps) + return -EINVAL; + + /* If the cpu_map is empty all online CPUs are involved */ + if (perf_cpu_map__empty(event_cpus)) { + cpu_map = online_cpus; + } else { + /* Make sure all specified CPUs are online */ + for (i = 0; i < perf_cpu_map__nr(event_cpus); i++) { + if (cpu_map__has(event_cpus, i) && + !cpu_map__has(online_cpus, i)) + return -EINVAL; + } + + cpu_map = event_cpus; + } + + nr_cpu = perf_cpu_map__nr(cpu_map); + /* Get PMU type as dynamically assigned by the core */ + type = cs_etm_pmu->type; + + /* First fill out the session header */ + info->type = PERF_AUXTRACE_CS_ETM; + info->priv[CS_HEADER_VERSION_0] = 0; + info->priv[CS_PMU_TYPE_CPUS] = type << 32; + info->priv[CS_PMU_TYPE_CPUS] |= nr_cpu; + info->priv[CS_ETM_SNAPSHOT] = ptr->snapshot_mode; + + offset = CS_ETM_SNAPSHOT + 1; + + for (i = 0; i < cpu__max_cpu() && offset < priv_size; i++) + if (cpu_map__has(cpu_map, i)) + cs_etm_get_metadata(i, &offset, itr, info); + + perf_cpu_map__put(online_cpus); + + return 0; +} + +static int cs_etm_alloc_wrapped_array(struct cs_etm_recording *ptr, int idx) +{ + bool *wrapped; + int cnt = ptr->wrapped_cnt; + + /* Make @ptr->wrapped as big as @idx */ + while (cnt <= idx) + cnt++; + + /* + * Free'ed in cs_etm_recording_free(). Using realloc() to avoid + * cross compilation problems where the host's system supports + * reallocarray() but not the target. + */ + wrapped = realloc(ptr->wrapped, cnt * sizeof(bool)); + if (!wrapped) + return -ENOMEM; + + wrapped[cnt - 1] = false; + ptr->wrapped_cnt = cnt; + ptr->wrapped = wrapped; + + return 0; +} + +static bool cs_etm_buffer_has_wrapped(unsigned char *buffer, + size_t buffer_size, u64 head) +{ + u64 i, watermark; + u64 *buf = (u64 *)buffer; + size_t buf_size = buffer_size; + + /* + * We want to look the very last 512 byte (chosen arbitrarily) in + * the ring buffer. + */ + watermark = buf_size - 512; + + /* + * @head is continuously increasing - if its value is equal or greater + * than the size of the ring buffer, it has wrapped around. + */ + if (head >= buffer_size) + return true; + + /* + * The value of @head is somewhere within the size of the ring buffer. + * This can be that there hasn't been enough data to fill the ring + * buffer yet or the trace time was so long that @head has numerically + * wrapped around. To find we need to check if we have data at the very + * end of the ring buffer. We can reliably do this because mmap'ed + * pages are zeroed out and there is a fresh mapping with every new + * session. + */ + + /* @head is less than 512 byte from the end of the ring buffer */ + if (head > watermark) + watermark = head; + + /* + * Speed things up by using 64 bit transactions (see "u64 *buf" above) + */ + watermark >>= 3; + buf_size >>= 3; + + /* + * If we find trace data at the end of the ring buffer, @head has + * been there and has numerically wrapped around at least once. + */ + for (i = watermark; i < buf_size; i++) + if (buf[i]) + return true; + + return false; +} + +static int cs_etm_find_snapshot(struct auxtrace_record *itr, + int idx, struct auxtrace_mmap *mm, + unsigned char *data, + u64 *head, u64 *old) +{ + int err; + bool wrapped; + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + + /* + * Allocate memory to keep track of wrapping if this is the first + * time we deal with this *mm. + */ + if (idx >= ptr->wrapped_cnt) { + err = cs_etm_alloc_wrapped_array(ptr, idx); + if (err) + return err; + } + + /* + * Check to see if *head has wrapped around. If it hasn't only the + * amount of data between *head and *old is snapshot'ed to avoid + * bloating the perf.data file with zeros. But as soon as *head has + * wrapped around the entire size of the AUX ring buffer it taken. + */ + wrapped = ptr->wrapped[idx]; + if (!wrapped && cs_etm_buffer_has_wrapped(data, mm->len, *head)) { + wrapped = true; + ptr->wrapped[idx] = true; + } + + pr_debug3("%s: mmap index %d old head %zu new head %zu size %zu\n", + __func__, idx, (size_t)*old, (size_t)*head, mm->len); + + /* No wrap has occurred, we can just use *head and *old. */ + if (!wrapped) + return 0; + + /* + * *head has wrapped around - adjust *head and *old to pickup the + * entire content of the AUX buffer. + */ + if (*head >= mm->len) { + *old = *head - mm->len; + } else { + *head += mm->len; + *old = *head - mm->len; + } + + return 0; +} + +static int cs_etm_snapshot_start(struct auxtrace_record *itr) +{ + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct evsel *evsel; + + evlist__for_each_entry(ptr->evlist, evsel) { + if (evsel->core.attr.type == ptr->cs_etm_pmu->type) + return evsel__disable(evsel); + } + return -EINVAL; +} + +static int cs_etm_snapshot_finish(struct auxtrace_record *itr) +{ + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + struct evsel *evsel; + + evlist__for_each_entry(ptr->evlist, evsel) { + if (evsel->core.attr.type == ptr->cs_etm_pmu->type) + return evsel__enable(evsel); + } + return -EINVAL; +} + +static u64 cs_etm_reference(struct auxtrace_record *itr __maybe_unused) +{ + return (((u64) rand() << 0) & 0x00000000FFFFFFFFull) | + (((u64) rand() << 32) & 0xFFFFFFFF00000000ull); +} + +static void cs_etm_recording_free(struct auxtrace_record *itr) +{ + struct cs_etm_recording *ptr = + container_of(itr, struct cs_etm_recording, itr); + + zfree(&ptr->wrapped); + free(ptr); +} + +struct auxtrace_record *cs_etm_record_init(int *err) +{ + struct perf_pmu *cs_etm_pmu; + struct cs_etm_recording *ptr; + + cs_etm_pmu = perf_pmu__find(CORESIGHT_ETM_PMU_NAME); + + if (!cs_etm_pmu) { + *err = -EINVAL; + goto out; + } + + ptr = zalloc(sizeof(struct cs_etm_recording)); + if (!ptr) { + *err = -ENOMEM; + goto out; + } + + ptr->cs_etm_pmu = cs_etm_pmu; + ptr->itr.pmu = cs_etm_pmu; + ptr->itr.parse_snapshot_options = cs_etm_parse_snapshot_options; + ptr->itr.recording_options = cs_etm_recording_options; + ptr->itr.info_priv_size = cs_etm_info_priv_size; + ptr->itr.info_fill = cs_etm_info_fill; + ptr->itr.find_snapshot = cs_etm_find_snapshot; + ptr->itr.snapshot_start = cs_etm_snapshot_start; + ptr->itr.snapshot_finish = cs_etm_snapshot_finish; + ptr->itr.reference = cs_etm_reference; + ptr->itr.free = cs_etm_recording_free; + ptr->itr.read_finish = auxtrace_record__read_finish; + + *err = 0; + return &ptr->itr; +out: + return NULL; +} diff --git a/tools/perf/arch/arm/util/cs-etm.h b/tools/perf/arch/arm/util/cs-etm.h new file mode 100644 index 000000000..a3354bda4 --- /dev/null +++ b/tools/perf/arch/arm/util/cs-etm.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(C) 2015 Linaro Limited. All rights reserved. + * Author: Mathieu Poirier <mathieu.poirier@linaro.org> + */ + +#ifndef INCLUDE__PERF_CS_ETM_H__ +#define INCLUDE__PERF_CS_ETM_H__ + +struct auxtrace_record *cs_etm_record_init(int *err); + +#endif diff --git a/tools/perf/arch/arm/util/dwarf-regs.c b/tools/perf/arch/arm/util/dwarf-regs.c new file mode 100644 index 000000000..fc5f71c91 --- /dev/null +++ b/tools/perf/arch/arm/util/dwarf-regs.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Mapping of DWARF debug register numbers into register names. + * + * Copyright (C) 2010 Will Deacon, ARM Ltd. + */ + +#include <stddef.h> +#include <linux/stringify.h> +#include <dwarf-regs.h> + +struct pt_regs_dwarfnum { + const char *name; + unsigned int dwarfnum; +}; + +#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num} +#define GPR_DWARFNUM_NAME(num) \ + {.name = __stringify(%r##num), .dwarfnum = num} +#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0} + +/* + * Reference: + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0040a/IHI0040A_aadwarf.pdf + */ +static const struct pt_regs_dwarfnum regdwarfnum_table[] = { + GPR_DWARFNUM_NAME(0), + GPR_DWARFNUM_NAME(1), + GPR_DWARFNUM_NAME(2), + GPR_DWARFNUM_NAME(3), + GPR_DWARFNUM_NAME(4), + GPR_DWARFNUM_NAME(5), + GPR_DWARFNUM_NAME(6), + GPR_DWARFNUM_NAME(7), + GPR_DWARFNUM_NAME(8), + GPR_DWARFNUM_NAME(9), + GPR_DWARFNUM_NAME(10), + REG_DWARFNUM_NAME("%fp", 11), + REG_DWARFNUM_NAME("%ip", 12), + REG_DWARFNUM_NAME("%sp", 13), + REG_DWARFNUM_NAME("%lr", 14), + REG_DWARFNUM_NAME("%pc", 15), + REG_DWARFNUM_END, +}; + +/** + * get_arch_regstr() - lookup register name from it's DWARF register number + * @n: the DWARF register number + * + * get_arch_regstr() returns the name of the register in struct + * regdwarfnum_table from it's DWARF register number. If the register is not + * found in the table, this returns NULL; + */ +const char *get_arch_regstr(unsigned int n) +{ + const struct pt_regs_dwarfnum *roff; + for (roff = regdwarfnum_table; roff->name != NULL; roff++) + if (roff->dwarfnum == n) + return roff->name; + return NULL; +} diff --git a/tools/perf/arch/arm/util/perf_regs.c b/tools/perf/arch/arm/util/perf_regs.c new file mode 100644 index 000000000..2864e2e37 --- /dev/null +++ b/tools/perf/arch/arm/util/perf_regs.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "../../util/perf_regs.h" + +const struct sample_reg sample_reg_masks[] = { + SMPL_REG_END +}; diff --git a/tools/perf/arch/arm/util/pmu.c b/tools/perf/arch/arm/util/pmu.c new file mode 100644 index 000000000..bbc297a7e --- /dev/null +++ b/tools/perf/arch/arm/util/pmu.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(C) 2015 Linaro Limited. All rights reserved. + * Author: Mathieu Poirier <mathieu.poirier@linaro.org> + */ + +#include <string.h> +#include <linux/coresight-pmu.h> +#include <linux/perf_event.h> +#include <linux/string.h> + +#include "arm-spe.h" +#include "../../util/pmu.h" + +struct perf_event_attr +*perf_pmu__get_default_config(struct perf_pmu *pmu __maybe_unused) +{ +#ifdef HAVE_AUXTRACE_SUPPORT + if (!strcmp(pmu->name, CORESIGHT_ETM_PMU_NAME)) { + /* add ETM default config here */ + pmu->selectable = true; +#if defined(__aarch64__) + } else if (strstarts(pmu->name, ARM_SPE_PMU_NAME)) { + return arm_spe_pmu_default_config(pmu); +#endif + } + +#endif + return NULL; +} diff --git a/tools/perf/arch/arm/util/unwind-libdw.c b/tools/perf/arch/arm/util/unwind-libdw.c new file mode 100644 index 000000000..36ba4c69c --- /dev/null +++ b/tools/perf/arch/arm/util/unwind-libdw.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <elfutils/libdwfl.h> +#include "../../util/unwind-libdw.h" +#include "../../util/perf_regs.h" +#include "../../util/event.h" + +bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg) +{ + struct unwind_info *ui = arg; + struct regs_dump *user_regs = &ui->sample->user_regs; + Dwarf_Word dwarf_regs[PERF_REG_ARM_MAX]; + +#define REG(r) ({ \ + Dwarf_Word val = 0; \ + perf_reg_value(&val, user_regs, PERF_REG_ARM_##r); \ + val; \ +}) + + dwarf_regs[0] = REG(R0); + dwarf_regs[1] = REG(R1); + dwarf_regs[2] = REG(R2); + dwarf_regs[3] = REG(R3); + dwarf_regs[4] = REG(R4); + dwarf_regs[5] = REG(R5); + dwarf_regs[6] = REG(R6); + dwarf_regs[7] = REG(R7); + dwarf_regs[8] = REG(R8); + dwarf_regs[9] = REG(R9); + dwarf_regs[10] = REG(R10); + dwarf_regs[11] = REG(FP); + dwarf_regs[12] = REG(IP); + dwarf_regs[13] = REG(SP); + dwarf_regs[14] = REG(LR); + dwarf_regs[15] = REG(PC); + + return dwfl_thread_state_registers(thread, 0, PERF_REG_ARM_MAX, + dwarf_regs); +} diff --git a/tools/perf/arch/arm/util/unwind-libunwind.c b/tools/perf/arch/arm/util/unwind-libunwind.c new file mode 100644 index 000000000..3a550225d --- /dev/null +++ b/tools/perf/arch/arm/util/unwind-libunwind.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <errno.h> +#include <libunwind.h> +#include "perf_regs.h" +#include "../../util/unwind.h" +#include "../../util/debug.h" + +int libunwind__arch_reg_id(int regnum) +{ + switch (regnum) { + case UNW_ARM_R0: + return PERF_REG_ARM_R0; + case UNW_ARM_R1: + return PERF_REG_ARM_R1; + case UNW_ARM_R2: + return PERF_REG_ARM_R2; + case UNW_ARM_R3: + return PERF_REG_ARM_R3; + case UNW_ARM_R4: + return PERF_REG_ARM_R4; + case UNW_ARM_R5: + return PERF_REG_ARM_R5; + case UNW_ARM_R6: + return PERF_REG_ARM_R6; + case UNW_ARM_R7: + return PERF_REG_ARM_R7; + case UNW_ARM_R8: + return PERF_REG_ARM_R8; + case UNW_ARM_R9: + return PERF_REG_ARM_R9; + case UNW_ARM_R10: + return PERF_REG_ARM_R10; + case UNW_ARM_R11: + return PERF_REG_ARM_FP; + case UNW_ARM_R12: + return PERF_REG_ARM_IP; + case UNW_ARM_R13: + return PERF_REG_ARM_SP; + case UNW_ARM_R14: + return PERF_REG_ARM_LR; + case UNW_ARM_R15: + return PERF_REG_ARM_PC; + default: + pr_err("unwind: invalid reg id %d\n", regnum); + return -EINVAL; + } + + return -EINVAL; +} |