/*
 * perf events self profiling example test case for hw breakpoints.
 *
 * Based on:
 *   http://ozlabs.org/~anton/junkcode/perf_events_example1.c
 * 
 * Requires perf_event.h and hw_breakpoint.h from a recent kernel tree,
 * or download from git via:
 *
 * wget -O perf_event.h "http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=blob_plain;f=include/linux/perf_event.h;hb=HEAD"
 * wget -O hw_breakpoint.h "http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=blob_plain;f=include/linux/hw_breakpoint.h;hb=HEAD"
 * Build with:
 * 
 * gcc -O2 -o hw_brk_test hw_brk_test.c -lpthread
 *
 * Copyright 2012 Michael Neuling, IBM Corporation <mikey@au.ibm.com>
 * Copyright 2012 Anton Blanchard, IBM Corporation <anton@au.ibm.com>
 */

#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <elf.h>
#include "perf_event.h"
#include "hw_breakpoint.h"
#include "common.h"

#ifndef __NR_perf_event_open
#if defined(__PPC__)
#define __NR_perf_event_open	319
#elif defined(__i386__)
#define __NR_perf_event_open	336
#elif defined(__x86_64__)
#define __NR_perf_event_open	298
#else
#error __NR_perf_event_open must be defined
#endif
#endif

int max_loops = 1048576;
int num_threads;
int fail = 0;
int arraytest;

#define DAWR_LENGTH_MAX ((0x3f + 1) * 8)

void usage(char *p)
{
	printf("Syntax:\n\t%s [-h] [<num threads> [<loops max>]]\n",
	       p);
}

static inline int sys_perf_event_open(struct perf_event_attr *attr, pid_t pid,
				      int cpu, int group_fd,
				      unsigned long flags)
{
	attr->size = sizeof(*attr);
	return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}

static void *runtestsingle(void *vptr_args)
{
	int i,j;
	struct perf_event_attr attr;
	size_t res;
	unsigned long long breaks, needed;
	int readint; /* random stacks will give diff addr here */
	int readintarraybig[2*DAWR_LENGTH_MAX/sizeof(int)];
	int *readintarray;
	volatile int *ptr;
	int break_fd;
	int loop_num = rand() % max_loops;
	int readwriteflag = (rand() % 3) + 1; // needs to be 1-3
	int exclude_user = rand() % 2;
	volatile int *k;

	// align to 0x400 boundary as required by DAWR
	readintarray = (int *)(((unsigned long)readintarraybig + 0x7ff) & 0xfffffffffffff800); 

	ptr = &readint;
	if (arraytest)
		ptr = &readintarray[0];

	// setup counters
	memset(&attr, 0, sizeof(attr));
	attr.disabled = 1;
	attr.type = PERF_TYPE_BREAKPOINT;
	attr.bp_type = readwriteflag;
	attr.bp_addr = (__u64)ptr;
	attr.bp_len = sizeof(int);
	if (arraytest)
		attr.bp_len = DAWR_LENGTH_MAX;
	attr.exclude_user = exclude_user;
	break_fd = sys_perf_event_open(&attr, 0, -1, -1, 0);
	if (break_fd < 0) {
		perror("sys_perf_event_open");
		exit(1);
	}

	// start counters
	ioctl(break_fd, PERF_EVENT_IOC_ENABLE);

	// test
	k = &readint;
	for (i = 0; i < loop_num; i++) {
		if (arraytest) {
			k = &(readintarray[i % (DAWR_LENGTH_MAX/sizeof(int))]);
			if ((((unsigned long)k) >> 10) != (((unsigned long)readintarray) >> 10))
				printf("Going to fail\n");
		}

		j = *k;
		*k = j;
	}

	// stop counters
	ioctl(break_fd, PERF_EVENT_IOC_DISABLE);

	// read and check counters
	res = read(break_fd, &breaks, sizeof(unsigned long long));
	assert(res == sizeof(unsigned long long));
	// we read and write each loop, so subtract the ones we are counting
	needed = 0;
	if (readwriteflag & HW_BREAKPOINT_R)
		needed += loop_num;
	if (readwriteflag & HW_BREAKPOINT_W)
		needed += loop_num;
	needed = needed * (1 - exclude_user);
	if (breaks != needed) {
		printf("FAILED: 0x%lx brks:%lld needed:%i %i %i %i\n\n",
		       (unsigned long int)ptr, breaks, needed, loop_num, readwriteflag, exclude_user);
		fail = 1;
	}
	close(break_fd);

	return NULL;
}

void runtest(void)
{
	pthread_t	*threads;
	int i;

	if ((threads = malloc(num_threads * sizeof(pthread_t))) == NULL) {
		perror("pthread malloc");
	}

	/* start threads */
	for (i = 0; i < num_threads; i++){
		if (pthread_create(&threads[i], NULL, runtestsingle, NULL) != 0) {
			perror("pthreads_create");
			fail = 1;
		}
	}

	/* wait for them to end */
	for (i = 0; i < num_threads; i++) {
		pthread_join(threads[i], NULL);
	}
}

int check_test(void)
{
	printf("threads=%i loops=%i %s test: ", num_threads, max_loops,
	       arraytest?"array":"scalar");
	if (fail){
		printf("FAILED!!!\n");
		exit(fail);
	} else
		printf("Passed\n");

	return 0;
}

int main(int argc, char *argv[], char **envp)
{
	char *e;

	srand ( time(NULL) );
	num_threads = sysconf(_SC_NPROCESSORS_ONLN) * 2;

	e = getenv("THREADS");
	if (e)
		num_threads = atoi(e);
	e = getenv("LOOPS");
	if (e)
		max_loops = atoi(e);
	if (argc > 1) {
		if (strcmp(argv[1], "-h") == 0) {
			usage(argv[0]);
			return 0;
		} else {
			num_threads = atoi(argv[1]);
			if (argc > 2)
				max_loops = atoi(argv[2]);
		}
	}


	fail = 0;
	arraytest = 0;
	runtest();
	check_test();


	if (! dawr_present(envp))
		exit(0);
	fail = 0;
	arraytest = 1;
	runtest();
	check_test();

}
