diff options
Diffstat (limited to 'tools/testing/selftests/powerpc')
-rw-r--r-- | tools/testing/selftests/powerpc/Makefile | 39 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/harness.c | 105 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/Makefile | 23 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/count_instructions.c | 135 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/event.c | 105 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/event.h | 39 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/loop.S | 46 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/subunit.h | 47 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/utils.h | 34 |
9 files changed, 573 insertions, 0 deletions
diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile new file mode 100644 index 000000000000..bd24ae5aaeab --- /dev/null +++ b/tools/testing/selftests/powerpc/Makefile @@ -0,0 +1,39 @@ +# Makefile for powerpc selftests + +# ARCH can be overridden by the user for cross compiling +ARCH ?= $(shell uname -m) +ARCH := $(shell echo $(ARCH) | sed -e s/ppc.*/powerpc/) + +ifeq ($(ARCH),powerpc) + +GIT_VERSION = $(shell git describe --always --long --dirty || echo "unknown") + +CC := $(CROSS_COMPILE)$(CC) +CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CURDIR) $(CFLAGS) + +export CC CFLAGS + +TARGETS = pmu + +endif + +all: + @for TARGET in $(TARGETS); do \ + $(MAKE) -C $$TARGET all; \ + done; + +run_tests: all + @for TARGET in $(TARGETS); do \ + $(MAKE) -C $$TARGET run_tests; \ + done; + +clean: + @for TARGET in $(TARGETS); do \ + $(MAKE) -C $$TARGET clean; \ + done; + rm -f tags + +tags: + find . -name '*.c' -o -name '*.h' | xargs ctags + +.PHONY: all run_tests clean tags diff --git a/tools/testing/selftests/powerpc/harness.c b/tools/testing/selftests/powerpc/harness.c new file mode 100644 index 000000000000..e80c42a584fe --- /dev/null +++ b/tools/testing/selftests/powerpc/harness.c @@ -0,0 +1,105 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <errno.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "subunit.h" +#include "utils.h" + +#define TIMEOUT 120 +#define KILL_TIMEOUT 5 + + +int run_test(int (test_function)(void), char *name) +{ + bool terminated; + int rc, status; + pid_t pid; + + /* Make sure output is flushed before forking */ + fflush(stdout); + + pid = fork(); + if (pid == 0) { + exit(test_function()); + } else if (pid == -1) { + perror("fork"); + return 1; + } + + /* Wake us up in timeout seconds */ + alarm(TIMEOUT); + terminated = false; + +wait: + rc = waitpid(pid, &status, 0); + if (rc == -1) { + if (errno != EINTR) { + printf("unknown error from waitpid\n"); + return 1; + } + + if (terminated) { + printf("!! force killing %s\n", name); + kill(pid, SIGKILL); + return 1; + } else { + printf("!! killing %s\n", name); + kill(pid, SIGTERM); + terminated = true; + alarm(KILL_TIMEOUT); + goto wait; + } + } + + if (WIFEXITED(status)) + status = WEXITSTATUS(status); + else { + if (WIFSIGNALED(status)) + printf("!! child died by signal %d\n", WTERMSIG(status)); + else + printf("!! child died by unknown cause\n"); + + status = 1; /* Signal or other */ + } + + return status; +} + +static void alarm_handler(int signum) +{ + /* Jut wake us up from waitpid */ +} + +static struct sigaction alarm_action = { + .sa_handler = alarm_handler, +}; + +int test_harness(int (test_function)(void), char *name) +{ + int rc; + + test_start(name); + test_set_git_version(GIT_VERSION); + + if (sigaction(SIGALRM, &alarm_action, NULL)) { + perror("sigaction"); + test_error(name); + return 1; + } + + rc = run_test(test_function, name); + + test_finish(name, rc); + + return rc; +} diff --git a/tools/testing/selftests/powerpc/pmu/Makefile b/tools/testing/selftests/powerpc/pmu/Makefile new file mode 100644 index 000000000000..7216f0091655 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/Makefile @@ -0,0 +1,23 @@ +noarg: + $(MAKE) -C ../ + +PROGS := count_instructions +EXTRA_SOURCES := ../harness.c event.c + +all: $(PROGS) + +$(PROGS): $(EXTRA_SOURCES) + +# loop.S can only be built 64-bit +count_instructions: loop.S count_instructions.c $(EXTRA_SOURCES) + $(CC) $(CFLAGS) -m64 -o $@ $^ + +run_tests: all + @-for PROG in $(PROGS); do \ + ./$$PROG; \ + done; + +clean: + rm -f $(PROGS) loop.o + +.PHONY: all run_tests clean diff --git a/tools/testing/selftests/powerpc/pmu/count_instructions.c b/tools/testing/selftests/powerpc/pmu/count_instructions.c new file mode 100644 index 000000000000..312b4f0fd27c --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/count_instructions.c @@ -0,0 +1,135 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/prctl.h> + +#include "event.h" +#include "utils.h" + +extern void thirty_two_instruction_loop(u64 loops); + +static void setup_event(struct event *e, u64 config, char *name) +{ + event_init_opts(e, config, PERF_TYPE_HARDWARE, name); + + e->attr.disabled = 1; + e->attr.exclude_kernel = 1; + e->attr.exclude_hv = 1; + e->attr.exclude_idle = 1; +} + +static int do_count_loop(struct event *events, u64 instructions, + u64 overhead, bool report) +{ + s64 difference, expected; + double percentage; + + prctl(PR_TASK_PERF_EVENTS_ENABLE); + + /* Run for 1M instructions */ + thirty_two_instruction_loop(instructions >> 5); + + prctl(PR_TASK_PERF_EVENTS_DISABLE); + + event_read(&events[0]); + event_read(&events[1]); + + expected = instructions + overhead; + difference = events[0].result.value - expected; + percentage = (double)difference / events[0].result.value * 100; + + if (report) { + event_report(&events[0]); + event_report(&events[1]); + + printf("Looped for %llu instructions, overhead %llu\n", instructions, overhead); + printf("Expected %llu\n", expected); + printf("Actual %llu\n", events[0].result.value); + printf("Delta %lld, %f%%\n", difference, percentage); + } + + event_reset(&events[0]); + event_reset(&events[1]); + + if (difference < 0) + difference = -difference; + + /* Tolerate a difference below 0.0001 % */ + difference *= 10000 * 100; + if (difference / events[0].result.value) + return -1; + + return 0; +} + +/* Count how many instructions it takes to do a null loop */ +static u64 determine_overhead(struct event *events) +{ + u64 current, overhead; + int i; + + do_count_loop(events, 0, 0, false); + overhead = events[0].result.value; + + for (i = 0; i < 100; i++) { + do_count_loop(events, 0, 0, false); + current = events[0].result.value; + if (current < overhead) { + printf("Replacing overhead %llu with %llu\n", overhead, current); + overhead = current; + } + } + + return overhead; +} + +static int count_instructions(void) +{ + struct event events[2]; + u64 overhead; + + setup_event(&events[0], PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + setup_event(&events[1], PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (event_open(&events[0])) { + perror("perf_event_open"); + return -1; + } + + if (event_open_with_group(&events[1], events[0].fd)) { + perror("perf_event_open"); + return -1; + } + + overhead = determine_overhead(events); + printf("Overhead of null loop: %llu instructions\n", overhead); + + /* Run for 1M instructions */ + FAIL_IF(do_count_loop(events, 0x100000, overhead, true)); + + /* Run for 10M instructions */ + FAIL_IF(do_count_loop(events, 0xa00000, overhead, true)); + + /* Run for 100M instructions */ + FAIL_IF(do_count_loop(events, 0x6400000, overhead, true)); + + /* Run for 1G instructions */ + FAIL_IF(do_count_loop(events, 0x40000000, overhead, true)); + + event_close(&events[0]); + event_close(&events[1]); + + return 0; +} + +int main(void) +{ + return test_harness(count_instructions, "count_instructions"); +} diff --git a/tools/testing/selftests/powerpc/pmu/event.c b/tools/testing/selftests/powerpc/pmu/event.c new file mode 100644 index 000000000000..2b2d11df2450 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/event.c @@ -0,0 +1,105 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#define _GNU_SOURCE +#include <unistd.h> +#include <sys/syscall.h> +#include <string.h> +#include <stdio.h> +#include <sys/ioctl.h> + +#include "event.h" + + +int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, + int group_fd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, attr, pid, cpu, + group_fd, flags); +} + +void event_init_opts(struct event *e, u64 config, int type, char *name) +{ + memset(e, 0, sizeof(*e)); + + e->name = name; + + e->attr.type = type; + e->attr.config = config; + e->attr.size = sizeof(e->attr); + /* This has to match the structure layout in the header */ + e->attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | \ + PERF_FORMAT_TOTAL_TIME_RUNNING; +} + +void event_init_named(struct event *e, u64 config, char *name) +{ + event_init_opts(e, config, PERF_TYPE_RAW, name); +} + +#define PERF_CURRENT_PID 0 +#define PERF_NO_CPU -1 +#define PERF_NO_GROUP -1 + +int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd) +{ + e->fd = perf_event_open(&e->attr, pid, cpu, group_fd, 0); + if (e->fd == -1) { + perror("perf_event_open"); + return -1; + } + + return 0; +} + +int event_open_with_group(struct event *e, int group_fd) +{ + return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, group_fd); +} + +int event_open(struct event *e) +{ + return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, PERF_NO_GROUP); +} + +void event_close(struct event *e) +{ + close(e->fd); +} + +int event_reset(struct event *e) +{ + return ioctl(e->fd, PERF_EVENT_IOC_RESET); +} + +int event_read(struct event *e) +{ + int rc; + + rc = read(e->fd, &e->result, sizeof(e->result)); + if (rc != sizeof(e->result)) { + fprintf(stderr, "read error on event %p!\n", e); + return -1; + } + + return 0; +} + +void event_report_justified(struct event *e, int name_width, int result_width) +{ + printf("%*s: result %*llu ", name_width, e->name, result_width, + e->result.value); + + if (e->result.running == e->result.enabled) + printf("running/enabled %llu\n", e->result.running); + else + printf("running %llu enabled %llu\n", e->result.running, + e->result.enabled); +} + +void event_report(struct event *e) +{ + event_report_justified(e, 0, 0); +} diff --git a/tools/testing/selftests/powerpc/pmu/event.h b/tools/testing/selftests/powerpc/pmu/event.h new file mode 100644 index 000000000000..e6993192ff34 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/event.h @@ -0,0 +1,39 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_PMU_EVENT_H +#define _SELFTESTS_POWERPC_PMU_EVENT_H + +#include <unistd.h> +#include <linux/perf_event.h> + +#include "utils.h" + + +struct event { + struct perf_event_attr attr; + char *name; + int fd; + /* This must match the read_format we use */ + struct { + u64 value; + u64 running; + u64 enabled; + } result; +}; + +void event_init(struct event *e, u64 config); +void event_init_named(struct event *e, u64 config, char *name); +void event_init_opts(struct event *e, u64 config, int type, char *name); +int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd); +int event_open_with_group(struct event *e, int group_fd); +int event_open(struct event *e); +void event_close(struct event *e); +int event_reset(struct event *e); +int event_read(struct event *e); +void event_report_justified(struct event *e, int name_width, int result_width); +void event_report(struct event *e); + +#endif /* _SELFTESTS_POWERPC_PMU_EVENT_H */ diff --git a/tools/testing/selftests/powerpc/pmu/loop.S b/tools/testing/selftests/powerpc/pmu/loop.S new file mode 100644 index 000000000000..8820e3df1444 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/loop.S @@ -0,0 +1,46 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + + .text + + .global thirty_two_instruction_loop + .type .thirty_two_instruction_loop,@function + .section ".opd","aw",@progbits +thirty_two_instruction_loop: + .quad .thirty_two_instruction_loop, .TOC.@tocbase, 0 + .previous +.thirty_two_instruction_loop: + cmpwi %r3,0 + beqlr + addi %r4,%r3,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 + addi %r4,%r4,1 # 28 addi's + subi %r3,%r3,1 + b .thirty_two_instruction_loop diff --git a/tools/testing/selftests/powerpc/subunit.h b/tools/testing/selftests/powerpc/subunit.h new file mode 100644 index 000000000000..98a22920792d --- /dev/null +++ b/tools/testing/selftests/powerpc/subunit.h @@ -0,0 +1,47 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_SUBUNIT_H +#define _SELFTESTS_POWERPC_SUBUNIT_H + +static inline void test_start(char *name) +{ + printf("test: %s\n", name); +} + +static inline void test_failure_detail(char *name, char *detail) +{ + printf("failure: %s [%s]\n", name, detail); +} + +static inline void test_failure(char *name) +{ + printf("failure: %s\n", name); +} + +static inline void test_error(char *name) +{ + printf("error: %s\n", name); +} + +static inline void test_success(char *name) +{ + printf("success: %s\n", name); +} + +static inline void test_finish(char *name, int status) +{ + if (status) + test_failure(name); + else + test_success(name); +} + +static inline void test_set_git_version(char *value) +{ + printf("tags: git_version:%s\n", value); +} + +#endif /* _SELFTESTS_POWERPC_SUBUNIT_H */ diff --git a/tools/testing/selftests/powerpc/utils.h b/tools/testing/selftests/powerpc/utils.h new file mode 100644 index 000000000000..5851c4b0f553 --- /dev/null +++ b/tools/testing/selftests/powerpc/utils.h @@ -0,0 +1,34 @@ +/* + * Copyright 2013, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_UTILS_H +#define _SELFTESTS_POWERPC_UTILS_H + +#include <stdint.h> +#include <stdbool.h> + +/* Avoid headaches with PRI?64 - just use %ll? always */ +typedef unsigned long long u64; +typedef signed long long s64; + +/* Just for familiarity */ +typedef uint32_t u32; +typedef uint8_t u8; + + +int test_harness(int (test_function)(void), char *name); + + +/* Yes, this is evil */ +#define FAIL_IF(x) \ +do { \ + if ((x)) { \ + fprintf(stderr, \ + "[FAIL] Test FAILED on line %d\n", __LINE__); \ + return 1; \ + } \ +} while (0) + +#endif /* _SELFTESTS_POWERPC_UTILS_H */ |