Collect the read-only trace encoder control, features and timestamp registers per CPU into the AUXTRACE_INFO metadata and validate the requested context, syncmode, syncmax and timestamp options against the hardware before recording. Parse the metadata back in the decoder.
Signed-off-by: Zane Leung liangzhen@linux.spacemit.com --- tools/perf/arch/riscv/util/auxtrace.c | 203 +++++++++++++++++++++++++- tools/perf/util/gtrace-decoder.c | 89 ++++++++++- tools/perf/util/gtrace.h | 30 +++- 3 files changed, 311 insertions(+), 11 deletions(-)
diff --git a/tools/perf/arch/riscv/util/auxtrace.c b/tools/perf/arch/riscv/util/auxtrace.c index 882632511d09..c11c5c34cbc8 100644 --- a/tools/perf/arch/riscv/util/auxtrace.c +++ b/tools/perf/arch/riscv/util/auxtrace.c @@ -9,6 +9,7 @@ #include <linux/bitops.h> #include <linux/log2.h> #include <linux/zalloc.h> +#include <linux/bitfield.h> #include <errno.h> #include <limits.h> #include <stdlib.h> @@ -39,28 +40,217 @@ struct rvtrace_recording { struct evlist *evlist; };
+static const char * const metadata_encoder_ro[] = { + [RVTRACE_ENCODER_FORMAT] = "control/format", + [RVTRACE_ENCODER_CONTEXT] = "control/has_context", + [RVTRACE_ENCODER_SRCBITS] = "features/srcbits", + [RVTRACE_ENCODER_TIMESTAMP] = "timestamp/source", +}; + static size_t rvtrace_info_priv_size(struct auxtrace_record *itr __maybe_unused, struct evlist *evlist __maybe_unused) { - return GTRACE_AUXTRACE_PRIV_SIZE; + 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 (GTRACE_HEADER_SIZE + encoder * RVTRACE_ENCODER_PRIV_SIZE); + +} + +static bool gtrace_pmu_path_exists(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path) +{ + char pmu_path[PATH_MAX]; + + /* Get RO metadata from sysfs */ + snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu.cpu, path); + + return perf_pmu__file_exists(pmu, pmu_path); +} + +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; + bool timestamp_exists; + + 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_SRCBITS], + &info->priv[*offset + RVTRACE_ENCODER_SRCBITS]); + + timestamp_exists = gtrace_pmu_path_exists(rvtrace_pmu, cpu, + metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP]); + info->priv[*offset + RVTRACE_ENCODER_TIMESTAMP] = timestamp_exists ? 1 : 0; + + /* 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 __maybe_unused, struct perf_record_auxtrace_info *auxtrace_info, size_t priv_size) { + unsigned int i; + u32 offset; + u64 nr_cpu, type; + struct perf_cpu_map *cpu_map; + struct perf_cpu_map *event_cpus = evlist__core(session->evlist)->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 (priv_size != GTRACE_AUXTRACE_PRIV_SIZE) + if (!evlist__core(session->evlist)->nr_mmaps) return -EINVAL;
+ if (perf_cpu_map__has_any_cpu(event_cpus)) { + cpu_map = online_cpus; + } else { + 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; + auxtrace_info->type = PERF_AUXTRACE_GTRACE; - auxtrace_info->priv[0] = rvtrace_pmu->type; + auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] = type << 32; + auxtrace_info->priv[GTRACE_PMU_TYPE_CPUS] |= nr_cpu; + + offset = GTRACE_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_encoder_validate_context(struct perf_pmu *rvtrace_pmu, struct evsel *evsel, + struct perf_cpu cpu) +{ + int err; + u64 ctxt; + __u64 context; + + evsel__get_config_val(evsel, "context", &ctxt); + + if (!ctxt) + return 0; + + err = rvtrace_get_ro(rvtrace_pmu, cpu, + metadata_encoder_ro[RVTRACE_ENCODER_CONTEXT], &context); + if (err) + return err; + + if (!context) { + pr_err("context not supported"); + return -EINVAL; + }
return 0; }
+static int rvtrace_encoder_validate_timestamp(struct perf_pmu *rvtrace_pmu, struct evsel *evsel, + struct perf_cpu cpu) +{ + u64 ts; + + evsel__get_config_val(evsel, "timestamp", &ts); + + if (!ts) + return 0; + + if (!gtrace_pmu_path_exists(rvtrace_pmu, cpu, + metadata_encoder_ro[RVTRACE_ENCODER_TIMESTAMP])) { + pr_err("timestamp not supported"); + return -EINVAL; + } + + return 0; +} + +static int rvtrace_validate_config(struct perf_pmu *rvtrace_pmu, struct evsel *evsel) +{ + unsigned int idx; + int err = 0; + struct perf_cpu_map *event_cpus = evlist__core(evsel->evlist)->user_requested_cpus; + struct perf_cpu_map *intersect_cpus; + struct perf_cpu cpu; + + if (!perf_cpu_map__has_any_cpu(event_cpus)) { + 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 { + intersect_cpus = perf_cpu_map__new_online_cpus(); + } + + perf_cpu_map__for_each_cpu_skip_any(cpu, idx, intersect_cpus) { + err = rvtrace_encoder_validate_context(rvtrace_pmu, evsel, cpu); + if (err) + break; + + err = rvtrace_encoder_validate_timestamp(rvtrace_pmu, evsel, cpu); + if (err) + break; + } + + perf_cpu_map__put(intersect_cpus); + return err; +} + static int rvtrace_set_auxtrace_mmap_page(struct record_opts *opts) { bool privileged = perf_event_paranoid_check(-1); @@ -127,6 +317,9 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist evlist__to_front(evlist, rvtrace_evsel); evsel__set_sample_bit(rvtrace_evsel, TIME);
+ if (opts->sample_time_set) + evsel__set_config_if_unset(rvtrace_evsel, "timestamp", 1); + /* Add dummy event to keep tracking */ err = parse_event(evlist, "dummy:u"); if (err) @@ -139,6 +332,10 @@ static int rvtrace_recording_options(struct auxtrace_record *itr, struct evlist tracking_evsel->core.attr.sample_period = 1; evsel__set_sample_bit(tracking_evsel, TIME);
+ err = rvtrace_validate_config(rvtrace_pmu, rvtrace_evsel); + if (err) + return err; + return 0; }
diff --git a/tools/perf/util/gtrace-decoder.c b/tools/perf/util/gtrace-decoder.c index c25fbfc61928..d70cb7cd399f 100644 --- a/tools/perf/util/gtrace-decoder.c +++ b/tools/perf/util/gtrace-decoder.c @@ -12,6 +12,7 @@ #include "evlist.h" #include "session.h" #include "gtrace.h" +#include "debug.h"
struct gtrace_decoder { struct auxtrace auxtrace; @@ -19,6 +20,8 @@ struct gtrace_decoder { struct perf_session *session; struct machine *machine; u32 pmu_type; + int num_cpu; + u64 **metadata; };
static int gtrace_process_event(struct perf_session *session __maybe_unused, @@ -52,7 +55,10 @@ static void gtrace_free(struct perf_session *session) auxtrace);
session->auxtrace = NULL; - free(ptr); + for (int i = 0; i < ptr->num_cpu; i++) + zfree(&ptr->metadata[i]); + zfree(&ptr->metadata); + zfree(&ptr); }
static bool gtrace_evsel_is_auxtrace(struct perf_session *session, @@ -64,24 +70,88 @@ static bool gtrace_evsel_is_auxtrace(struct perf_session *session, return evsel->core.attr.type == ptr->pmu_type; }
+static const char * const gtrace_global_header_fmts[] = { + [GTRACE_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_SRCBITS] = " SRCBITS %lld\n", + [RVTRACE_ENCODER_TIMESTAMP] = " TIMESTAMP %lld\n", +}; + +static void gtrace_print_auxtrace_info(u64 *val, int num_cpu) +{ + int i, j, cpu = 0, nr_params = 0, fmt_offset = 0; + + for (i = 0; i < GTRACE_HEADER_MAX; i++) + fprintf(stdout, gtrace_global_header_fmts[i], val[i]); + + for (i = GTRACE_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 gtrace__process_auxtrace_info(union perf_event *event, struct perf_session *session) { struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info; struct gtrace_decoder *ptr; - - if (auxtrace_info->header.size < GTRACE_AUXTRACE_PRIV_SIZE + - sizeof(struct perf_record_auxtrace_info)) + u64 *priv; + u64 **metadata; + int num_cpu; + int priv_size; + int err; + int i; + + priv = (u64 *) auxtrace_info->priv; + num_cpu = priv[GTRACE_PMU_TYPE_CPUS] & 0xffffffff; + if (dump_trace) + gtrace_print_auxtrace_info(priv, num_cpu); + + priv_size = auxtrace_info->header.size - sizeof(struct perf_event_header) - + INFO_HEADER_SIZE - GTRACE_HEADER_SIZE; + if (num_cpu <= 0 || priv_size <= 0 || + num_cpu > priv_size / (int)sizeof(u64)) return -EINVAL;
- ptr = zalloc(sizeof(*ptr)); - if (!ptr) + metadata = zalloc(sizeof(*metadata) * num_cpu); + if (!metadata) return -ENOMEM;
+ i = GTRACE_HEADER_SIZE; + + 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] = priv[i + k]; + i += RVTRACE_ENCODER_PRIV_MAX; + } + + ptr = zalloc(sizeof(*ptr)); + if (!ptr) { + err = -ENOMEM; + goto err_free_metadata; + } + ptr->session = session; ptr->machine = &session->machines.host; ptr->auxtrace_type = auxtrace_info->type; - ptr->pmu_type = auxtrace_info->priv[0]; + ptr->pmu_type = (unsigned int) ((priv[GTRACE_PMU_TYPE_CPUS] >> 32) & 0xffffffff); + ptr->num_cpu = num_cpu; + ptr->metadata = metadata;
ptr->auxtrace.process_event = gtrace_process_event; ptr->auxtrace.process_auxtrace_event = gtrace_process_auxtrace_event; @@ -92,4 +162,9 @@ int gtrace__process_auxtrace_info(union perf_event *event, session->auxtrace = &ptr->auxtrace;
return 0; + +err_free_metadata: + for (int j = 0; j < num_cpu; j++) + zfree(&metadata); + return err; } diff --git a/tools/perf/util/gtrace.h b/tools/perf/util/gtrace.h index 02db4f2ff9f5..76006e30e7c3 100644 --- a/tools/perf/util/gtrace.h +++ b/tools/perf/util/gtrace.h @@ -11,7 +11,35 @@
struct perf_session;
-#define GTRACE_AUXTRACE_PRIV_SIZE sizeof(u64) +/* Supported trace protocol formats */ +enum gtrace_format { + GTRACE_FORMAT_ETRACE, + GTRACE_FORMAT_NTRACE, +}; + +enum { + GTRACE_PMU_TYPE_CPUS, + GTRACE_HEADER_MAX, +}; + +/* RISC-V Trace Encoder metadata */ +enum { + RVTRACE_ENCODER_CPU, + RVTRACE_ENCODER_NR_TRC_PARAMS, + RVTRACE_ENCODER_FORMAT, + RVTRACE_ENCODER_CONTEXT, + RVTRACE_ENCODER_SRCBITS, + RVTRACE_ENCODER_TIMESTAMP, + RVTRACE_ENCODER_PRIV_MAX, +}; + +#define RVTRACE_ENCODER_NR_TRC_PARAMS_LENGTH (RVTRACE_ENCODER_PRIV_MAX - RVTRACE_ENCODER_FORMAT) + +#define GTRACE_HEADER_SIZE (GTRACE_HEADER_MAX * sizeof(u64)) +#define RVTRACE_ENCODER_PRIV_SIZE (RVTRACE_ENCODER_PRIV_MAX * sizeof(u64)) + +#define INFO_HEADER_SIZE (sizeof(((struct perf_record_auxtrace_info *)0)->type) + \ + sizeof(((struct perf_record_auxtrace_info *)0)->reserved__))
int gtrace__process_auxtrace_info(union perf_event *event, struct perf_session *session); #endif