summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c
blob: 35a3426e341c7970fe48b917aae6a83f2da03f02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
 * Copyright 2014, Michael Ellerman, IBM Corp.
 * Licensed under GPLv2.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/prctl.h>

#include "ebb.h"


/*
 * Run a calibrated instruction loop and count instructions executed using
 * EBBs. Make sure the counts look right.
 */

extern void thirty_two_instruction_loop(uint64_t loops);

static bool counters_frozen = true;

static int do_count_loop(struct event *event, uint64_t instructions,
			 uint64_t overhead, bool report)
{
	int64_t difference, expected;
	double percentage;

	clear_ebb_stats();

	counters_frozen = false;
	mb();
	mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);

	thirty_two_instruction_loop(instructions >> 5);

	counters_frozen = true;
	mb();
	mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC);

	count_pmc(4, sample_period);

	event->result.value = ebb_state.stats.pmc_count[4-1];
	expected = instructions + overhead;
	difference = event->result.value - expected;
	percentage = (double)difference / event->result.value * 100;

	if (report) {
		printf("Looped for %lu instructions, overhead %lu\n", instructions, overhead);
		printf("Expected %lu\n", expected);
		printf("Actual   %llu\n", event->result.value);
		printf("Delta    %ld, %f%%\n", difference, percentage);
		printf("Took %d EBBs\n", ebb_state.stats.ebb_count);
	}

	if (difference < 0)
		difference = -difference;

	/* Tolerate a difference of up to 0.0001 % */
	difference *= 10000 * 100;
	if (difference / event->result.value)
		return -1;

	return 0;
}

/* Count how many instructions it takes to do a null loop */
static uint64_t determine_overhead(struct event *event)
{
	uint64_t current, overhead;
	int i;

	do_count_loop(event, 0, 0, false);
	overhead = event->result.value;

	for (i = 0; i < 100; i++) {
		do_count_loop(event, 0, 0, false);
		current = event->result.value;
		if (current < overhead) {
			printf("Replacing overhead %lu with %lu\n", overhead, current);
			overhead = current;
		}
	}

	return overhead;
}

static void pmc4_ebb_callee(void)
{
	uint64_t val;

	val = mfspr(SPRN_BESCR);
	if (!(val & BESCR_PMEO)) {
		ebb_state.stats.spurious++;
		goto out;
	}

	ebb_state.stats.ebb_count++;
	count_pmc(4, sample_period);
out:
	if (counters_frozen)
		reset_ebb_with_clear_mask(MMCR0_PMAO);
	else
		reset_ebb();
}

int instruction_count(void)
{
	struct event event;
	uint64_t overhead;

	SKIP_IF(!ebb_is_supported());

	event_init_named(&event, 0x400FA, "PM_RUN_INST_CMPL");
	event_leader_ebb_init(&event);
	event.attr.exclude_kernel = 1;
	event.attr.exclude_hv = 1;
	event.attr.exclude_idle = 1;

	FAIL_IF(event_open(&event));
	FAIL_IF(ebb_event_enable(&event));

	sample_period = COUNTER_OVERFLOW;

	setup_ebb_handler(pmc4_ebb_callee);
	mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
	ebb_global_enable();

	overhead = determine_overhead(&event);
	printf("Overhead of null loop: %lu instructions\n", overhead);

	/* Run for 1M instructions */
	FAIL_IF(do_count_loop(&event, 0x100000, overhead, true));

	/* Run for 10M instructions */
	FAIL_IF(do_count_loop(&event, 0xa00000, overhead, true));

	/* Run for 100M instructions */
	FAIL_IF(do_count_loop(&event, 0x6400000, overhead, true));

	/* Run for 1G instructions */
	FAIL_IF(do_count_loop(&event, 0x40000000, overhead, true));

	/* Run for 16G instructions */
	FAIL_IF(do_count_loop(&event, 0x400000000, overhead, true));

	/* Run for 64G instructions */
	FAIL_IF(do_count_loop(&event, 0x1000000000, overhead, true));

	/* Run for 128G instructions */
	FAIL_IF(do_count_loop(&event, 0x2000000000, overhead, true));

	ebb_global_disable();
	event_close(&event);

	printf("Finished OK\n");

	return 0;
}

int main(void)
{
	test_harness_set_timeout(300);
	return test_harness(instruction_count, "instruction_count");
}