/******************************************************************************
*
* Copyright (C) 2009 - 2014 Xilinx, Inc.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/

/*
 * helloworld.c: simple test application
 *
 * This application configures UART 16550 to baud rate 9600.
 * PS7 UART (Zynq) is not initialized by this application, since
 * bootrom/bsp configures it to baud rate 115200
 *
 * ------------------------------------------------
 * | UART TYPE   BAUD RATE                        |
 * ------------------------------------------------
 *   uartns550   9600
 *   uartlite    Configurable only in HW design
 *   ps7_uart    115200 (configured by bootrom/bsp)
 */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>

#define BASE 		0xA0000000
#define PHASE 		0x0
#define LIMIT_PREF	0x4
#define LIMIT_COMP	0x8
#define LIMIT_WB	0xc

#ifdef linux
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>

const char *memdev="/dev/mem";

/*
 * The support function which returns pointer to the virtual
 * address at which starts remapped physical region in the
 * process virtual memory space.
 */
void *map_phys_address(off_t region_base, size_t region_size, int page_offset, int opt_cached)
{
	unsigned long mem_window_size;
	unsigned long pagesize;
	unsigned char *mm;
	unsigned char *mem;
	int fd;  /*
	 * Open a device ("/dev/mem") representing physical address space
	 * in POSIX systems
	 */
	fd = open(memdev, O_RDWR | (!opt_cached? O_SYNC: 0));
	if (fd < 0) {
		fprintf(stderr, "cannot open %s\n", memdev);
		return NULL;
	}  /*
	 * The virtual to physical address mapping translation granularity
	 * corresponds to memory page size. This call obtains the page
	 * size used by running operating system at given CPU architecture.
	 * 4kB are used by Linux running on ARM, ARM64, x86 and x86_64 systems.
	 */
	pagesize=sysconf(_SC_PAGESIZE);  /*
	 * Extend physical region start address and size to page size boundaries
	 * to cover complete requested region.
	 */
	mem_window_size = ((region_base & (pagesize-1)) + region_size + pagesize-1) & ~(pagesize-1);
	//printf("memw %d\n",mem_window_size);
	/*
	 * Map file (in our case physical memory) range at specified offset
	 * to virtual memory ragion/area (see VMA Linux kernel structures)
	 * of the process.
	 */

	mm = mmap(NULL, mem_window_size, PROT_WRITE|PROT_READ,
			MAP_SHARED, fd, (region_base & ~(pagesize-1)) + pagesize*page_offset);  /* Report failure if the mmap is not allowed for given file or its region */
	if (mm == MAP_FAILED) {
		fprintf(stderr,"mmap error\n");
		return NULL;
	}  /*
	 * Add offset in the page to the returned pointer for non-page-aligned
	 * requests.
	 */
	mem = mm + (region_base & (pagesize-1));  return mem;
}
#else
#define MSR(reg, v) ({ asm volatile ("msr " # reg ",%0" :: "r" (v)); })
#define MRS32(reg) ({ uint32_t v; asm volatile ("mrs %0," # reg : "=r" (v)); v; })
#define MRS64(reg) ({ uint64_t v; asm volatile ("mrs %0," # reg : "=r" (v)); v; })
#endif

static void clocks_init(void)
{
#ifndef linux
	MSR(PMCNTENSET_EL0, 0x80000000);
	MSR(PMCR_EL0, MRS32(PMCR_EL0) | 1);
#endif
}

static uint64_t clocks_get(void)
{
#ifdef linux
	struct timespec t;
	clock_gettime(CLOCK_MONOTONIC, &t);
	return (uint64_t)t.tv_sec * 1000000000 + t.tv_nsec;
#else
	return MRS64(PMCCNTR_EL0);
#endif
}

static uint32_t reg_read(uintptr_t addr)
{
	return *(volatile uint32_t *) addr;
}

static void reg_write(uintptr_t addr, uint32_t data)
{
	volatile uint32_t *localaddr = (volatile uint32_t *)addr;
	*localaddr = data;
}

#define N_MES 100
volatile static uint64_t one_write[N_MES];
volatile static uint64_t one_read[N_MES];
volatile static uint64_t write_4[N_MES];
volatile static uint64_t read_4[N_MES];
volatile static uint64_t write_read[N_MES];

int main()
{
	clocks_init();
	void * base;
#ifdef linux
	base = map_phys_address(BASE, 0x1000, 0, MAP_SHARED);
	assert(base != NULL);
#else
	base = (uintptr_t) BASE;
#endif
	srand(clocks_get());

	printf("#start\n\r");
	for (uint32_t i=0; i<N_MES; i++)
	{
		uint64_t start, stop;

		uint32_t magic0 = rand();
		uint32_t magic1 = rand();
		uint32_t magic2 = rand();
		uint32_t magic3 = rand();
		//writes
		start = clocks_get();
		reg_write(base + PHASE, magic0);
		reg_write(base + LIMIT_PREF, magic1);
		reg_write(base + LIMIT_COMP, magic2);
		reg_write(base + LIMIT_WB, magic3);
		stop = clocks_get();
		write_4[i] = stop - start;

		//reads
		start = clocks_get();
		uint32_t r0 = reg_read(base + PHASE);
		uint32_t r1 = reg_read(base + LIMIT_PREF);
		uint32_t r2 = reg_read(base + LIMIT_COMP);
		uint32_t r3 = reg_read(base + LIMIT_WB);
		stop = clocks_get();
		//check reads
		assert(r0 == magic0);
		assert(r1 == magic1);
		assert(r2 == magic2);
		assert(r3 == magic3);
		read_4[i] = stop - start;

		//writes+reads
		magic0 = rand();
		magic1 = rand();
		magic2 = rand();
		magic3 = rand();

		start = clocks_get();
		reg_write(base + PHASE, magic0);
		r0 = reg_read(base + PHASE);
		reg_write(base + LIMIT_PREF, magic1);
		r1 = reg_read(base + LIMIT_PREF);
		reg_write(base + LIMIT_COMP, magic2);
		r2 = reg_read(base + LIMIT_COMP);
		reg_write(base + LIMIT_WB, magic3);
		r3 = reg_read(base + LIMIT_WB);
		stop = clocks_get();
		//check reads
		assert(r0 == magic0);
		assert(r1 == magic1);
		assert(r2 == magic2);
		assert(r3 == magic3);
		write_read[i] = stop - start;

		magic0 = rand();
		start = clocks_get();
		reg_write(base + LIMIT_WB, magic0);
		stop = clocks_get();
		one_write[i] = stop - start;

		start = clocks_get();
		r0 = reg_read(base + LIMIT_WB);
		stop = clocks_get();
		assert(r0 == magic0);
		one_read[i] = stop - start;
	}
	printf("#reads\twrites\twrite+read\treadone\twriteone\n\r");
	for (int i=0; i < N_MES; i++){
		printf("%lu\t%lu\t%lu\t%lu\t%lu\n\r",
				read_4[i], write_4[i], write_read[i], one_read[i], one_write[i]);
	}
	fflush(0);

    return 0;
}
