/*
**	FastROM
**
**	Copyright © 1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	Freely distributable
*/

#include <exec/execbase.h>
#include <exec/memory.h>

#include <dos/dosextens.h>
#include <dos/rdargs.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>

#include <string.h>

/****************************************************************************/

#define ASM	__asm
#define FAR	__far
#define REG(x)	register __ ## x

/****************************************************************************/

	// Magic ROM constants

#define ROM_END			0x01000000
#define ROM_SIZE_OFFSET		0x14

/****************************************************************************/

	// The translation control register field definitions

#define TCRF_Enable		(1L << 15)
#define TCRF_PageSize8K		(1L << 14)

	// The page descriptor field definitions

#define PAGEDESCF_Resident	(3)
#define PAGEDESCF_WriteProtect	(1L << 2)
#define PAGEDESCF_CacheMode	(96)

	// And the values found in these places

#define PAGEDESCTYPE_Invalid		(0)
#define PAGEDESCTYPE_Indirect		(2)

#define CACHEMODE_WriteThrough		(0)
#define CACHEMODE_CopyBack		(1<<5)
#define CACHEMODE_Off_Serialized	(2<<5)
#define CACHEMODE_Off			(3<<5)

/****************************************************************************/

#define FRS_NAME		"FastROM"

	// To maintain accesses to the remapped pages

struct FastROMSemaphore
{
	struct SignalSemaphore	 frs_Semaphore;
	UBYTE			 frs_Name[sizeof(FRS_NAME)];

	ULONG			*frs_Pages;
	ULONG			 frs_PageCount;
	ULONG			 frs_PageSize;

	UBYTE			*frs_Zero;
	ULONG			 frs_ZeroSize;
};

	// For command line parsing

struct CmdArgs
{
	LONG			 Enable;
	LONG			 Disable;
};

/****************************************************************************/

ULONG ASM *	GetMMU_URP(REG(a6) struct ExecBase *SysBase);
ULONG ASM *	GetMMU_SRP(REG(a6) struct ExecBase *SysBase);
UWORD ASM	GetMMU_TCR(REG(a6) struct ExecBase *SysBase);
VOID ASM	ChangeTranslation(REG(a0) APTR PagePtr,REG(d0) ULONG Value,REG(a6) struct ExecBase *SysBase);

APTR		AllocAligned(LONG Size,LONG Granularity,struct ExecBase *SysBase);
APTR		GetPhysicalAddress(ULONG *Root,APTR Address,ULONG PageShift);
ULONG *		GetPageDescriptor(ULONG *Root,ULONG Address,ULONG PageShift);

struct FastROMSemaphore *	ObtainFRS(struct ExecBase *SysBase);

/****************************************************************************/

STRPTR		VersTag = "$VER: FastROM 1.8 (12.7.96)\r\n";

/****************************************************************************/

LONG FAR
Main(VOID)
{
	struct ExecBase *SysBase;
	struct DosLibrary *DOSBase;
	struct FastROMSemaphore *FRS;
	ULONG *Root,*Page;
	UWORD TCR;
	ULONG ROM_Start,ROM_Size;
	LONG i,PageCount;
	ULONG *Pages,PageData;
	ULONG PageShift,PageSize;
	struct CmdArgs CmdArgs;
	struct RDArgs *RDArgs;
	ULONG LowestAddress;
	struct MemHeader *MemHeader;
	UBYTE *Zero;
	ULONG ZeroSize,ZeroCount;

		// Get them libs...

	SysBase = *(struct ExecBase **)4L;

		// In the unlikely case this program gets run
		// from Workbench...

	if(!((struct Process *)FindTask(NULL))->pr_CLI)
	{
		struct MsgPort *Port;

		Port = &((struct Process *)FindTask(NULL))->pr_MsgPort;

		WaitPort(Port);

		Forbid();

		ReplyMsg(GetMsg(Port));

		return(0);
	}

	if(!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library",37)))
		return(RETURN_FAIL);

	if(!(SysBase->AttnFlags & AFF_68040))
	{
		Printf("FastROM: No 68040 CPU found.\n");

		CloseLibrary(DOSBase);

		return(RETURN_FAIL);
	}

		// Grab the access semaphore if it is there. If not,
		// create it first

	if(!(FRS = ObtainFRS(SysBase)))
	{
		Printf("FastROM: Could not create access semaphore.\n");

		CloseLibrary(DOSBase);

		return(RETURN_FAIL);
	}

		// We don't want to build complete MMU tables here, somebody
		// needs to have already done the job for us.

	TCR = GetMMU_TCR(SysBase);

	if(!(TCR & TCRF_Enable))
	{
		Printf("FastROM: Sorry, MMU translation is disabled.\n");

		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_FAIL);
	}

		// Check which page size we will have to deal with

	if(TCR & TCRF_PageSize8K)
		PageShift = 13;
	else
		PageShift = 12;

	PageSize = 1L << PageShift;

		// Calculate the size of the ROM and its start address
		// from magic constants at the end of the ROM image

	ROM_Size	= *(ULONG *)(ROM_END - ROM_SIZE_OFFSET);
	ROM_Start	= ROM_END - ROM_Size;

		// Get the MMU root pointer

	Root = GetMMU_URP(SysBase);

		// On the Amiga the user root pointer should match
		// the supervisor root pointer

	if(Root != GetMMU_SRP(SysBase))
	{
		Printf("FastROM: Sorry, supervisor and user root pointers do not match.\n");

		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_FAIL);
	}

		// Now check what we should do

	CmdArgs.Enable = CmdArgs.Disable = FALSE;

	if(!(RDArgs = ReadArgs("ENABLE=ON/S,DISABLE=OFF/S",(LONG *)&CmdArgs,NULL)))
	{
		PrintFault(IoErr(),"FastROM");

		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_ERROR);
	}

	FreeArgs(RDArgs);

		// We cannot do both at the same time.

	if(CmdArgs.Enable && CmdArgs.Disable)
	{
		Printf("FastROM: You can enable or disable fast ROM translation, but not both.\n");

		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_ERROR);
	}
	else
	{
			// If nothing is selected, enable

		if(!CmdArgs.Enable && !CmdArgs.Disable)
			CmdArgs.Enable = TRUE;
	}

		// Is the translation already enabled?

	if(CmdArgs.Enable && FRS->frs_Pages != NULL)
	{
		Printf("FastROM: Fast ROM translation is already enabled.\n");

		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_OK);
	}

		// If the translation is already disabled, go quietly

	if(CmdArgs.Disable && FRS->frs_Pages == NULL)
	{
		ReleaseSemaphore(FRS);
		CloseLibrary(DOSBase);

		return(RETURN_OK);
	}

		// Calculate the number of pages the ROM occupies

	PageCount = ROM_Size / PageSize;

		// Enable the translation?

	if(CmdArgs.Enable)
	{
			// Check each ROM page for validity and test if it
			// has already been remapped.

		for(i = 0 ; i < PageCount ; i++)
		{
			Page = GetPageDescriptor(Root,ROM_Start + PageSize * i,PageShift);

				// The page must be resident

			if((*Page & PAGEDESCF_Resident) == PAGEDESCTYPE_Invalid || (*Page & PAGEDESCF_Resident) == PAGEDESCTYPE_Indirect)
			{
				Printf("FastROM: The ROM mapping table is not pointing to valid memory.\n");

				ReleaseSemaphore(FRS);
				CloseLibrary(DOSBase);

				return(RETURN_FAIL);
			}

				// The page should point to ROM space

			if((*Page & ~(PageSize-1)) != ROM_Start + PageSize * i)
			{
				Printf("FastROM: The ROM has already been remapped (0x%08lx » 0x%08lx).\n",ROM_Start + PageSize * i,*Page & ~(PageSize-1));

				ReleaseSemaphore(FRS);
				CloseLibrary(DOSBase);

				return(RETURN_FAIL);
			}
		}

			// Find the lowest valid memory address. For a normal
			// 68040 system this will be 0x1000.

		Forbid();

		LowestAddress = (ULONG)~0;

		for(MemHeader = (struct MemHeader *)SysBase->MemList.lh_Head ; MemHeader->mh_Node.ln_Succ ; MemHeader = (struct MemHeader *)MemHeader->mh_Node.ln_Succ)
		{
			if((ULONG)MemHeader < LowestAddress)
				LowestAddress = (ULONG)MemHeader;
		}

		Permit();

			// Calculate how large the low memory area to
			// move will be

		ZeroSize	= (LowestAddress + PageSize - 1) & ~(PageSize - 1);
		ZeroCount	= ZeroSize / PageSize;

			// Check the low memory area validity

		for(i = 0 ; i < ZeroCount ; i++)
		{
			Page = GetPageDescriptor(Root,PageSize * i,PageShift);

				// The page must be resident

			if((*Page & PAGEDESCF_Resident) == PAGEDESCTYPE_Invalid || (*Page & PAGEDESCF_Resident) == PAGEDESCTYPE_Indirect)
			{
				Printf("FastROM: The low memory mapping table is not pointing to valid memory.\n");

				ReleaseSemaphore(FRS);
				CloseLibrary(DOSBase);

				return(RETURN_FAIL);
			}

				// Check if the area has already been remapped

			if((*Page & ~(PageSize-1)) != PageSize * i)
			{
				Printf("FastROM: The low memory area has already been remapped (0x%08lx » 0x%08lx).\n",PageSize * i,*Page & ~(PageSize-1));

				ReleaseSemaphore(FRS);
				CloseLibrary(DOSBase);

				return(RETURN_FAIL);
			}
		}

			// Allocate memory for the low memory area

		if(Zero = (UBYTE *)AllocAligned(ZeroSize,PageSize,SysBase))
		{
				// Allocate auxiliary page pointer table for bookkeeping

			if(Pages = (ULONG *)AllocVec(PageCount * sizeof(APTR),MEMF_ANY|MEMF_CLEAR))
			{
					// We'll try to allocate one contiguous chunk of memory
					// to remap the ROM into.

				if(Pages[0] = (ULONG)AllocAligned(ROM_Size,PageSize,SysBase))
				{
						// Add the pages

					for(i = 1 ; i < PageCount ; i++)
						Pages[i] = Pages[i - 1] + PageSize;

					FRS->frs_PageCount = 1;
				}
				else
				{
						// Ok, so that didn't work, memory is probably
						// too fragmented. We'll try to allocate the
						// pages piece by piece.

					for(i = 0 ; i < PageCount ; i++)
					{
						if(!(Pages[i] = (ULONG)AllocAligned(PageSize,PageSize,SysBase)))
						{
							LONG j;

								// Clean up and abort.

							for(j = 0 ; j < i ; j++)
								FreeMem((APTR)Pages[j],PageSize);

							FreeVec(Pages);
							Pages = NULL;

							break;
						}
					}

					FRS->frs_PageCount = PageCount;
				}
			}

				// If unsuccessful, clean up

			if(!Pages)
				FreeMem(Zero,ZeroSize);
		}
		else
			Pages = NULL;

			// Did we get the page memory?

		if(!Pages)
		{
			Printf("FastROM: Not enough memory for remapping.\n");

			ReleaseSemaphore(FRS);
			CloseLibrary(DOSBase);

			return(RETURN_FAIL);
		}

			// Store these for later use

		FRS->frs_Pages		= Pages;
		FRS->frs_PageSize	= PageSize;
		FRS->frs_Zero		= Zero;
		FRS->frs_ZeroSize	= ZeroSize;

			// Copy the ROM into the pages

		for(i = 0 ; i < PageCount ; i++)
			CopyMemQuick((APTR)(ROM_Start + PageSize * i),(APTR)Pages[i],PageSize);

			// Copy the low memory area

		CopyMemQuick((APTR)0,(APTR)Zero,ZeroSize);

		Printf("Moving 0x%08lx » 0x%08lx (ROM)\n"
                       "       0x%08lx » 0x%08lx (low memory area, size 0x%04lx)\n",
                       ROM_Start,Pages[0],0,Zero,ZeroSize);

			// Just in case...

		Disable();

			// Now we'll remap the ROM pages piece by piece

		for(i = 0 ; i < PageCount ; i++)
		{
				// Get the descriptor for the page

			Page = GetPageDescriptor(Root,Pages[i],PageShift);

				// Mark the page the ROM is to be remapped into
				// as write protected. Note that this will make
				// writing to ROM space deadly since the default
				// operating system exception handler does not
				// abort the writes.

			PageData = *Page | PAGEDESCF_WriteProtect;

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),PageData,SysBase);

				// Remap the ROM page.

			Page = GetPageDescriptor(Root,ROM_Start + PageSize * i,PageShift);

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),PageData,SysBase);
		}

			// Remap the low memory area

		for(i = 0 ; i < ZeroCount ; i++)
		{
				// Get the descriptor for the page

			Page = GetPageDescriptor(Root,(ULONG)&Zero[PageSize * i],PageShift);

			PageData = *Page;

			Page = GetPageDescriptor(Root,PageSize * i,PageShift);

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),PageData,SysBase);
		}

		Enable();
	}
	else
	{
			// Get the page table back

		Pages		= FRS->frs_Pages;

			// Pick up the low memory mapping data

		Zero		= FRS->frs_Zero;
		ZeroSize	= FRS->frs_ZeroSize;
		ZeroCount	= FRS->frs_ZeroSize / PageSize;

		Printf("Moving back 0x%08lx » 0x%08lx (ROM)\n"
                       "            0x%08lx » 0x%08lx (low memory area, size 0x%04lx)\n",
                       Pages[0],ROM_Start,Zero,0,ZeroSize);

			// Just in case...

		Disable();

			// Now we'll remap the ROM pages to point back to ROM space

		for(i = 0 ; i < PageCount ; i++)
		{
				// Get the page entry

			Page = GetPageDescriptor(Root,ROM_Start + PageSize * i,PageShift);

				// Disable the write protection and set the cache mode
				// to copyback.

			PageData = (*Page & ~(PAGEDESCF_WriteProtect|PAGEDESCF_CacheMode)) | CACHEMODE_CopyBack;

				// Clip off the page attributes and fill in the page address,
				// which matches the physical address

			PageData = ((ROM_Start + PageSize * i) & ~(PageSize-1)) | (PageData & (PageSize-1));

				// Make the change

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),PageData,SysBase);

				// Get the descriptor of the page the ROM was mapped into

			Page = GetPageDescriptor(Root,Pages[i],PageShift);

				// Turn off the write protection

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),*Page & ~PAGEDESCF_WriteProtect,SysBase);
		}

			// Map the low memory area back

		for(i = 0 ; i < ZeroCount ; i++)
		{
				// Get the page entry

			Page = GetPageDescriptor(Root,PageSize * i,PageShift);

				// Turn off caching and make accesses serialized;
				// this is going to be chip memory

			PageData = (*Page & ~PAGEDESCF_CacheMode) | CACHEMODE_Off_Serialized;

				// Clip off the page attributes and fill in the page address,
				// which matches the physical address

			PageData = ((PageSize * i) & ~(PageSize-1)) | (PageData & (PageSize-1));

				// Make the change

			ChangeTranslation(GetPhysicalAddress(Root,(APTR)Page,PageShift),PageData,SysBase);
		}

		Enable();

			// Now free the memory allocated for the pages

		if(FRS->frs_PageCount == 1)
			FreeMem((APTR)Pages[0],ROM_Size);
		else
		{
			for(i = 0 ; i < FRS->frs_PageCount ; i++)
				FreeMem((APTR)Pages[i],FRS->frs_PageSize);
		}

			// Free the page table storage

		FreeVec(Pages);

			// Free the low memory area storage

		FreeMem(Zero,ZeroSize);

			// And mark the translation as disabled

		FRS->frs_Pages = NULL;
	}

		// That's all, folks

	ReleaseSemaphore(FRS);
	CloseLibrary(DOSBase);

	return(RETURN_OK);
}

	// Allocate memory with a certain granularity

APTR
AllocAligned(LONG Size,LONG Granularity,struct ExecBase *SysBase)
{
	APTR Location;

	if(Location = AllocMem(Size + Granularity,MEMF_FAST|MEMF_PUBLIC))
	{
		APTR Aligned;

			// Round off the address

		Aligned = (APTR)(((ULONG)Location + Granularity - 1) & ~(Granularity - 1));

		Forbid();

			// Free the allocated memory and reallocate
			// it immediately, that's the trick ;)

		FreeMem(Location,Size + Granularity);

		Location = AllocAbs(Size,Aligned);

		Permit();
	}

	return(Location);
}

	// Return the physical address corresponding to a logical address

APTR
GetPhysicalAddress(ULONG *Root,APTR Address,ULONG PageShift)
{
	ULONG *Page;
	ULONG PageMask;

		// Get the mask ready for later

	PageMask = (1L << PageShift)-1;

		// Find the page the address is mapped into

	Page = GetPageDescriptor(Root,(ULONG)Address,PageShift);

		// Knit the page offset of the original address
		// and its physical counterpart together

	return((APTR)((*Page & ~PageMask) | ((ULONG)Address & PageMask)));
}

	// Get the page descriptor associated with a given address

ULONG *
GetPageDescriptor(ULONG *Root,ULONG Address,ULONG PageShift)
{
	ULONG *Pointer,*Page;
	ULONG PointerMask,PageMask;

		// Get the masks ready for later use, depending
		// on the current page size

	if(PageShift == 12)
	{
		PointerMask	= ~((1L << 8)-1);
		PageMask	= (1L << 6)-1;
	}
	else
	{
		PointerMask	= ~((1L << 7)-1);
		PageMask	= (1L << 5)-1;
	}

	Pointer	= (ULONG *)(Root[(Address >> 25) & 0x7F] & ~((1L << 9)-1));
	Page	= (ULONG *)(Pointer[(Address >> 18) & 0x7F] & PointerMask);
	Page	= &Page[(Address >> PageShift) & PageMask];

	return(Page);
}

	// Gets access to the global management semaphore

struct FastROMSemaphore *
ObtainFRS(struct ExecBase *SysBase)
{
	struct FastROMSemaphore *FRS;

		// Block access to the semaphore list

	Forbid();

		// Can we find the semaphore?

	if(!(FRS = (struct FastROMSemaphore *)FindSemaphore(FRS_NAME)))
	{
			// Ok, we couldn't find it, so we'll create it here

		if(!(FRS = AllocMem(sizeof(*FRS),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR)))
		{
				// Clean up and return failure

			Permit();

			return(NULL);
		}

			// Set up the name

		strcpy(FRS->frs_Name,FRS_NAME);

			// And fill in the header data

		FRS->frs_Semaphore.ss_Link.ln_Name	= FRS->frs_Name;
		FRS->frs_Semaphore.ss_Link.ln_Pri	= -127;

			// Make it public

		AddSemaphore(FRS);
	}

		// Lock access to the semaphore

	ObtainSemaphore(FRS);

	Permit();

		// And return success

	return(FRS);
}
