#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#ifdef _WIN32
#include "w32util.h"
#else
#include <unistd.h>
#include <sys/mman.h>
#include <nlist.h>
#endif

#include "feptypes.h"
#include "libtools.h"
#include "triglbl.h"
#include "hou2mmap.h"
#include "debugcomm.h"

int host_endian   = ENDIAN_SAYS;
int global_endian = ENDIAN_SAYS;  /* might change later */

/* 
 * host_swap4 is for swapping byte-copied memory on a big-endian host
 */
uint32 
host_swap4(uint32 l) {
	if (host_endian == ME_BIG_ENDIAN)
		return (
		(((l)&0xFF000000)>>24) | \
		(((l)&0x00FF0000)>> 8) | \
		(((l)&0x0000FF00)<< 8) | \
		(((l)&0x000000FF)<<24) );
	else
		return l;
}

/* 
 * global_swap4 swaps if there is a difference between global_endian and 
 *  host endian.  This is for global offsets from an other-endian host.
 */
uint32 
global_swap4(uint32 l) {
	if (host_endian != global_endian)
		return (
		(((l)&0xFF000000)>>24) | \
		(((l)&0x00FF0000)>> 8) | \
		(((l)&0x0000FF00)<< 8) | \
		(((l)&0x000000FF)<<24) );
	else
		return l;
}

static void
nap(int msecs)
{
	#if defined __linux__
		usleep(msecs*1000);
	#elif defined _WIN32
		w32_sleep(msecs);
	#else
		char    dummy[64];
		poll(&dummy, 0, msecs);
	#endif
}

static int
waitfor_rw(GLOBAL_COMMON *gcp)
{
	int	i;

	for (i = 0; gcp->rw.rw_req && i < 100; ++i)
		nap(10);
	if (i >= 100)
		return (0);
	return (1);
}

/* some outside programs might want to use an offset and length */
void *
fep_internal_map(char *device, unsigned long offset, size_t length)
{
	void	*va;

#ifdef _WIN32
	va = w32_mmap(device);
#else
	int	fd;

	/*
	 *	Map in FEP memory
	 */
	fd = open(device, 2);
	if (fd == -1)
		return NULL;

	va = (void *) mmap((caddr_t) 0, length,
			PROT_READ|PROT_WRITE,
			MAP_SHARED, fd, (off_t)offset);
#endif

	if (va == (void *) -1)
		return (NULL);

	return (va);
}

void
fep_internal_unmap(char *device, void *va)
{
#ifdef _WIN32
	w32_unmmap(device, va);
#endif
}

void *
fep_map_ram(char *device)
{
	return fep_internal_map(device, 0, 2*8192*1024);
}

void *
fep_map_debug(char *device)
{
	uchar	*va;

	va = fep_internal_map(device, 0, GLOBAL_DATA_OFFSET);
	if (!va)
		return (va);

	return (va + GLOBAL_DEBUG_OFFSET);
}

void *
fep_map_64011(char *device)
{
	return fep_internal_map(device, 0, 4096);
}

void
fep_get_global_endian(void *va)
{
	if (*(uint32 *)((char *)va + GLOBAL_ENDIAN_OFFSET))
		global_endian = ME_BIG_ENDIAN;
	else
		global_endian = ME_LITTLE_ENDIAN;
}

int
fep_read(char *va, ulong addr, void *vbuf, int len)
{
	ulong		gco;
	GLOBAL_COMMON	*gcp;
	int		amt;
	int		size = len;
	char		*buf = vbuf;

	/*
	 *	Get pointer to global common area
	 */
	gco = * (ulong *) (va + GLOBAL_DATA_OFFSET);
	if (gco == 0)
		return (-1);

	gcp = (GLOBAL_COMMON *) (va + host_swap4(gco));

	while (len)
	{
		/*
		 *	Read using the RW interface
		 */
		amt = len;
		if (amt > sizeof(gcp->rw.rw_data))
			amt = sizeof(gcp->rw.rw_data);

		gcp->rw.rw_type = host_swap4(RW_RAM);
		gcp->rw.rw_addr = host_swap4(addr);
		gcp->rw.rw_size = host_swap4(amt);
		gcp->rw.rw_req  = host_swap4(RW_READ);

		if (!waitfor_rw(gcp))
			return (-1);

		memcpy(buf, (char *) gcp->rw.rw_data, amt);

		addr += amt;
		buf += amt;
		len -= amt;
	}

	return (size);
}

int
fep_write(char *va, ulong addr, void *vbuf, int len)
{
	ulong		gco;
	GLOBAL_COMMON	*gcp;
	int		amt;
	int		size = len;
	char		*buf = vbuf;

	/*
	 *	Get pointer to global common area
	 */
	gco = * (ulong *) (va + GLOBAL_DATA_OFFSET);
	if (gco == 0)
		return (-1);

	gcp = (GLOBAL_COMMON *) (va + host_swap4(gco));

	while (len)
	{
		/*
		 *	Write using the RW interface
		 */
		amt = len;
		if (amt > sizeof(gcp->rw.rw_data))
			amt = sizeof(gcp->rw.rw_data);

		memcpy((char *) gcp->rw.rw_data, buf, amt);

		gcp->rw.rw_type = host_swap4(RW_RAM);
		gcp->rw.rw_addr = host_swap4(addr);
		gcp->rw.rw_size = host_swap4(amt);
		gcp->rw.rw_req  = host_swap4(RW_WRITE);

		if (!waitfor_rw(gcp))
			return (-1);

		addr += amt;
		buf += amt;
		len -= amt;
	}

	return (size);
}
