Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc9b6bf0 authored by Adrian Hunter's avatar Adrian Hunter Committed by Arnaldo Carvalho de Melo
Browse files

perf tools: Add Intel PT support for PSB periods



The PSB packet is a synchronization packet that provides a starting
point for decoding or recovery from errors.

This patch adds support for a new Intel PT feature that allows the
frequency of PSB packets to be specified.

Support for this feature is indicated by
/sys/bus/event_source/devices/intel_pt/caps/psb_cyc which contains "1"
if the feature is supported and "0" otherwise.

The PSB period can be specified as a PMU config term e.g. perf record -e
intel_pt/psb_period=2/u sleep 1

The default value is 3 or the nearest lower value that is supported.  0
is always supported.

Valid values are given by:

/sys/bus/event_source/devices/intel_pt/caps/psb_periods

which contains a hexadecimal value, the bits of which represent valid
values e.g. bit 2 set means value 2 is valid.

The value is converted to the approximate number of trace bytes between
PSB packets as:

	2 ^ (value + 11)

e.g. value 3 means 16KiB bytes between PSBs

If an invalid value is entered, the error message will give a list of
valid values e.g.

	$ perf record -e intel_pt/psb_period=15/u uname
	Invalid psb_period for intel_pt. Valid values are: 0-5

tools/perf/Documentation/intel-pt.txt is updated in a later patch as
there are a number of new features being added.

For more information about PSB periods refer to the Intel 64 and IA-32
Architectures SDM Chapter 36 Intel Processor Trace from June 2015 or
later.

Signed-off-by: default avatarAdrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/1437150840-31811-18-git-send-email-adrian.hunter@intel.com


Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent 2a21d036
Loading
Loading
Loading
Loading
+210 −7
Original line number Diff line number Diff line
@@ -99,17 +99,121 @@ static int intel_pt_parse_terms(struct list_head *formats, const char *str,
	return intel_pt_parse_terms_with_default(formats, str, config);
}

static size_t intel_pt_psb_period(struct perf_pmu *intel_pt_pmu __maybe_unused,
				  struct perf_evlist *evlist __maybe_unused)
static u64 intel_pt_masked_bits(u64 mask, u64 bits)
{
	return 256;
	const u64 top_bit = 1ULL << 63;
	u64 res = 0;
	int i;

	for (i = 0; i < 64; i++) {
		if (mask & top_bit) {
			res <<= 1;
			if (bits & top_bit)
				res |= 1;
		}
		mask <<= 1;
		bits <<= 1;
	}

	return res;
}

static int intel_pt_read_config(struct perf_pmu *intel_pt_pmu, const char *str,
				struct perf_evlist *evlist, u64 *res)
{
	struct perf_evsel *evsel;
	u64 mask;

	*res = 0;

	mask = perf_pmu__format_bits(&intel_pt_pmu->format, str);
	if (!mask)
		return -EINVAL;

	evlist__for_each(evlist, evsel) {
		if (evsel->attr.type == intel_pt_pmu->type) {
			*res = intel_pt_masked_bits(mask, evsel->attr.config);
			return 0;
		}
	}

	return -EINVAL;
}

static size_t intel_pt_psb_period(struct perf_pmu *intel_pt_pmu,
				  struct perf_evlist *evlist)
{
	u64 val;
	int err, topa_multiple_entries;
	size_t psb_period;

	if (perf_pmu__scan_file(intel_pt_pmu, "caps/topa_multiple_entries",
				"%d", &topa_multiple_entries) != 1)
		topa_multiple_entries = 0;

	/*
	 * Use caps/topa_multiple_entries to indicate early hardware that had
	 * extra frequent PSBs.
	 */
	if (!topa_multiple_entries) {
		psb_period = 256;
		goto out;
	}

	err = intel_pt_read_config(intel_pt_pmu, "psb_period", evlist, &val);
	if (err)
		val = 0;

	psb_period = 1 << (val + 11);
out:
	pr_debug2("%s psb_period %zu\n", intel_pt_pmu->name, psb_period);
	return psb_period;
}

static int intel_pt_pick_bit(int bits, int target)
{
	int pos, pick = -1;

	for (pos = 0; bits; bits >>= 1, pos++) {
		if (bits & 1) {
			if (pos <= target || pick < 0)
				pick = pos;
			if (pos >= target)
				break;
		}
	}

	return pick;
}

static u64 intel_pt_default_config(struct perf_pmu *intel_pt_pmu)
{
	char buf[256];
	int psb_cyc, psb_periods, psb_period;
	int pos = 0;
	u64 config;

	intel_pt_parse_terms(&intel_pt_pmu->format, "tsc", &config);
	pos += scnprintf(buf + pos, sizeof(buf) - pos, "tsc");

	if (perf_pmu__scan_file(intel_pt_pmu, "caps/psb_cyc", "%d",
				&psb_cyc) != 1)
		psb_cyc = 1;

	if (psb_cyc) {
		if (perf_pmu__scan_file(intel_pt_pmu, "caps/psb_periods", "%x",
					&psb_periods) != 1)
			psb_periods = 0;
		if (psb_periods) {
			psb_period = intel_pt_pick_bit(psb_periods, 3);
			pos += scnprintf(buf + pos, sizeof(buf) - pos,
					 ",psb_period=%d", psb_period);
		}
	}

	pr_debug2("%s default config: %s\n", intel_pt_pmu->name, buf);

	intel_pt_parse_terms(&intel_pt_pmu->format, buf, &config);

	return config;
}

@@ -239,6 +343,103 @@ static int intel_pt_track_switches(struct perf_evlist *evlist)
	return 0;
}

static void intel_pt_valid_str(char *str, size_t len, u64 valid)
{
	unsigned int val, last = 0, state = 1;
	int p = 0;

	str[0] = '\0';

	for (val = 0; val <= 64; val++, valid >>= 1) {
		if (valid & 1) {
			last = val;
			switch (state) {
			case 0:
				p += scnprintf(str + p, len - p, ",");
				/* Fall through */
			case 1:
				p += scnprintf(str + p, len - p, "%u", val);
				state = 2;
				break;
			case 2:
				state = 3;
				break;
			case 3:
				state = 4;
				break;
			default:
				break;
			}
		} else {
			switch (state) {
			case 3:
				p += scnprintf(str + p, len - p, ",%u", last);
				state = 0;
				break;
			case 4:
				p += scnprintf(str + p, len - p, "-%u", last);
				state = 0;
				break;
			default:
				break;
			}
			if (state != 1)
				state = 0;
		}
	}
}

static int intel_pt_val_config_term(struct perf_pmu *intel_pt_pmu,
				    const char *caps, const char *name,
				    const char *supported, u64 config)
{
	char valid_str[256];
	unsigned int shift;
	unsigned long long valid;
	u64 bits;
	int ok;

	if (perf_pmu__scan_file(intel_pt_pmu, caps, "%llx", &valid) != 1)
		valid = 0;

	if (supported &&
	    perf_pmu__scan_file(intel_pt_pmu, supported, "%d", &ok) == 1 && !ok)
		valid = 0;

	valid |= 1;

	bits = perf_pmu__format_bits(&intel_pt_pmu->format, name);

	config &= bits;

	for (shift = 0; bits && !(bits & 1); shift++)
		bits >>= 1;

	config >>= shift;

	if (config > 63)
		goto out_err;

	if (valid & (1 << config))
		return 0;
out_err:
	intel_pt_valid_str(valid_str, sizeof(valid_str), valid);
	pr_err("Invalid %s for %s. Valid values are: %s\n",
	       name, INTEL_PT_PMU_NAME, valid_str);
	return -EINVAL;
}

static int intel_pt_validate_config(struct perf_pmu *intel_pt_pmu,
				    struct perf_evsel *evsel)
{
	if (!evsel)
		return 0;

	return intel_pt_val_config_term(intel_pt_pmu, "caps/psb_periods",
					"psb_period", "caps/psb_cyc",
					evsel->attr.config);
}

static int intel_pt_recording_options(struct auxtrace_record *itr,
				      struct perf_evlist *evlist,
				      struct record_opts *opts)
@@ -251,6 +452,7 @@ static int intel_pt_recording_options(struct auxtrace_record *itr,
	const struct cpu_map *cpus = evlist->cpus;
	bool privileged = geteuid() == 0 || perf_event_paranoid() < 0;
	u64 tsc_bit;
	int err;

	ptr->evlist = evlist;
	ptr->snapshot_mode = opts->auxtrace_snapshot_mode;
@@ -281,6 +483,10 @@ static int intel_pt_recording_options(struct auxtrace_record *itr,
	if (!opts->full_auxtrace)
		return 0;

	err = intel_pt_validate_config(intel_pt_pmu, intel_pt_evsel);
	if (err)
		return err;

	/* Set default sizes for snapshot mode */
	if (opts->auxtrace_snapshot_mode) {
		size_t psb_period = intel_pt_psb_period(intel_pt_pmu, evlist);
@@ -366,8 +572,6 @@ static int intel_pt_recording_options(struct auxtrace_record *itr,
	 * threads.
	 */
	if (have_timing_info && !cpu_map__empty(cpus)) {
		int err;

		err = intel_pt_track_switches(evlist);
		if (err == -EPERM)
			pr_debug2("Unable to select sched:sched_switch\n");
@@ -394,7 +598,6 @@ static int intel_pt_recording_options(struct auxtrace_record *itr,
	/* Add dummy event to keep tracking */
	if (opts->full_auxtrace) {
		struct perf_evsel *tracking_evsel;
		int err;

		err = parse_events(evlist, "dummy:u", NULL);
		if (err)