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

#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/mman.h>
#include <linux/pci.h>
#include <linux/bios32.h>

#include <asm/io.h>

#include "sysenv.h"
#include "host.h"

#include "x86_decode.h"
#include "x86_debug.h"

#define KB		1024
#define MEM_SIZE	(64*KB)		/* should be plenty for now */
#define BUSMEM_BASE	0x0000000
#define BUSMEM_SIZE	0x1000000	/* 16MB ISA/PCI bus space */

#define vuip	volatile unsigned int *

extern long mmap(long addr, long len, long prot, long flags, long fd,
		 long base);

static long pci_config_space;

#define SPARSE(x)	((x) << 5)		/* correct for APECS and LCA */
#if defined(CONFIG_ALPHA_APECS)
# define SPARSE_MEM	APECS_SPARSE_MEM
# define CONFIG_SPACE	APECS_CONF
#elif defined(CONFIG_ALPHA_LCA)
# define SPARSE_MEM	LCA_SPARSE_MEM
# define CONFIG_SPACE	LCA_CONF
#else
# error Sorry, you lose.
#endif

extern int pci_slot;		/* XXX hack---fix me */

extern void sethae (unsigned long hae);

static struct hae hae;

void printk ();


static int
mk_conf_addr(unsigned char bus, unsigned char device_fn,
	     unsigned char where, unsigned long *pci_addr)
{
	unsigned long addr;
	int device = device_fn >> 3;
	int func = device_fn & 0x7;

	/* only type 0 configuration cycle supported for now: */

	if (device > 20) {
		return -1;
	}
	addr = (1 << (11 + device)) | (func << 8) | where;
	*pci_addr = addr;
	return 0;
}


static unsigned int
conf_read(unsigned long addr)
{
    return *((volatile unsigned int*)addr);
}


static void
conf_write(unsigned long addr, unsigned int value)
{
	/* access configuration space: */
	*((volatile unsigned int*)addr) = value;
}


int
pcibios_read_config_byte (unsigned char bus, unsigned char device_fn,
			  unsigned char where, unsigned char *value)
{
    unsigned long pci_addr, addr;

    *value = 0xff;
    if (mk_conf_addr(bus, device_fn, where, &pci_addr) < 0) {
	return PCIBIOS_SUCCESSFUL;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x00;
    *value = conf_read(addr) >> ((where & 3) * 8);
    return PCIBIOS_SUCCESSFUL;
}


int
pcibios_read_config_word (unsigned char bus, unsigned char device_fn,
			  unsigned char where, unsigned short *value)
{
    unsigned long pci_addr, addr;

    *value = 0xffff;
    if (where & 0x1) {
	return PCIBIOS_BAD_REGISTER_NUMBER;
    }
    if (mk_conf_addr(bus, device_fn, where, &pci_addr)) {
	return PCIBIOS_SUCCESSFUL;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x08;
    *value = conf_read(addr) >> ((where & 3) * 8);
    return PCIBIOS_SUCCESSFUL;
}


int
pcibios_read_config_dword (unsigned char bus, unsigned char device_fn,
			   unsigned char where, unsigned int *value)
{
    unsigned long pci_addr, addr;

    if (mk_conf_addr(bus, device_fn, where, &pci_addr) < 0) {
	return 0;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x18;
    *value = conf_read(addr);
    return 0;
}


int
pcibios_write_config_byte (unsigned char bus, unsigned char device_fn,
			   unsigned char where, unsigned char value)
{
    unsigned long pci_addr, addr;

    if (mk_conf_addr(bus, device_fn, where, &pci_addr) < 0) {
	return PCIBIOS_SUCCESSFUL;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x00;
    conf_write(addr, value << ((where & 3) * 8));
    return PCIBIOS_SUCCESSFUL;
}


int
pcibios_write_config_word (unsigned char bus, unsigned char device_fn,
			   unsigned char where, unsigned short value)
{
    unsigned long pci_addr, addr;

    if (mk_conf_addr(bus, device_fn, where, &pci_addr) < 0) {
	return PCIBIOS_SUCCESSFUL;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x08;
    conf_write(addr, value << ((where & 3) * 8));
    return PCIBIOS_SUCCESSFUL;
}


int
pcibios_write_config_dword (unsigned char bus, unsigned char device_fn,
			    unsigned char where, unsigned int value)
{
    unsigned long pci_addr, addr;

    if (mk_conf_addr(bus, device_fn, where, &pci_addr) < 0) {
	return 0;
    }
    addr = pci_config_space + (pci_addr << 5) + 0x18;
    conf_write(addr, value);
    return 0;
}


int
pcibios_find_class (unsigned int class_code, unsigned short index, unsigned char *bus,
		    unsigned char *devfn)
{
    FILE * fp;
    int ibus, dev, fn, n;
    unsigned int val;

    /* this is slow like a dog, but this is for testing only... */
    fp = fopen("/proc/pci", "r");
    if (!fp) {
	perror("fopen");
	exit(1);
    }
    fscanf(fp, "%*[^\n]\n");
    while (1) {
	n = fscanf(fp, "Bus %d, device %d, function %d:\n", &ibus, &dev, &fn);
	if (n == 3) {
	    pcibios_read_config_dword(ibus, PCI_DEVFN(dev, fn), PCI_VENDOR_ID, &val);
	    if (val >> 8 == class_code) {
		if (index == 0) {
		    *bus = ibus;
		    *devfn = PCI_DEVFN(dev, fn);
		    return PCIBIOS_SUCCESSFUL;
		}
		--index;
	    }
	} else {
	    fscanf(fp, "%*[^\n]\n");
	}
    }
    fclose(fp);
    return PCIBIOS_DEVICE_NOT_FOUND;
}


int
pcibios_find_device (unsigned short vendor, unsigned short device_id,
		     unsigned short index, unsigned char *bus,
		     unsigned char *devfn)
{
    FILE * fp;
    int ibus, dev, fn, n;
    unsigned int val;

    /* this is slow like a dog, but this is for testing only... */
    fp = fopen("/proc/pci", "r");
    if (!fp) {
	perror("fopen");
	exit(1);
    }
    fscanf(fp, "%*[^\n]\n");
    while (1) {
	n = fscanf(fp, "Bus %d, device %d, function %d:\n", &ibus, &dev, &fn);
	if (n == 3) {
	    pcibios_read_config_dword(ibus, PCI_DEVFN(dev, fn), PCI_VENDOR_ID, &val);
	    if (val == (device_id << 16) | vendor) {
		if (index == 0) {
		    *bus = ibus;
		    *devfn = PCI_DEVFN(dev, fn);
		    return PCIBIOS_SUCCESSFUL;
		}
		--index;
	    }
	} else {
	    fscanf(fp, "%*[^\n]\n");
	}
    }
    fclose(fp);
    return PCIBIOS_DEVICE_NOT_FOUND;
}


unsigned long
readb(unsigned long base, unsigned long off)
{
	unsigned long result, shift, msb;

	shift = (off & 0x3) * 8;
	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	result = *(vuip) ((off << 5) + base + 0x00);
	result >>= shift;
	return 0xffUL & result;
}


unsigned long
readw(unsigned long base, unsigned long off)
{
	unsigned long result, shift, msb;

	shift = (off & 0x3) * 8;
	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	result = *(vuip) ((off << 5) + base + 0x08);
	result >>= shift;
	return 0xffffUL & result;
}


unsigned long
readl(unsigned long base, unsigned long off)
{
	unsigned long result, msb;

	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	result = *(vuip) ((off << 5) + base + 0x18);
	return result;
}


void
writeb(unsigned char b, unsigned long base, unsigned long off)
{
	unsigned long msb;
	unsigned int w;

	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	asm ("insbl %2,%1,%0" : "r="(w) : "ri"(off & 0x3), "r"(b));
	*(vuip) ((off << 5) + base + 0x00) = w;
}


void
writew(unsigned short b, unsigned long base, unsigned long off)
{
	unsigned long msb;
	unsigned int w;

	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	asm ("inswl %2,%1,%0" : "r="(w) : "ri"(off & 0x3), "r"(b));
	*(vuip) ((off << 5) + base + 0x08) = w;
}


void
writel(unsigned long w, unsigned long base, unsigned long off)
{
	unsigned long msb;

	if (off >= (1UL << 24)) {
		msb = off & 0xf8000000;
		off -= msb;
		if (msb && msb != hae.cache) {
			sethae(msb);
		}
	}
	*(vuip) ((off << 5) + base + 0x18) = w;
}


static inline unsigned long
mem_read (SysEnv *m, unsigned long addr, unsigned long size)
{
    unsigned long val = 0;

    if (addr >= 0xa0000 && addr <= 0xfffff) {
	switch (size) {
	  case 1:
	    val = readb(m->busmem_base, addr);
	    break;
	  case 2:
	    if (addr & 0x1) {
		val = ( readb(m->busmem_base, addr) |
		       (readb(m->busmem_base, addr + 1) << 8));
	    } else {
		val = readw(m->busmem_base, addr);
	    }
	    break;
	  case 4:
	    if (addr & 0x3) {
		val = ( readb(m->busmem_base, addr) |
		       (readb(m->busmem_base, addr + 1) <<  8) |
		       (readb(m->busmem_base, addr + 2) << 16) |
		       (readb(m->busmem_base, addr + 3) << 24));
	    } else {
		val = readl(m->busmem_base, addr);
	    }
	    break;
	}
    } else if (addr > m->mem_size - size) {
	printk("mem_read: address %#lx out of range!\n", addr);
	halt_sys(m);
    } else {
	switch (size) {
	  case 1:
	    val = *(u8 *)(m->mem_base + addr);
	    break;
	  case 2:
	    if (addr & 0x1) {
		val = ( *(u8*)(m->mem_base + addr) |
		       (*(u8*)(m->mem_base + addr + 1) << 8));
	    } else {
		val = *(u16*) (m->mem_base + addr);
	    }
	    break;
	  case 4:
	    if (addr & 0x3) {
		val = ( *(u8*)(m->mem_base + addr + 0) |
		       (*(u8*)(m->mem_base + addr + 1) << 8) |
		       (*(u8*)(m->mem_base + addr + 2) << 16) |
		       (*(u8*)(m->mem_base + addr + 3) << 24));
	    } else {
		val = *(u32*)(m->mem_base + addr);
	    }
	    break;
	}
    }
    if (DEBUG_MEM_TRACE(m)) {
	x86_debug_printf(m, "%#08x %d -> %#x\n", addr, size, val);
    }
    return val;
}


void
mem_write (SysEnv *m, unsigned long addr, unsigned long val,
	   unsigned long size)
{
    if (DEBUG_MEM_TRACE(m)) {
	x86_debug_printf(m, "%#08x %d <- %#x\n", addr, size, val);
    }

    if (addr >= 0xa0000 && addr <= 0xfffff) {
	/* it's an adapter BIOS ROM access */
	switch (size) {
	  case 1:
	    writeb(val, m->busmem_base, addr);
	    break;
	  case 2:
	    if (addr & 0x1) {
		writeb(val >> 0, m->busmem_base, addr);
		writeb(val >> 8, m->busmem_base, addr + 1);
	    } else {
		writew(val, m->busmem_base, addr);
	    }
	    break;
	  case 4:
	    if (addr & 0x3) {
		writeb(val >>  0, m->busmem_base, addr);
		writeb(val >>  8, m->busmem_base, addr + 1);
		writeb(val >> 16, m->busmem_base, addr + 1);
		writeb(val >> 24, m->busmem_base, addr + 1);
	    } else {
		writel(val, m->busmem_base, addr);
	    }
	    break;
	}
    } else if (addr >= m->mem_size) {
	printk("mem_write: address %#lx out of range!\n", addr);
	halt_sys(m);
    } else {
	switch (size) {
	  case 1:
	    *(u8*)(m->mem_base + addr) = val;
	    break;
	  case 2:
	    if (addr & 0x1) {
		*(u8*)(m->mem_base + addr + 0) = (val >> 0) & 0xff;
		*(u8*)(m->mem_base + addr + 1) = (val >> 8) & 0xff;
	    } else {
		*(u16*)(m->mem_base + addr) = val;
	    }
	    break;
	  case 4:
	    if (addr & 0x1) {
		*(u8*)(m->mem_base + addr + 0) = (val >>  0) & 0xff;
		*(u8*)(m->mem_base + addr + 1) = (val >>  8) & 0xff;
		*(u8*)(m->mem_base + addr + 2) = (val >> 16) & 0xff;
		*(u8*)(m->mem_base + addr + 3) = (val >> 24) & 0xff;
	    } else {
		*(u16*)(m->mem_base + addr) = val;
	    }
	    break;
	}
    }
}


u8
sys_rdb (SysEnv *m, u32 addr)
{
    return mem_read(m, addr, 1);
}


u16
sys_rdw (SysEnv *m, u32 addr)
{
    return mem_read(m, addr, 2);
}


u32
sys_rdl (SysEnv *m, u32 addr)
{
    return mem_read(m, addr, 4);
}


void
sys_wrb (SysEnv *m, u32 addr, u8 val)
{
    mem_write(m, addr, val, 1);
}


void
sys_wrw (SysEnv *m, u32 addr, u16 val)
{
    mem_write(m, addr, val, 2);
}


void
sys_wrl (SysEnv *m, u32 addr, u32 val)
{
    mem_write(m, addr, val, 4);
}


u8
sys_inb (SysEnv *m, u32 port)
{
    unsigned long val;

    val = _inb(port);
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 1 -> %#x\n", port, val);
    }
    return val;
}


u16
sys_inw (SysEnv *m, u32 port)
{
    unsigned long val;

    val = _inw(port);
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 1 -> %#x\n", port, val);
    }
    return val;
}


u32
sys_inl (SysEnv *m, u32 port)
{
    unsigned long val;

    val = _inl(port);
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 1 -> %#x\n", port, val);
    }
    return val;
}


void
sys_outb (SysEnv *m, u32 port, u8 val)
{
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 1 <- %#x\n", port, val);
    }
    _outb(val, port);
}


void
sys_outw (SysEnv *m, u32 port, u16 val)
{
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 2 <- %#x\n", port, val);
    }
    _outw(val, port);
}


void
sys_outl (SysEnv *m, u32 port, u32 val)
{
    if (DEBUG_IO_TRACE(m)) {
	x86_debug_printf(m, "\t\t\t\t%#04x 4 <- %#x\n", port, val);
    }
    _outl(val, port);
}


void
sys_init (SysEnv *m)
{
    int fd;

    if (ioperm(0x0000, 0x10000, 1)) {
	perror("ioperm");
	exit(1);
    }

    fd = open("/dev/mem", O_RDWR);
    if (fd < 0) {
	perror("/dev/mem");
	exit(1);
    }

    pci_config_space = mmap(0, 0x10000000,
			    PROT_READ|PROT_WRITE, MAP_SHARED, fd, CONFIG_SPACE);
    if (pci_config_space == -1) {
	    perror("mmap");
    }

    if (pci_slot >= 0) {
	unsigned int val;

	pcibios_read_config_dword(0, pci_slot<<3, 0x00, &val);
	printk("device/vendor id=%x\n", val);
	pcibios_write_config_dword(0, pci_slot<<3, 0x30, 0xc0001);
	pcibios_read_config_dword(0, pci_slot<<3, 0x30, &val);
	printk("ROM base address+1=%x\n", val);
    }
    m->busmem_base = mmap(0, SPARSE(BUSMEM_SIZE),
			  PROT_READ | PROT_WRITE, MAP_SHARED, fd,
			  SPARSE_MEM + SPARSE(BUSMEM_BASE));
    if ((long) m->busmem_base == -1) {
	perror("mmap");
    }

    m->mem_base = (unsigned long) kmalloc(MEM_SIZE, 0);
    if (!m->mem_base) {
	perror("malloc");
	exit(1);
    }
    m->mem_size = MEM_SIZE;
}


void
printk(const char * format, long a0, long a1, long a2, long a3, long a4,
       long a5)
{
    fprintf(stderr, format, a0, a1, a2, a3, a4, a5);
}
