/* lib.c
 * tag: simple function library
 *
 * Copyright (C) 2003 Stefan Reinauer
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */

#include "config.h"
#include "libc/vsprintf.h"
#include "libopenbios/bindings.h"
#include "spitfire.h"
#include "libopenbios/sys_info.h"
#include "boot.h"

#include "arch/sparc64/ofmem_sparc64.h"

/* Format a string and print it on the screen, just like the libc
 * function printf.
 */
int printk( const char *fmt, ... )
{
        char *p, buf[512];
	va_list args;
	int i;

	va_start(args, fmt);
        i = vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	for( p=buf; *p; p++ )
		putchar(*p);
	return i;
}

/* Private functions for mapping between physical/virtual addresses */ 
phys_addr_t
va2pa(unsigned long va)
{
    if ((va >= (unsigned long)&_start) &&
        (va < (unsigned long)&_end))
        return va - va_shift;
    else
        return va;
}

unsigned long
pa2va(phys_addr_t pa)
{
    if ((pa + va_shift >= (unsigned long)&_start) &&
        (pa + va_shift < (unsigned long)&_end))
        return pa + va_shift;
    else
        return pa;
}

void *malloc(size_t size)
{
	return ofmem_malloc(size);
}

void* realloc( void *ptr, size_t size )
{
	return ofmem_realloc(ptr, size);
}

void free(void *ptr)
{
	ofmem_free(ptr);
}

static void
mmu_open(void)
{
    RET(-1);
}

static void
mmu_close(void)
{
}

void ofmem_walk_boot_map(translation_entry_cb cb)
{
    unsigned long phys, virt, size, mode, data, mask;
    unsigned int i;

    for (i = 0; i < 64; i++) {
        data = spitfire_get_dtlb_data(i);
        if (data & SPITFIRE_TTE_VALID) {
            switch ((data >> 61) & 3) {
            default:
            case 0x0: /* 8k */
                mask = 0xffffffffffffe000ULL;
                size = PAGE_SIZE_8K;
                break;
            case 0x1: /* 64k */
                mask = 0xffffffffffff0000ULL;
                size = PAGE_SIZE_64K;
                break;
            case 0x2: /* 512k */
                mask = 0xfffffffffff80000ULL;
                size = PAGE_SIZE_512K;
                break;
            case 0x3: /* 4M */
                mask = 0xffffffffffc00000ULL;
                size = PAGE_SIZE_4M;
                break;
            }

            virt = spitfire_get_dtlb_tag(i);
            virt &= mask;

            /* extract 41bit physical address */
            phys = data & 0x000001fffffff000ULL;
			phys &= mask;

			mode = data & 0xfff;

			cb(phys, virt, size, mode);
        }
    }
}

/*
  3.6.5 translate
  ( virt -- false | phys.lo ... phys.hi mode true )
*/
static void
mmu_translate(void)
{
    ucell virt, mode;
    phys_addr_t phys;

    virt = POP();

    phys = ofmem_translate(virt, &mode);

    if (phys != -1UL) {
		PUSH(phys & 0xffffffff);
		PUSH(phys >> 32);
		PUSH(mode);
		PUSH(-1);
    }
    else {
    	PUSH(0);
    }
}

/*
 * D5.3 pgmap@ ( va -- tte )
 */
static void
pgmap_fetch(void)
{
    unsigned long va, tte_data;

    va = POP();

    tte_data = find_tte(va);
    if (tte_data == -1)
        goto error;

    /* return tte_data */
    PUSH(tte_data);
    return;

error:
    /* If we get here, there was no entry */
    PUSH(0);
}

/*
  ( index tte_data vaddr -- ? )
*/
static void
dtlb_load(void)
{
    unsigned long vaddr, tte_data, idx;

    vaddr = POP();
    tte_data = POP();
    idx = POP();
    dtlb_load3(vaddr, tte_data, idx);
}

/* MMU D-TLB miss handler */
void
dtlb_miss_handler(void)
{
	unsigned long faultva, tte_data = 0;

	/* Grab fault address from MMU and round to nearest 8k page */
	faultva = dtlb_faultva();
	faultva >>= 13;
	faultva <<= 13;

	/* If a valid va>tte-data routine has been set, invoke that Forth xt instead */
	if (va2ttedata && *va2ttedata != 0) {

		/* va>tte-data ( addr cnum -- false | tte-data true ) */
		PUSH(faultva);
		PUSH(0);
		enterforth(*va2ttedata);

		/* Check the result first... */
		tte_data = POP();
		if (!tte_data) {
			bug();
		} else {
			/* Grab the real data */
			tte_data = POP();
		}		
	} else {
		/* Search the ofmem linked list for this virtual address */
		tte_data = find_tte(faultva);
	}

	if (tte_data) {
		/* Update MMU */
		dtlb_load2(faultva, tte_data);
	} else {
		/* If we got here, there was no translation so fail */
		bug();
	}

}

/*
  ( index tte_data vaddr -- ? )
*/
static void
itlb_load(void)
{
    unsigned long vaddr, tte_data, idx;

    vaddr = POP();
    tte_data = POP();
    idx = POP();
    itlb_load3(vaddr, tte_data, idx);
}

/* MMU I-TLB miss handler */
void
itlb_miss_handler(void)
{
	unsigned long faultva, tte_data = 0;

	/* Grab fault address from MMU and round to nearest 8k page */
	faultva = itlb_faultva();
	faultva >>= 13;
	faultva <<= 13;

	/* If a valid va>tte-data routine has been set, invoke that Forth xt instead */
	if (va2ttedata && *va2ttedata != 0) {

		/* va>tte-data ( addr cnum -- false | tte-data true ) */
		PUSH(faultva);
		PUSH(0);
		enterforth(*va2ttedata);

		/* Check the result first... */
		tte_data = POP();
		if (!tte_data) {
			bug();
		} else {
			/* Grab the real data */
			tte_data = POP();
		}		
	} else {
		/* Search the ofmem linked list for this virtual address */
		tte_data = find_tte(faultva);
	}

	if (tte_data) {
		/* Update MMU */
		itlb_load2(faultva, tte_data);
	} else {
		/* If we got here, there was no translation so fail */
		bug();
	}
}

void
prom_debug_handler(void)
{
	/* Execute the current debugger-hook */
	feval("debugger-hook");
}

/*
  3.6.5 map
  ( phys.lo ... phys.hi virt size mode -- )
*/
static void
mmu_map(void)
{
    ucell virt, size, mode;
    phys_addr_t phys;

    mode = POP();
    size = POP();
    virt = POP();
    phys = POP();
    phys <<= 32;
    phys |= POP();

    ofmem_map(phys, virt, size, mode);
}

/*
  3.6.5 unmap
  ( virt size -- )
*/
static void
mmu_unmap(void)
{
    ucell virt, size;

    size = POP();
    virt = POP();
    ofmem_unmap(virt, size);
}

/*
  3.6.5 claim
  ( virt size align -- base )
*/
static void
mmu_claim(void)
{
    ucell virt=-1UL, size, align;

    align = POP();
    size = POP();
    if (!align) {
    	virt = POP();
    }

    virt = ofmem_claim_virt(virt, size, align);

    PUSH(virt);
}

/*
  3.6.5 release
  ( virt size -- )
*/
static void
mmu_release(void)
{
    ucell virt, size;

    size = POP();
    virt = POP();

    ofmem_release_virt(virt, size);
}

/* ( phys size align --- base ) */
static void
mem_claim( void )
{
    ucell size, align;
    phys_addr_t phys=-1UL;

    align = POP();
    size = POP();
    if (!align) {
        phys = POP();
        phys <<= 32;
        phys |= POP();
    }

    phys = ofmem_claim_phys(phys, size, align);

    PUSH(phys & 0xffffffffUL);
    PUSH(phys >> 32);
}

/* ( phys size --- ) */
static void
mem_release( void )
{
    phys_addr_t phys;
    ucell size;

    size = POP();
    phys = POP();
    phys <<= 32;
    phys |= POP();

    ofmem_release_phys(phys, size);
}

/* ( name-cstr phys size align --- phys ) */
static void
mem_retain ( void )
{
    ucell size, align;
    phys_addr_t phys=-1UL;

    align = POP();
    size = POP();
    if (!align) {
        phys = POP();
        phys <<= 32;
        phys |= POP();
    }

    /* Currently do nothing with the name */
    POP();

    phys = ofmem_retain(phys, size, align);

    PUSH(phys & 0xffffffffUL);
    PUSH(phys >> 32);
}

/* ( virt size align -- baseaddr|-1 ) */
static void
ciface_claim( void )
{
	ucell align = POP();
	ucell size = POP();
	ucell virt = POP();
	ucell ret = ofmem_claim( virt, size, align );

	/* printk("ciface_claim: %08x %08x %x\n", virt, size, align ); */
	PUSH( ret );
}

/* ( virt size -- ) */
static void
ciface_release( void )
{
	ucell size = POP();
	ucell virt = POP();
	ofmem_release(virt, size);
}

DECLARE_NODE(memory, INSTALL_OPEN, 0, "/memory");

NODE_METHODS( memory ) = {
    { "claim",              mem_claim       },
    { "release",            mem_release     },
    { "SUNW,retain",        mem_retain      },
};

DECLARE_NODE(mmu, INSTALL_OPEN, 0, "/virtual-memory");

NODE_METHODS(mmu) = {
    { "open",               mmu_open              },
    { "close",              mmu_close             },
    { "translate",          mmu_translate         },
    { "SUNW,dtlb-load",     dtlb_load             },
    { "SUNW,itlb-load",     itlb_load             },
    { "map",                mmu_map               },
    { "unmap",              mmu_unmap             },
    { "claim",              mmu_claim             },
    { "release",            mmu_release           },
};

void ob_mmu_init(const char *cpuname, uint64_t ram_size)
{
    /* memory node */
    REGISTER_NODE(memory);

    /* MMU node */
    REGISTER_NODE(mmu);

    ofmem_register(find_dev("/memory"), find_dev("/virtual-memory"));

    push_str("/chosen");
    fword("find-device");

    push_str("/virtual-memory");
    fword("open-dev");
    fword("encode-int");
    push_str("mmu");
    fword("property");

    push_str("/memory");
    fword("find-device");

    /* All memory: 0 to RAM_size */
    PUSH(0);
    fword("encode-int");
    PUSH(0);
    fword("encode-int");
    fword("encode+");
    PUSH((int)(ram_size >> 32));
    fword("encode-int");
    fword("encode+");
    PUSH((int)(ram_size & 0xffffffff));
    fword("encode-int");
    fword("encode+");
    push_str("reg");
    fword("property");

    push_str("/openprom/client-services");
    fword("find-device");
    bind_func("cif-claim", ciface_claim);
    bind_func("cif-release", ciface_release);

    /* Other MMU functions */
    PUSH(0);
    fword("active-package!");
    bind_func("pgmap@", pgmap_fetch);

    /* Find address of va2ttedata defer word contents for MMU miss handlers */
    va2ttedata = (ucell *)findword("va>tte-data");
    va2ttedata++;
}
