/* Memory utilities

   Copyright (C) 1995, 1996 David S. Miller
   		 1996 Jakub Jelinek
   		 1996 Andrew Tridgell

   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; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "silo.h"

struct linux_prom_registers prom_reg_memlist[64];
struct linux_mlist_v0 prom_phys_avail[64];

/* Internal Prom library routine to sort a linux_mlist_v0 memory
 * list.  Used below in initialization.
 */
static void prom_sortmemlist (struct linux_mlist_v0 *thislist)
{
    int swapi = 0;
    int i, mitr, tmpsize;
    char *tmpaddr;
    char *lowest;

    for (i = 0; thislist[i].theres_more != 0; i++) {
	lowest = thislist[i].start_adr;
	for (mitr = i + 1; thislist[mitr - 1].theres_more != 0; mitr++)
	    if (thislist[mitr].start_adr < lowest) {
		lowest = thislist[mitr].start_adr;
		swapi = mitr;
	    }
	if (lowest == thislist[i].start_adr)
	    continue;
	tmpaddr = thislist[swapi].start_adr;
	tmpsize = thislist[swapi].num_bytes;
	for (mitr = swapi; mitr > i; mitr--) {
	    thislist[mitr].start_adr = thislist[mitr - 1].start_adr;
	    thislist[mitr].num_bytes = thislist[mitr - 1].num_bytes;
	}
	thislist[i].start_adr = tmpaddr;
	thislist[i].num_bytes = tmpsize;
    }
}

/* Initialize the memory lists based upon the prom version. */
struct linux_mlist_v0 *prom_meminit (void)
{
    int node = 0;
    unsigned int iter, num_regs;
    struct linux_mlist_v0 *mptr;	/* ptr for traversal */

    static meminited = 0;
    
    if (meminited) return prom_phys_avail;
    meminited = 1;

    switch (prom_vers) {
    case PROM_V0:
	/* Nice, kind of easier to do in this case. */
	/* First, the total physical descriptors. */
	/* Last, the available physical descriptors. */
	for (mptr = (*(romvec->pv_v0mem.v0_available)), iter = 0;
	     mptr; mptr = mptr->theres_more, iter++) {
	    prom_phys_avail[iter].start_adr = mptr->start_adr;
	    prom_phys_avail[iter].num_bytes = mptr->num_bytes;
	    prom_phys_avail[iter].theres_more = &prom_phys_avail[iter + 1];
	}
	prom_phys_avail[iter - 1].theres_more = 0;
	prom_sortmemlist (prom_phys_avail);
	break;
    case PROM_V2:
    case PROM_V3:
	/* Grrr, have to traverse the prom device tree ;( */
	node = prom_getchild (prom_root_node);
	node = prom_searchsiblings (node, "memory");
	num_regs = prom_getproperty (node, "available",
				     (char *) prom_reg_memlist,
				     sizeof (prom_reg_memlist));
	num_regs = (num_regs / sizeof (struct linux_prom_registers));
	for (iter = 0; iter < num_regs; iter++) {
	    prom_phys_avail[iter].start_adr =
		prom_reg_memlist[iter].phys_addr;
	    prom_phys_avail[iter].num_bytes =
		(unsigned long) prom_reg_memlist[iter].reg_size;
	    prom_phys_avail[iter].theres_more =
		&prom_phys_avail[iter + 1];
	}
	prom_phys_avail[iter - 1].theres_more = 0;
	prom_sortmemlist (prom_phys_avail);
	break;

    }
    return prom_phys_avail;
}

static int sun4c_hwflushes;
static int sun4c_linesize;
static void sun4c_init (void)
{
    static int inited = 0;
    int propval;
    
    if (!inited) {
        inited = 1;
        propval = prom_getintdefault (prom_root_node, "vac_hwflush", -1);
        sun4c_hwflushes = (propval == -1) ? prom_getintdefault (prom_root_node, "vac-hwflush", 0) : propval;
        sun4c_linesize = prom_getintdefault (prom_root_node, "vac-linesize", 16);
    }
}

void sun4c_map (unsigned long virtual, unsigned long page)
{
    unsigned long virt = virtual;

    sun4c_init ();
    if (sun4c_hwflushes) {
    	__asm__ __volatile__ ("\n\tsta %%g0, [%0] 0x06\n\t" : : "r" (virt));
    } else {
        unsigned long end = virt + 4096;
        
        for (; virt < end; virt += sun4c_linesize)
            __asm__ __volatile__ ("\n\tsta %%g0, [%0] 0x0d\n\t" : : "r" (virt));
    }
    virt = virtual;
    __asm__ __volatile__ ("\n\tsta %1, [%0] 0x04\n\t" : : "r" (virt), "r" (page));
}

int sun4c_mapio (unsigned long phys, unsigned long virtual, int rdonly)
{
    unsigned long page = ((phys >> 12) & 0xffff) | 0x94000000;
    
    if (!rdonly) page |= 0x40000000;
    sun4c_map (virtual & ~4095, page);
    return 0;
}

void sun4c_unmapio (unsigned long virtual)
{
    sun4c_map (virtual & ~4095, 0);
}

void ensure_sun4c_mapping (char *beg, int len)
{
    /* Not written yet */
}

inline unsigned long sun4m_get_lev1 (void)
{
    unsigned long ret;
    
    __asm__ ("\n\t"
             "set 0x100, %0\n\t"
             "lda [%0] 4, %0\n\t"
             "sll %0, 4, %0\n\t"
             "lda [%0] 32, %0\n\t"
             "srl %0, 4, %0\n\t"
             "sll %0, 8, %0\n\t" : "=r" (ret));
    return ret;
}

inline unsigned long sun4m_get_direct (unsigned long l)
{
    unsigned long ret;
    
    __asm__ ("\n\t"
             "lda [%1] 32, %0\n\t" : "=r" (ret) : "r" (l));
    return ret;
}

#define SUN4M_MAPIT \
    if ((char *)(*romvec->pv_v2devops.v2_dumb_mmap)(start, 0, (unsigned)start, cur - start) == start) \
        start = (char *)-1; \
    else \
    	break;
    
void ensure_sun4m_mapping (char *beg, int len)
{
    char *end = beg + len, *cur = beg;
    unsigned long lev1 = sun4m_get_lev1 (), lev2, lev3;
    char *start = (char *)-1;

    while (cur < end) {
        if (!((unsigned)cur & 0xffffff) || cur == beg) {
            lev2 = sun4m_get_direct (lev1 + (((unsigned)cur >> 24) << 2));
            if (!(lev2 & 0x3)) {
                if (start == (char *)-1)
                    start = cur;
                cur = (char *)((unsigned)cur & 0xff000000) + 0x1000000;
                continue;
            } else {
                if (start != (char *)-1) {
                    SUN4M_MAPIT
                }
                lev2 &= ~0xf;
                lev2 <<= 4;
            }
        }
        if (!((unsigned)cur & 0x3ffff) || cur == beg) {
            lev3 = sun4m_get_direct (lev2 + ((((unsigned)cur >> 18) & 0x3f) << 2));
            if (!(lev3 & 0x3)) {
                if (start == (char *)-1)
                    start = cur;
                cur = (char *)((unsigned)cur & 0xfffc0000) + 0x40000;
                continue;
            } else {
                if (start != (char *)-1) {
                    SUN4M_MAPIT
                }
                lev3 &= ~0xf;
                lev3 <<= 4;
            }
        }
        if (sun4m_get_direct (lev3 + ((((unsigned)cur >> 12) & 0x3f) << 2)) & 0x3) {
            if (start != (char *)-1) {
                SUN4M_MAPIT
            }
        } else {
            if (start == (char *)-1) {
                start = cur;
            }
        }
        cur += 4096;
    }
    if (start != (char *)-1) {
        if ((char *)(*romvec->pv_v2devops.v2_dumb_mmap)(start, 0, (unsigned)start, end - start) != start) {
            printf ("Fatal error: Cannot create identical memory mapping\n");
            (*romvec->pv_halt)();
        }
    }
}

char *memory_find (int len)
{
    register struct linux_mlist_v0 *mlist;
    char *beg = 0, *start;
    int l = 0, num;

    if (architecture != sun4u) {
        prom_meminit ();
        mlist = prom_phys_avail;
        for (;;) {
            if (beg && mlist->start_adr != beg + l)
                beg = 0;
            start = mlist->start_adr;
            num = mlist->num_bytes;
            if (start <= (char *)0x300000) {
                num += start - (char *)0x300000;
                start = (char *)0x300000;
            }
            if (num > 0) {
                if (num + (beg ? l : 0) >= len) {
                    if (!beg) beg = start;
                    switch (architecture) {
                        case sun4c:
                            ensure_sun4c_mapping (beg, len); break;
                        case sun4m:
                            ensure_sun4m_mapping (beg, len); break;
                    }
                    return beg;
                }
                if (beg) l += num;
                else {
                    beg = start;
                    l = num;
                }
            }
            if (!mlist->theres_more) goto not_found;
            mlist = mlist->theres_more;
        }
    } else {
        int n, node, i;
        struct p1275_translations { unsigned long long va; unsigned long long len; unsigned long long tag; } *p;
        char *end_mapped = (char *)0, *q;
        
        node = prom_finddevice("/virtual-memory");
        n = prom_getproplen(node, "translations");
        if (n == 0 || n == -1) {
            printf("Could not get translations property\n");
            return (char *)0;
        }
        p = malloc(n);
        if (!p) return (char *)0;
        if (prom_getproperty(node, "translations", (char *)p, n) == -1) {
            free (p);
            printf("Could not get translations property\n");
            return (char *)0;
        }
        n /= sizeof(*p);
        for (i = 0; i < n; i++) {
            if (p[i].va >= 0x2000 && p[i].va <= 0x380000) {
            	if ((p[i].len + p[i].va) >> 32)
            	    end_mapped = (char *)0x80000000;
            	else
                    end_mapped = (char *)(long)(p[i].len + p[i].va);
                break;
            }
        }
        free (p);
        if (!end_mapped || (char *)0x400000 + len > end_mapped) {
            printf("The PROM did not map enough memory at boot time. Bug jj@ultra.linux.cz to write support\n");
            goto not_found;
        }
        q = end_mapped - len - 32768;
        if (q < (char *)0x400000)
            q = (char *)0x400000;
        return q;
    }
not_found:
    return (char *)0;
}
