/*
 *
 * Test program for AGPGART module under Linux
 *
 * Copyright (C) 1999 Jeff Hartmann,
 * Precision Insight, Inc., Xi Graphics, Inc.
 * Copyright (C) 2003-2006 Dave Jones,
 *
 */

#ifdef S_SPLINT_S
#define __signed__ signed
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <linux/agpgart.h>
#include <asm/mtrr.h>

static unsigned char *gart;
static int gartfd;
static int mtrr;

static int usec(void)
{
	struct timeval tv;
	struct timezone tz;

	gettimeofday(&tv, &tz);
	return (tv.tv_sec & 2047) * 1000000 + tv.tv_usec;
}

static int MemoryBenchmark(void *buffer, int dwords)
{
	int i;
	int start, end;
	int mb;
	int *base;

	base = (int *) buffer;
	start = usec();
	for (i = 0; i < dwords; i += 8) {
		base[i] = 0x15151515;	/* dmapad nops */
		base[i + 1] = 0x15151515;
		base[i + 2] = 0x15151515;
		base[i + 3] = 0x15151515;
		base[i + 4] = 0x15151515;
		base[i + 5] = 0x15151515;
		base[i + 6] = 0x15151515;
		base[i + 7] = 0x15151515;
	}
	end = usec();
	mb = ((float) dwords / 0x40000) * 1000000 / (end - start);
	printf("MemoryBenchmark: %i MB/s\n", mb);
	return mb;
}

static int insert_gart(int page, int size)
{
	agp_allocate entry;
	agp_bind bind;

	entry.type = 0;
	entry.pg_count = size;

#ifdef DEBUG
	printf("Using AGPIOC_ALLOCATE\n");
#endif
	if (ioctl(gartfd, AGPIOC_ALLOCATE, &entry) != 0) {
		perror("ioctl(AGPIOC_ALLOCATE)");
		exit(EXIT_FAILURE);
	}

	bind.key = entry.key;
	bind.pg_start = page;
#ifdef DEBUG
	printf ("Using AGPIOC_BIND\n");
#endif
	if (ioctl(gartfd, AGPIOC_BIND, &bind)) {
		perror("ioctl(AGPIOC_BIND)");
		exit(EXIT_FAILURE);
	}

	printf("entry.key : %i\n", entry.key);
	return entry.key;
}

static int unbind_gart(int key)
{
	agp_unbind unbind;

	unbind.key = key;
#ifdef DEBUG
	printf ("Using AGPIOC_UNBIND\n");
#endif
	if (ioctl(gartfd, AGPIOC_UNBIND, &unbind) != 0) {
		perror("ioctl(AGPIOC_UNBIND)");
		exit(EXIT_FAILURE);
	}
	return 0;
}

static int bind_gart(int key, int page)
{
	agp_bind bind;

	bind.key = key;
	bind.pg_start = page;
#ifdef DEBUG
	printf ("Using AGPIOC_BIND\n");
#endif
	if (ioctl(gartfd, AGPIOC_BIND, &bind) != 0) {
		perror("ioctl(AGPIOC_BIND)");
		exit(EXIT_FAILURE);
	}
	return 0;
}

static int remove_gart(int key)
{
#ifdef DEBUG
	printf("Using AGPIOC_DEALLOCATE\n");
#endif

	if (ioctl(gartfd, AGPIOC_DEALLOCATE, key) != 0) {
		perror("ioctl(GARTIOCREMOVE)");
		exit(EXIT_FAILURE);
	}
	return 0;
}

static void openmtrr(void)
{
	if ((mtrr = open("/proc/mtrr", O_WRONLY, 0)) == -1) {
		if (errno == ENOENT) {
			perror("/proc/mtrr not found: MTRR not enabled\n");
		} else {
			perror("Error opening /proc/mtrr:");
			perror("MTRR not enabled\n");
			exit(EXIT_FAILURE);
		}
	}
}

static void CoverRangeWithMTRR(unsigned long base, int range, int type)
{
	/* set it if we aren't just checking the number */
	if (type != -1) {
		struct mtrr_sentry sentry;

		memset(&sentry, 0, sizeof (struct mtrr_sentry));
		sentry.base = base;
		sentry.size = range;
		sentry.type = type;
		printf("Setting up MTRR at base=%p for %dMB.\n",
					(void *)sentry.base, sentry.size/1024/1024);

		if (ioctl(mtrr, MTRRIOC_ADD_ENTRY, &sentry) == -1)	{
			perror("mtrr");
			exit(EXIT_FAILURE);
		}
	}
}

static int init_agp(void)
{
	agp_info info;
	agp_setup setup;

#ifdef DEBUG
	printf ("Using AGPIOC_ACQUIRE\n");
#endif
	if (ioctl(gartfd, AGPIOC_ACQUIRE) != 0) {
		perror("ioctl(AGPIOC_ACQUIRE)");
		exit(EXIT_FAILURE);
	}
#ifdef DEBUG
	printf("Using AGPIOC_INFO\n");
#endif
	if (ioctl(gartfd, AGPIOC_INFO, &info) != 0) {
		perror("ioctl(AGPIOC_INFO)");
		exit(EXIT_FAILURE);
	}

	printf("version: %u.%u\n",
		(unsigned int) info.version.major,
		(unsigned int) info.version.minor);
	printf("bridge id: 0x%lx\n", (unsigned long) info.bridge_id);
	printf("agp_mode: 0x%x\n", (unsigned int) info.agp_mode);
	printf("aper_base: 0x%lx\n", (unsigned long) info.aper_base);
	printf("aper_size: %u\n", (unsigned int) info.aper_size);
	printf("pg_total: %u\n", (unsigned int) info.pg_total);
	printf("pg_system: %u\n", (unsigned int) info.pg_system);
	printf("pg_used: %u\n", (unsigned int) info.pg_used);

	openmtrr();
	if (mtrr != -1)
		CoverRangeWithMTRR(info.aper_base, info.aper_size * 0x100000, MTRR_TYPE_WRCOMB);

	gart = mmap(NULL, info.aper_size * 0x100000, PROT_READ | PROT_WRITE, MAP_SHARED, gartfd, 0);
	if (gart == (unsigned char *) 0xffffffff) {
		perror("mmap");
		(void)close (gartfd);
		exit(EXIT_FAILURE);
	}

	setup.agp_mode = info.agp_mode;
#ifdef DEBUG
	printf ("Using AGPIOC_SETUP\n");
#endif
	if (ioctl(gartfd, AGPIOC_SETUP, &setup) != 0) {
		perror("ioctl(AGPIOC_SETUP)");
		exit(EXIT_FAILURE);
	}
	return 0;
}

static int xchangeDummy;

static void FlushWriteCombining(void)
{
#ifdef __i386__
	__asm__ volatile(" push %%eax ; xchg %%eax, %0 ; pop %%eax"::"m" (xchangeDummy));
	__asm__ volatile(" push %%eax ; push %%ebx ; push %%ecx ; push %%edx ; movl $0,%%eax ; cpuid ; pop %%edx ; pop %%ecx ; pop %%ebx ; pop %%eax":	/* no outputs */ : /* no inputs */ );
#endif
#ifdef __x86_64__
	__asm__ volatile(" push %%rax ; xchg %%rax, %0 ; pop %%rax"::"m" (xchangeDummy));
	__asm__ volatile(" push %%rax ; push %%rbx ; push %%rcx ; push %%rdx ; movq $0,%%rax ; cpuid ; pop %%rdx ; pop %%rcx ; pop %%rbx ; pop %%rax":	/* no outputs */ : /* no inputs */ );
#endif
}

static void BenchMark()
{
	int i, worked = 1;

	i = MemoryBenchmark(gart, (1024 * 1024 * 4) / 4) +
		MemoryBenchmark(gart,(1024 * 1024 * 4) / 4) +
		MemoryBenchmark(gart, (1024 * 1024 * 4) / 4);

	printf("Average speed: %i MB/s\n", i / 3);

	printf("Testing data integrity (1st pass): ");
	fflush(stdout);

	FlushWriteCombining();

	for (i = 0; i < 8 * 0x100000; i++)
		gart[i] = i % 256;

	FlushWriteCombining();

	for (i = 0; i < 8 * 0x100000; i++) {
		if (!(gart[i] == i % 256)) {
#ifdef DEBUG
			printf("failed on %i, gart[i] = %i\n", i, gart[i]);
#endif
			worked = 0;
		}
	}

	if (!worked)
		printf("failed");
	else
		printf("passed");
	printf(" on first pass.\n");

	unbind_gart(0);
	unbind_gart(1);
	bind_gart(0, 0);
	bind_gart(1, 1024);

	worked = 1;
	printf("Testing data integrity (2nd pass): ");
	fflush(stdout);

	for (i = 0; i < 8 * 0x100000; i++) {
		if (!(gart[i] == i % 256)) {
#ifdef DEBUG
			printf("failed on %i, gart[i] = %i\n", i, gart[i]);
#endif
			worked = 0;
		}
	}

	if (!worked)
		printf("failed");
	else
		printf("passed");
	printf(" on second pass.\n");
}

int main()
{
	int key;
	int key2;

	gartfd = open("/dev/agpgart", O_RDWR);
	if (gartfd == -1) {
		perror("open");
		exit(EXIT_FAILURE);
	}

	init_agp ();
	key = insert_gart(0, 1024);
	key2 = insert_gart(1024, 1024);

#ifdef DEBUG
	printf ("Using AGPIOC_INFO\n");
	if (ioctl(gartfd, AGPIOC_INFO, &info) != 0) {
		perror("ioctl(AGPIOC_INFO)");
		exit(EXIT_FAILURE);
	}

	printf("version: %i.%i\n", info.version.major, info.version.minor);
	printf("bridge id: 0x%lx\n", info.bridge_id);
	printf("agp_mode: 0x%lx\n", info.agp_mode);
	printf("aper_base: 0x%lx\n", info.aper_base);
	printf("aper_size: %i\n", info.aper_size);
	printf("pg_total: %i\n", info.pg_total);
	printf("pg_system: %i\n", info.pg_system);
	printf("pg_used: %i\n", info.pg_used);
#endif

	printf("Allocated 8 megs of GART memory\n");

	BenchMark();

	remove_gart(key);
	remove_gart(key2);
#ifdef DEBUG
	printf ("Using AGPIOC_RELEASE\n");
#endif
	if (ioctl(gartfd, AGPIOC_RELEASE) != 0) {
		perror("ioctl(AGPIOC_RELEASE)");
		exit(EXIT_FAILURE);
	}
	close(gartfd);
	exit(EXIT_SUCCESS);
}
