/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

/*
 * GP2X: Memory tweaking stuff.
 *
 */

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

#include "gp2x-mem.h"

char uname[256];

//void InitRam (void)
//{
//	if(!gp2x_dev)
//	{
//		gp2x_dev = open("/dev/mem", O_RDWR);
//		gp2x_ram = (unsigned short *)mmap(0, 0x10000, 3, 1, gp2x_dev, 0x03000000);
//		gp2x_memregs = (unsigned short *)mmap(0, 0x10000, 3, 1, gp2x_dev, 0xc0000000);
//	}
//}

//void CloseRam (void)
//{
//	if(gp2x_dev) close(gp2x_dev);
//}

/*
****** [BEGIN] Squidge's MMU hack code ******
*/

int myuname(char *buffer)
{
	asm volatile ("swi #0x90007a");
}

void DecodeCoarse (unsigned int indx, unsigned int sa)
{
	unsigned int cpt[256];
	unsigned int temp;
	unsigned int i = 0;
    unsigned int wb = 0;

    sa &= 0xfffffc00;
    indx *= 1048576;

    //printf ("  > %08X\n", sa);
    //printf ("%d\n",
    lseek (gp2x_dev, sa, SEEK_SET);
    memset (cpt, 0, 256*4);
    temp = read (gp2x_dev, cpt, 256*4);
    //printf ("%d\n", temp);
    if (temp != 256*4)
    {
        printf ("  # Bad read\n");
        return;
    }

    //printf ("%08X %08X %08X %08X\n", cpt[0], cpt[4], cpt[8], cpt[12]);

    for (i = 0; i < 256; i ++)
    {
        if (cpt[i])
        {
            switch (cpt[i] & 3)
            {
                case 0:
                    //printf ("  -- [%08X] Invalid --\n", cpt[i]);
                    break;
                case 1:
			/*
			  printf ("  VA: %08X PA: %08X - %08X A: %d %d %d %d D: %d C: %d B: %d\n", indx,
			  cpt[i] & 0xFFFF0000, (cpt[i] & 0xFFFF0000) | 0xFFFF,
			  (cpt[i] >> 10) & 3, (cpt[i] >> 8) & 3, (cpt[i] >> 6) & 3,
			  (cpt[i] >> 4) & 3, dom, (cpt[i] >> 3) & 1, (cpt[i] >> 2) & 1);
			*/
                    break;
                case 2:
			//a=((cpt[i] & 0xff000000)>>24);
			/*printf ("  VA: %08X PA: %08X - %08X A: %d %d %d %d D: %d C: %d B: %d\n", indx,
			  cpt[i] & 0xfffff000, (cpt[i] & 0xfffff000) | 0xFFF,
			  (cpt[i] >> 10) & 3, (cpt[i] >> 8) & 3, (cpt[i] >> 6) & 3,
			  (cpt[i] >> 4) & 3, dom, (cpt[i] >> 3) & 1, (cpt[i] >> 2) & 1);
			*/
			// This is where we look for any virtual addresses that map to physical address 0x03000000 and
			// alter the cachable and bufferable bits...
			if (((cpt[i] & 0xff000000) == 0x02000000) )
			{
				//printf("SOUND c and b bits not set, overwriting\n");
				if((cpt[i] & 12)==0) {
					cpt[i] |= 0xFFC;
					wb++;
				}
			}
			//if ((a>=0x31 && a<=0x36) && ((cpt[i] & 12)==0))
			if (((cpt[i] & 0xff000000) == 0x03000000) )
			{
				//printf("SDL   c and b bits not set, overwriting\n");
				if((cpt[i] & 12)==0) {
					cpt[i] |= 0xFFC;
					wb++;
				}
			}
			break;
                case 3:
                    //printf ("  -- [%08X/%d] Unsupported --\n", cpt[i],cpt[i] & 3);
                    break;
                default:
                    //printf ("  -- [%08X/%d] Unknown --\n", cpt[i], cpt[i] & 3);
                    break;
            }
        }
        indx += 4096;
    }
    //printf ("%08X %08X %08X %08X\n", cpt[0], cpt[4], cpt[8], cpt[12]);
    if (wb)
    {
        //printf("Hacking cpt\n");
        lseek (gp2x_dev, sa, SEEK_SET);
        temp = write (gp2x_dev, cpt, 256*4);
        printf("%d bytes written, %s\n", temp, temp == 1024 ? "yay!" : "oh fooble :(!");
    }
}

void dumppgtable (unsigned int ttb)
{
    unsigned int pgtable[4096];
    char *desctypes[] = {"Invalid", "Coarse", "Section", "Fine"};

    memset (pgtable, 0, 4096*4);
    lseek (gp2x_dev, ttb, SEEK_SET);
    read (gp2x_dev, pgtable, 4096*4);

    int i;
    for (i = 0; i < 4096; i ++)
    {
        int temp;

        if (pgtable[i])
        {
		//printf ("Indx: %d VA: %08X Type: %d [%s] \n", i, i * 1048576, pgtable[i] & 3, desctypes[pgtable[i]&3]);
            switch (pgtable[i]&3)
            {
                case 0:
                    //printf (" -- Invalid --\n");
                    break;
                case 1:
                    DecodeCoarse(i, pgtable[i]);
                    break;
                case 2:
                    temp = pgtable[i] & 0xFFF00000;
                    //printf ("  PA: %08X - %08X A: %d D: %d C: %d B: %d\n", temp, temp | 0xFFFFF,
                    //        (pgtable[i] >> 10) & 3, (pgtable[i] >> 5) & 15, (pgtable[i] >> 3) & 1,
                    //        (pgtable[i] >> 2) & 1);

                    break;
                case 3:
                    printf ("  -- Unsupported! --\n");
                    break;
            }
        }
    }
}

int hackpgtable (void)
{
    unsigned int oldc1, oldc2, oldc3, oldc4;
    unsigned int newc1 = 0xee120f10, newc2 = 0xe12fff1e;
    unsigned int ttb, ttx;
    int a=0;int try=0;
    // We need to execute a "MRC p15, 0, r0, c2, c0, 0", to get the pointer to the translation table base, but we can't
    // do this in user mode, so we have to patch the kernel to get it to run it for us in supervisor mode. We dothis
    // at the moment by overwriting the sys_newuname function and then calling it.

    lseek (gp2x_dev[2], 0x6ec00, SEEK_SET); // fixme: We should ask the kernel for this address rather than assuming it...
    read (gp2x_dev[2], &oldc1, 4);
    read (gp2x_dev[2], &oldc2, 4);
    read (gp2x_dev[2], &oldc3, 4);
    read (gp2x_dev[2], &oldc4, 4);

    //printf ("0:%08X %08X - %08X %08X\n", oldc1, oldc2, newc1, newc2);
    //printf ("point1 %d\n",a);

    do {
	    lseek (gp2x_dev[2], 0x6ec00, SEEK_SET);
	    a+=write (gp2x_dev[2], &newc1, 4);
	    a+=write (gp2x_dev[2], &newc2, 4);
	    SDL_Delay(200);
	    try++;
	    ttb = myuname(uname);
	    //printf ("2:%08X try %d\n", ttb,try);
    } while (ttb==0 && try<4);

    lseek (gp2x_dev[2], 0x6ec00, SEEK_SET);
    a+=write (gp2x_dev[2], &oldc1, 4);
    a+=write (gp2x_dev[2], &oldc2, 4);

    //printf ("2:%08X %d\n", ttb,a);
    if (ttb!=0) {


	    //printf ("Restored contents\n");

	    // We now have the translation table base ! Walk the table looking for our allocation and hack it :)
	    dumppgtable(ttb);

	    // Now drain the write buffer and flush the tlb caches. Something else we can't do in user mode...
	    unsigned int tlbc1 = 0xe3a00000; // mov    r0, #0
	    unsigned int tlbc2 = 0xee070f9a; // mcr    15, 0, r0, cr7, cr10, 4
	    unsigned int tlbc3 = 0xee080f17; // mcr    15, 0, r0, cr8, cr7, 0
	    unsigned int tlbc4 = 0xe1a0f00e; // mov    pc, lr

	    lseek (gp2x_dev[2], 0x6ec00, SEEK_SET);
	    write (gp2x_dev[2], &tlbc1, 4);
	    write (gp2x_dev[2], &tlbc2, 4);
	    write (gp2x_dev[2], &tlbc3, 4);
	    write (gp2x_dev[2], &tlbc4, 4);

	    SDL_Delay(200);

	    ttx = myuname(uname);

	    printf ("Return from uname: %08X\n", ttx);

	    lseek (gp2x_dev[2], 0x6ec00, SEEK_SET);
	    write (gp2x_dev[2], &oldc1, 4);
	    write (gp2x_dev[2], &oldc2, 4);
	    write (gp2x_dev[2], &oldc3, 4);
	    write (gp2x_dev[2], &oldc4, 4);
	    lseek (gp2x_dev[2], 0x0, SEEK_SET);
	    return 0;
    }

    lseek (gp2x_dev[2], 0x0, SEEK_SET);
    return 1;
    //printf ("Restored contents\n");

    //printf ("Pagetable after modification!\n");
    //printf ("-------------------------------\n");
    //dumppgtable(ttb);
}

/*
****** [END] Squidge's MMU hack code ******
*/

void SetClock (unsigned c)
{
	unsigned v;
	unsigned mdiv,pdiv=3,scale=0;

	// Set ARM920t clock
	c *= 1000000;
	mdiv = (c*pdiv) / SYS_CLK_FREQ;
    mdiv = ((mdiv-8)<<8) & 0xff00;
	pdiv = ((pdiv-2)<<2) & 0xfc;
    scale &= 3;
    v = mdiv | pdiv | scale;
    gp2x_memregs[0x910>>1] = v;
}

void patchMMU (void)
{
	//volatile unsigned int *secbuf = (unsigned int *)malloc (204800);

	printf ("Reconfiguring cached memory regions...\n");

	//hackpgtable();
	//printf ("Sucess...\n");

	system("/sbin/rmmod mmuhack");
	system("/sbin/insmod -f mmuhack.o");

	int mmufd = open("/dev/mmuhack", O_RDWR);

	if(mmufd < 0)
	{
		printf ("Upper memory uncached (attempt failed, access to upper memory will be slower)...\n");
	}
	else
	{
		printf ("Upper memory cached...\n");
		close(mmufd);
	}
}

void unpatchMMU (void)
{
	printf ("Restoreing cached memory regions...\n");
	system("/sbin/rmmod mmuhack");
	return 1;
}