/*
 *	<ofmem_sparc32.c>
 *
 *	OF Memory manager
 *
 *   Copyright (C) 1999-2004 Samuel Rydh (samuel@ibrium.se)
 *   Copyright (C) 2004 Stefan Reinauer
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libc/string.h"
#include "arch/sparc32/ofmem_sparc32.h"
#include "asm/asi.h"
#include "pgtsrmmu.h"

#define OF_MALLOC_BASE		((char*)OFMEM + ALIGN_SIZE(sizeof(ofmem_t), 8))

#define MEMSIZE (256 * 1024)
static union {
	char memory[MEMSIZE];
	ofmem_t ofmem;
} s_ofmem_data;

#define OFMEM      	(&s_ofmem_data.ofmem)
#define TOP_OF_RAM 	(s_ofmem_data.memory + MEMSIZE)

translation_t **g_ofmem_translations = &s_ofmem_data.ofmem.trans;

extern uint32_t qemu_mem_size;

static inline size_t ALIGN_SIZE(size_t x, size_t a)
{
    return (x + a - 1) & ~(a-1);
}

static ucell get_heap_top( void )
{
	return (ucell)TOP_OF_RAM;
}

ofmem_t* ofmem_arch_get_private(void)
{
	return OFMEM;
}

void* ofmem_arch_get_malloc_base(void)
{
	return OF_MALLOC_BASE;
}

ucell ofmem_arch_get_heap_top(void)
{
	return get_heap_top();
}

ucell ofmem_arch_get_virt_top(void)
{
	return (ucell)OFMEM_VIRT_TOP;
}

phys_addr_t ofmem_arch_get_phys_top(void)
{
	ofmem_t *ofmem = ofmem_arch_get_private();

	return (uintptr_t)ofmem->ramsize - 0x1000000;
}

ucell ofmem_arch_get_iomem_base(void)
{
	return pointer2cell(&_end);
}

ucell ofmem_arch_get_iomem_top(void)
{
	return pointer2cell(&_iomem);
}

retain_t *ofmem_arch_get_retained(void)
{
	/* Not used */
	return 0;
}

int ofmem_arch_get_physaddr_cellsize(void)
{
	return 2;
}

int ofmem_arch_encode_physaddr(ucell *p, phys_addr_t value)
{
	int n = 0;

	p[n++] = value >> 32;
	p[n++] = value;

	return n;
}

int ofmem_arch_get_translation_entry_size(void)
{
	/* Return size of a single MMU package translation property entry in cells */
	return 3;
}

void ofmem_arch_create_translation_entry(ucell *transentry, translation_t *t)
{
	/* Generate translation property entry for SPARC. While there is no
	formal documentation for this, both Linux kernel and OpenSolaris sources
	expect a translation property entry to have the following layout:

		virtual address
		length
		mode
	*/

	transentry[0] = t->virt;
	transentry[1] = t->size;
	transentry[2] = t->mode;
}

/* Return the size of a memory available entry given the phandle in cells */
int ofmem_arch_get_available_entry_size(phandle_t ph)
{
	return 1 + ofmem_arch_get_physaddr_cellsize();
}

/* Generate memory available property entry for Sparc32 */
void ofmem_arch_create_available_entry(phandle_t ph, ucell *availentry, phys_addr_t start, ucell size)
{
  int i = 0;

	i += ofmem_arch_encode_physaddr(availentry, start);
	availentry[i] = size;
}

/* Unmap a set of pages */
void ofmem_arch_unmap_pages(ucell virt, ucell size)
{
    /* OFMEM re-maps the pages for us, so just ensure the TLB is in sync */
    srmmu_flush_whole_tlb();
}

/* Map a set of pages */
void ofmem_arch_map_pages(phys_addr_t phys, ucell virt, ucell size, ucell mode)
{
	unsigned long npages, off;
	uint32_t pte;
	unsigned long pa;

	off = phys & (PAGE_SIZE - 1);
	npages = (off + (size - 1) + (PAGE_SIZE - 1)) / PAGE_SIZE;
	phys &= ~(uint64_t)(PAGE_SIZE - 1);

	while (npages-- != 0) {
		pa = find_pte(virt, 1);

		pte = SRMMU_ET_PTE | ((phys & PAGE_MASK) >> 4);
		pte |= mode;

		*(uint32_t *)pa = pte;

		virt += PAGE_SIZE;
		phys += PAGE_SIZE;
	}
}

/* Architecture-specific OFMEM helpers */
unsigned long
find_pte(unsigned long va, int alloc)
{
    uint32_t pte;
    void *p;
    unsigned long pa;
    int ret;

    pte = l1[(va >> SRMMU_PGDIR_SHIFT) & (SRMMU_PTRS_PER_PGD - 1)];
    if ((pte & SRMMU_ET_MASK) == SRMMU_ET_INVALID) {
        if (alloc) {
            ret = ofmem_posix_memalign(&p, SRMMU_PTRS_PER_PMD * sizeof(int),
                                 SRMMU_PTRS_PER_PMD * sizeof(int));
            if (ret != 0)
                return ret;
            pte = SRMMU_ET_PTD | ((va2pa((unsigned long)p)) >> 4);
            l1[(va >> SRMMU_PGDIR_SHIFT) & (SRMMU_PTRS_PER_PGD - 1)] = pte;
            /* barrier() */
        } else {
            return -1;
        }
    }

    pa = (pte & 0xFFFFFFF0) << 4;
    pa += ((va >> SRMMU_PMD_SHIFT) & (SRMMU_PTRS_PER_PMD - 1)) << 2;
    pte = *(uint32_t *)pa2va(pa);
    if ((pte & SRMMU_ET_MASK) == SRMMU_ET_INVALID) {
        if (alloc) {
            ret = ofmem_posix_memalign(&p, SRMMU_PTRS_PER_PTE * sizeof(void *),
                                 SRMMU_PTRS_PER_PTE * sizeof(void *));
            if (ret != 0)
                return ret;
            pte = SRMMU_ET_PTD | ((va2pa((unsigned int)p)) >> 4);
            *(uint32_t *)pa2va(pa) = pte;
        } else {
            return -2;
        }
    }

    pa = (pte & 0xFFFFFFF0) << 4;
    pa += ((va >> PAGE_SHIFT) & (SRMMU_PTRS_PER_PTE - 1)) << 2;

    return pa2va(pa);
}

/************************************************************************/
/* misc                                                                 */
/************************************************************************/

ucell ofmem_arch_default_translation_mode( phys_addr_t phys )
{
	return SRMMU_REF | SRMMU_CACHE | SRMMU_PRIV;
}

ucell ofmem_arch_io_translation_mode( phys_addr_t phys )
{
	return SRMMU_REF | SRMMU_PRIV;
}

/************************************************************************/
/* init / cleanup                                                       */
/************************************************************************/

void ofmem_init( void )
{
	memset(&s_ofmem_data, 0, sizeof(s_ofmem_data));
	s_ofmem_data.ofmem.ramsize = qemu_mem_size;
	
	/* Mark the first page as non-free */
	ofmem_claim_virt(0, PAGE_SIZE, 0);
	
	/* Claim reserved physical addresses at top of RAM */
	ofmem_claim_phys(ofmem_arch_get_phys_top(), s_ofmem_data.ofmem.ramsize - ofmem_arch_get_phys_top(), 0);
	
	/* Claim OpenBIOS reserved space */
	ofmem_claim_virt(0xffd00000, 0x300000, 0);
}
