@@ -120,6 +120,7 @@ perf-$(CONFIG_AUXTRACE) += arm-spe.o
perf-$(CONFIG_AUXTRACE) += arm-spe-decoder/
perf-$(CONFIG_AUXTRACE) += hisi-ptt.o
perf-$(CONFIG_AUXTRACE) += hisi-ptt-decoder/
+perf-$(CONFIG_AUXTRACE) += hisi-pmcu.o
perf-$(CONFIG_AUXTRACE) += s390-cpumsf.o
ifdef CONFIG_LIBOPENCSD
@@ -53,6 +53,7 @@
#include "intel-bts.h"
#include "arm-spe.h"
#include "hisi-ptt.h"
+#include "hisi-pmcu.h"
#include "s390-cpumsf.h"
#include "util/mmap.h"
@@ -1324,6 +1325,9 @@ int perf_event__process_auxtrace_info(struct perf_session *session,
case PERF_AUXTRACE_HISI_PTT:
err = hisi_ptt_process_auxtrace_info(event, session);
break;
+ case PERF_AUXTRACE_HISI_PMCU:
+ err = hisi_pmcu_process_auxtrace_info(event, session);
+ break;
case PERF_AUXTRACE_UNKNOWN:
default:
return -EINVAL;
new file mode 100644
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HiSilicon Performance Monitor Control Unit (PMCU) support
+ *
+ * Copyright (C) 2022 HiSilicon Limited
+ */
+
+#include <errno.h>
+#include <linux/math.h>
+#include <linux/types.h>
+#include <linux/zalloc.h>
+#include <perf/event.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "auxtrace.h"
+#include "color.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "hisi-pmcu.h"
+#include "session.h"
+#include "tool.h"
+#include <internal/lib.h>
+
+#define HISI_PMCU_AUX_HEADER_ALIGN 0x10
+#define HISI_PMCU_NR_CPU_CLUSTER 8
+#define dump_print(fmt, ...) \
+ color_fprintf(stdout, PERF_COLOR_BLUE, fmt, ##__VA_ARGS__)
+
+enum hisi_pmcu_auxtrace_header_index {
+ HISI_PMCU_HEADER_BUFFER_SIZE,
+ HISI_PMCU_HEADER_NR_PMU,
+ HISI_PMCU_HEADER_NR_CPU,
+ HISI_PMCU_HEADER_COMP_MODE,
+ HISI_PMCU_HEADER_SUBSAMPLE_SIZE,
+ HISI_PMCU_HEADER_NR_SUBSAMPLE_PER_SAMPLE,
+ HISI_PMCU_HEADER_NR_EVENT,
+ HISI_PMCU_HEADER_MAX
+};
+
+struct hisi_pmcu_aux_header_info {
+ u32 buffer_size;
+ u32 nr_pmu;
+ u32 nr_cpu;
+ u32 comp_mode;
+ u32 subsample_size;
+ u32 nr_subsample_per_sample;
+ u32 nr_event;
+ u32 events[];
+};
+
+struct hisi_pmcu_process {
+ u32 pmu_type;
+ struct auxtrace auxtrace;
+ struct hisi_pmcu_aux_header_info *header;
+};
+
+static int hisi_pmcu_process_event(struct perf_session *session __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct perf_tool *tool __maybe_unused)
+{
+ return 0;
+}
+
+static int hisi_pmcu_process_header(struct hisi_pmcu_process *pmcu,
+ const unsigned char *__data, u64 size)
+{
+ struct hisi_pmcu_aux_header_info *header;
+ const u32 *data = (const u32 *) __data;
+ unsigned int i, j;
+ u32 read_size;
+
+ read_size = HISI_PMCU_HEADER_MAX * sizeof(*data);
+ if (size < read_size)
+ return -EINVAL;
+
+ read_size += data[HISI_PMCU_HEADER_NR_EVENT] * sizeof(*data);
+ if (size < read_size)
+ return -EINVAL;
+
+ pmcu->header = malloc(read_size);
+ header = pmcu->header;
+ memcpy(header, data, read_size);
+ read_size = round_up(read_size, HISI_PMCU_AUX_HEADER_ALIGN);
+
+ dump_print(". ... Header: size 0x%lx bytes\n", read_size);
+ for (i = 0; i < read_size; i += HISI_PMCU_AUX_HEADER_ALIGN) {
+ dump_print(". %08lx: ", i);
+ for (j = 0; j < HISI_PMCU_AUX_HEADER_ALIGN; j++)
+ dump_print("%02x ", __data[i + j]);
+ dump_print("\n");
+ }
+
+ dump_print(". Auxtrace buffer max size: 0x%lx\n", header->buffer_size);
+ dump_print(". Number of PMU counters in parallel: %d\n", header->nr_pmu);
+ dump_print(". Number of monitored CPUs: %d\n", header->nr_cpu);
+ dump_print(". Compatible mode: %s\n", header->comp_mode ? "yes" : "no");
+ dump_print(". Subsample size: 0x%lx\n", header->subsample_size);
+ dump_print(". Number of subsamples per sample: %d\n", header->nr_subsample_per_sample);
+ dump_print(". Number of events: %d\n", header->nr_event);
+
+ for (i = 0; i < header->nr_event; i++)
+ dump_print(". Event %3d: 0x%04x\n", i, header->events[i]);
+
+ return read_size;
+}
+
+static int hisi_pmcu_dump_subsample(struct hisi_pmcu_aux_header_info *header,
+ const unsigned char *data, u64 offset,
+ u32 evoffset)
+{
+ int nr_cluster, core, cid, i;
+ u32 pos = 0, event;
+
+ nr_cluster = header->nr_cpu / HISI_PMCU_NR_CPU_CLUSTER;
+
+ for (cid = 0; cid < 2; cid++) {
+ for (core = 0; core < HISI_PMCU_NR_CPU_CLUSTER; core++) {
+ for (i = 0; i < nr_cluster; i++) {
+ dump_print(". %08lx: %08lx PMCID%dSR CPU %d\n",
+ offset + pos, *(u32 *) (data + pos),
+ cid,
+ core + i * HISI_PMCU_NR_CPU_CLUSTER);
+ pos += sizeof(u32);
+ }
+ }
+ }
+
+ for (event = 0; event < header->nr_pmu; event++) {
+ for (core = 0; core < HISI_PMCU_NR_CPU_CLUSTER; core++) {
+ for (i = 0; i < nr_cluster; i++) {
+ dump_print(". %08lx: %016llx Event %04lx CPU %d\n",
+ offset + pos, *(u64 *) (data + pos),
+ header->events[event + evoffset],
+ core + i * HISI_PMCU_NR_CPU_CLUSTER);
+ pos += sizeof(u64);
+ }
+ }
+ }
+
+ if (!header->comp_mode) {
+ for (core = 0; core < HISI_PMCU_NR_CPU_CLUSTER; core++) {
+ for (i = 0; i < nr_cluster; i++) {
+ dump_print(". %08lx: %016llx Cycle count CPU %d\n",
+ offset + pos, *(u64 *) (data + pos),
+ core + i * HISI_PMCU_NR_CPU_CLUSTER);
+ pos += sizeof(u64);
+ }
+ }
+ }
+
+ return pos;
+}
+
+static int hisi_pmcu_dump_sample(struct hisi_pmcu_aux_header_info *header,
+ const unsigned char *data, u64 offset)
+{
+ u32 pos = 0, i = 0;
+
+ while (i < header->nr_subsample_per_sample) {
+ dump_print(". Subsample %d\n", i + 1);
+ pos += hisi_pmcu_dump_subsample(header, data + pos,
+ offset + pos,
+ i * header->nr_pmu);
+ i++;
+ }
+
+ return pos;
+}
+
+static int hisi_pmcu_dump_data(struct hisi_pmcu_process *pmcu,
+ const unsigned char *data, u64 size)
+{
+ struct hisi_pmcu_aux_header_info *header;
+ u32 sample_size;
+ u32 nr_sample;
+ u64 pos = 0;
+ int ret;
+
+ dump_print(". ... HISI PMCU data: size 0x%lx bytes\n", size);
+
+ ret = hisi_pmcu_process_header(pmcu, data, size);
+ if (ret < 0)
+ return ret;
+
+ pos += ret;
+
+ header = pmcu->header;
+ sample_size = header->subsample_size * header->nr_subsample_per_sample;
+ nr_sample = 1;
+ dump_print(". ... Data: size 0x%lx bytes\n", size - pos);
+ while (pos < size) {
+ u32 buf_remain;
+
+ dump_print(". Sample %d\n", nr_sample);
+ pos += hisi_pmcu_dump_sample(header, data + pos, pos);
+ nr_sample++;
+
+ // Skip gap at the end of an auxtrace buffer
+ buf_remain = header->buffer_size - pos % header->buffer_size;
+ if (buf_remain < sample_size)
+ pos += buf_remain;
+ }
+
+ return 0;
+}
+
+static int hisi_pmcu_process_auxtrace_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_tool *tool __maybe_unused)
+{
+ struct hisi_pmcu_process *pmcu_process;
+ void *data;
+ u64 size;
+ int fd;
+
+ if (!dump_trace)
+ return 0;
+
+ size = event->auxtrace.size;
+ if (!size)
+ return 0;
+
+ data = malloc(size);
+ if (!data)
+ return -errno;
+
+ fd = perf_data__fd(session->data);
+
+ if (readn(fd, data, size) < 0) {
+ free(data);
+ return -errno;
+ }
+
+ pmcu_process = container_of(session->auxtrace,
+ struct hisi_pmcu_process, auxtrace);
+
+ return hisi_pmcu_dump_data(pmcu_process, data, size);
+}
+
+static int hisi_pmcu_flush_events(struct perf_session *session __maybe_unused,
+ struct perf_tool *tool __maybe_unused)
+{
+ return 0;
+}
+
+static void hisi_pmcu_free_events(struct perf_session *session __maybe_unused)
+{
+}
+
+static void hisi_pmcu_free(struct perf_session *session)
+{
+ struct hisi_pmcu_process *pmcu_process;
+
+ pmcu_process = container_of(session->auxtrace,
+ struct hisi_pmcu_process, auxtrace);
+
+ session->auxtrace = NULL;
+ free(pmcu_process);
+}
+
+static bool hisi_pmcu_evsel_is_auxtrace(struct perf_session *session,
+ struct evsel *evsel)
+{
+ struct hisi_pmcu_process *pmcu_process;
+
+ pmcu_process = container_of(session->auxtrace,
+ struct hisi_pmcu_process, auxtrace);
+
+ return evsel->core.attr.type == pmcu_process->pmu_type;
+}
+
+int hisi_pmcu_process_auxtrace_info(union perf_event *event,
+ struct perf_session *session)
+{
+ struct perf_record_auxtrace_info *auxtrace_info;
+ struct hisi_pmcu_process *pmcu_process;
+
+ auxtrace_info = &event->auxtrace_info;
+
+ if (auxtrace_info->header.size < sizeof(*auxtrace_info) +
+ HISI_PMCU_AUXTRACE_PRIV_SIZE)
+ return -EINVAL;
+
+ pmcu_process = zalloc(sizeof(*pmcu_process));
+ if (!pmcu_process)
+ return -ENOMEM;
+
+ pmcu_process->pmu_type = auxtrace_info->priv[0];
+
+ pmcu_process->auxtrace = (struct auxtrace) {
+ .process_event = hisi_pmcu_process_event,
+ .process_auxtrace_event = hisi_pmcu_process_auxtrace_event,
+ .flush_events = hisi_pmcu_flush_events,
+ .free_events = hisi_pmcu_free_events,
+ .free = hisi_pmcu_free,
+ .evsel_is_auxtrace = hisi_pmcu_evsel_is_auxtrace,
+ };
+
+ session->auxtrace = &pmcu_process->auxtrace;
+
+ return 0;
+}
@@ -14,4 +14,6 @@
struct auxtrace_record *hisi_pmcu_recording_init(int *err,
struct perf_pmu *hisi_pmcu_pmu);
+int hisi_pmcu_process_auxtrace_info(union perf_event *event,
+ struct perf_session *session);
#endif