/*************************************************************************/ /*!
@File
@Title          Services Firmware image utilities used at init time
@Copyright      Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@Description    Services Firmware image utilities used at init time
@License        Dual MIT/GPLv2

The contents of this file are subject to the MIT license as set out below.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.

If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.

This License is also included in this distribution in the file called
"MIT-COPYING".

EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ /**************************************************************************/

/* The routines implemented here are built on top of an abstraction layer to
 * hide DDK/OS-specific details in case they are used outside of the DDK
 * (e.g. when trusted device is enabled).
 * Any new dependency should be added to rgxlayer.h.
 * Any new code should be built on top of the existing abstraction layer,
 * which should be extended when necessary. */
#include "rgxfwimageutils.h"


/************************************************************************
* FW Segments configuration
************************************************************************/
typedef struct _RGX_FW_SEGMENT_
{
	IMG_UINT32 ui32SegId;        /*!< Segment Id */
	IMG_UINT32 ui32SegStartAddr; /*!< Segment Start Addr */
	IMG_UINT32 ui32SegAllocSize; /*!< Amount of memory to allocate for that segment */
	IMG_UINT32 ui32FWMemOffset;  /*!< Offset of this segment in the collated FW mem allocation */
	const IMG_CHAR *pszSegName;
} RGX_FW_SEGMENT;

typedef struct _RGX_FW_SEGMENT_LIST_
{
	RGX_FW_SEGMENT *psRGXFWCodeSeg;
	RGX_FW_SEGMENT *psRGXFWDataSeg;
	IMG_UINT32 ui32CodeSegCount;
	IMG_UINT32 ui32DataSegCount;
} RGX_FW_SEGMENT_LIST;


static RGX_FW_SEGMENT asRGXMetaFWCodeSegments[] = {
/* Seg ID                 Seg Start Addr           Alloc size   FWMem offset  Name */
{RGXFW_SEGMMU_TEXT_ID,    RGXFW_BOOTLDR_META_ADDR, 0x31000,      0,           "Bootldr and Code"}, /* Has to be the first one to get the proper DevV addr */
};
static RGX_FW_SEGMENT asRGXMetaFWDataSegments[] = {
/* Seg ID                 Seg Start Addr           Alloc size   FWMem offset  Name */
{RGXFW_SEGMMU_DATA_ID,    0x38880000,              0x17000,      0,           "Local Shared and Data"},
};
#define RGXFW_META_NUM_CODE_SEGMENTS  (sizeof(asRGXMetaFWCodeSegments)/sizeof(asRGXMetaFWCodeSegments[0]))
#define RGXFW_META_NUM_DATA_SEGMENTS  (sizeof(asRGXMetaFWDataSegments)/sizeof(asRGXMetaFWDataSegments[0]))

static RGX_FW_SEGMENT asRGXMipsFWCodeSegments[] = {
/* Seg ID   Seg Start Addr                         Alloc size                         FWMem offset                         Name */
{    0,     RGXMIPSFW_BOOT_NMI_CODE_VIRTUAL_BASE,  RGXMIPSFW_BOOT_NMI_CODE_SIZE,      RGXMIPSFW_BOOT_NMI_CODE_OFFSET,      "Bootldr and NMI code"},
{    1,     RGXMIPSFW_EXCEPTIONS_VIRTUAL_BASE,     RGXMIPSFW_EXCEPTIONSVECTORS_SIZE,  RGXMIPSFW_EXCEPTIONSVECTORS_OFFSET,  "Exception vectors"},
{    2,     RGXMIPSFW_CODE_VIRTUAL_BASE,           RGXMIPSFW_CODE_SIZE,               RGXMIPSFW_CODE_OFFSET,               "Text"},
};
static RGX_FW_SEGMENT asRGXMipsFWDataSegments[] = {
/* Seg ID   Seg Start Addr                         Alloc size                         FWMem offset                         Name */
{    3,     RGXMIPSFW_BOOT_NMI_DATA_VIRTUAL_BASE,  RGXMIPSFW_BOOT_NMI_DATA_SIZE,      RGXMIPSFW_BOOT_NMI_DATA_OFFSET,      "Bootldr and NMI data"},
{    4,     RGXMIPSFW_DATA_VIRTUAL_BASE,           RGXMIPSFW_DATA_SIZE,               RGXMIPSFW_DATA_OFFSET,               "Local Data"},
{    5,     RGXMIPSFW_STACK_VIRTUAL_BASE,          RGXMIPSFW_STACK_SIZE,              RGXMIPSFW_DATA_SIZE,                 "Stack"},
};

#define RGXFW_MIPS_NUM_CODE_SEGMENTS  (sizeof(asRGXMipsFWCodeSegments)/sizeof(asRGXMipsFWCodeSegments[0]))
#define RGXFW_MIPS_NUM_DATA_SEGMENTS  (sizeof(asRGXMipsFWDataSegments)/sizeof(asRGXMipsFWDataSegments[0]))

/*!
*******************************************************************************

 @Function      FindMMUSegment

 @Description   Given a 32 bit FW address attempt to find the corresponding
                pointer to FW allocation

 @Input         ui32OffsetIn      : 32 bit FW address
 @Input         pvHostFWCodeAddr  : Pointer to FW code
 @Input         pvHostFWDataAddr  : Pointer to FW data
 @Input         uiHostAddrOut     : CPU pointer equivalent to ui32OffsetIn

 @Return        PVRSRV_ERROR

******************************************************************************/
static PVRSRV_ERROR FindMMUSegment(IMG_UINT32 ui32OffsetIn,
                                   void *pvHostFWCodeAddr,
                                   void *pvHostFWDataAddr,
                                   void **uiHostAddrOut,
                                   RGX_FW_SEGMENT_LIST *psRGXFWSegList)
{
	RGX_FW_SEGMENT *psSegArr;
	IMG_UINT32 i;

	psSegArr = psRGXFWSegList->psRGXFWCodeSeg;
	for (i = 0; i < psRGXFWSegList->ui32CodeSegCount; i++)
	{
		if ((ui32OffsetIn >= psSegArr[i].ui32SegStartAddr) &&
		    (ui32OffsetIn < (psSegArr[i].ui32SegStartAddr + psSegArr[i].ui32SegAllocSize)))
		{
			*uiHostAddrOut = pvHostFWCodeAddr;
			goto found;
		}
	}

	psSegArr = psRGXFWSegList->psRGXFWDataSeg;
	for (i = 0; i < psRGXFWSegList->ui32DataSegCount; i++)
	{
		if ((ui32OffsetIn >= psSegArr[i].ui32SegStartAddr) &&
		   (ui32OffsetIn < (psSegArr[i].ui32SegStartAddr + psSegArr[i].ui32SegAllocSize)))
		{
			*uiHostAddrOut = pvHostFWDataAddr;
			goto found;
		}
	}

	return PVRSRV_ERROR_INIT_FAILURE;

found:
	/* Direct Mem write to mapped memory */
	ui32OffsetIn -= psSegArr[i].ui32SegStartAddr;
	ui32OffsetIn += psSegArr[i].ui32FWMemOffset;

	/* Add offset to pointer to FW allocation only if
	 * that allocation is available
	 */
	if (*uiHostAddrOut)
	{
		*(IMG_UINT8 **)uiHostAddrOut += ui32OffsetIn;
	}

	return PVRSRV_OK;
}


/*!
*******************************************************************************

 @Function      RGXFWConfigureSegID

 @Description   Configures a single segment of the Segment MMU
                (base, limit and out_addr)

 @Input         hPrivate        : Implementation specific data
 @Input         ui64SegOutAddr  : Segment output base address (40 bit devVaddr)
 @Input         ui32SegBase     : Segment input base address (32 bit FW address)
 @Input         ui32SegLimit    : Segment size
 @Input         ui32SegID       : Segment ID
 @Input         pszName         : Segment name
 @Input         ppui32BootConf  : Pointer to bootloader data

 @Return        void

******************************************************************************/
static void RGXFWConfigureSegID(const void *hPrivate,
                                IMG_UINT64 ui64SegOutAddr,
                                IMG_UINT32 ui32SegBase,
                                IMG_UINT32 ui32SegLimit,
                                IMG_UINT32 ui32SegID,
                                const IMG_CHAR *pszName,
                                IMG_UINT32 **ppui32BootConf)
{
	IMG_UINT32 *pui32BootConf = *ppui32BootConf;
	IMG_UINT32 ui32SegOutAddr0  = ui64SegOutAddr & 0x00000000FFFFFFFFUL;
	IMG_UINT32 ui32SegOutAddr1  = (ui64SegOutAddr >> 32) & 0x00000000FFFFFFFFUL;

	/* META segments have a minimum size */
	IMG_UINT32 ui32LimitOff = (ui32SegLimit < RGXFW_SEGMMU_ALIGN) ?
	                          RGXFW_SEGMMU_ALIGN : ui32SegLimit;
	/* the limit is an offset, therefore off = size - 1 */
	ui32LimitOff -= 1;

	RGXCommentLog(hPrivate,
	              "* FW %s - seg%d: meta_addr = 0x%08x, devv_addr = 0x%llx, limit = 0x%x",
	              pszName, ui32SegID,
	              ui32SegBase, (unsigned long long)ui64SegOutAddr,
	              ui32LimitOff);

	ui32SegBase |= RGXFW_SEGMMU_ALLTHRS_WRITEABLE;

	*pui32BootConf++ = META_CR_MMCU_SEGMENTn_BASE(ui32SegID);
	*pui32BootConf++ = ui32SegBase;

	*pui32BootConf++ = META_CR_MMCU_SEGMENTn_LIMIT(ui32SegID);
	*pui32BootConf++ = ui32LimitOff;

	*pui32BootConf++ = META_CR_MMCU_SEGMENTn_OUTA0(ui32SegID);
	*pui32BootConf++ = ui32SegOutAddr0;

	*pui32BootConf++ = META_CR_MMCU_SEGMENTn_OUTA1(ui32SegID);
	*pui32BootConf++ = ui32SegOutAddr1;

	*ppui32BootConf = pui32BootConf;
}

/*!
*******************************************************************************

 @Function      RGXFWConfigureSegMMU

 @Description   Configures META's Segment MMU

 @Input         hPrivate             : Implementation specific data
 @Input         psFWCodeDevVAddrBase : FW code base device virtual address
 @Input         psFWDataDevVAddrBase : FW data base device virtual address
 @Input         ppui32BootConf       : Pointer to bootloader data

 @Return        void

******************************************************************************/
static void RGXFWConfigureSegMMU(const void       *hPrivate,
                                 IMG_DEV_VIRTADDR *psFWCodeDevVAddrBase,
                                 IMG_DEV_VIRTADDR *psFWDataDevVAddrBase,
                                 IMG_UINT32       **ppui32BootConf)
{
	IMG_UINT64 ui64SegOutAddrTop;
	IMG_UINT32 i;

	PVR_UNREFERENCED_PARAMETER(psFWCodeDevVAddrBase);

	/* Configure Segment MMU */
	RGXCommentLog(hPrivate, "********** FW configure Segment MMU **********");

	if (RGX_DEVICE_HAS_ERN(hPrivate, 45914))
	{
		ui64SegOutAddrTop = RGXFW_SEGMMU_OUTADDR_TOP_ERN_45914(META_MMU_CONTEXT_MAPPING, RGXFW_SEGMMU_META_DM_ID);
	}
	else
	{
		ui64SegOutAddrTop = RGXFW_SEGMMU_OUTADDR_TOP_PRE_S7(META_MMU_CONTEXT_MAPPING, RGXFW_SEGMMU_META_DM_ID);
	}

	for (i = 0; i < RGXFW_META_NUM_DATA_SEGMENTS ; i++)
	{
		IMG_UINT64 ui64SegOutAddr;

		ui64SegOutAddr = (psFWDataDevVAddrBase->uiAddr | ui64SegOutAddrTop) +
		                  asRGXMetaFWDataSegments[i].ui32FWMemOffset;

		RGXFWConfigureSegID(hPrivate,
		                    ui64SegOutAddr,
		                    asRGXMetaFWDataSegments[i].ui32SegStartAddr,
		                    asRGXMetaFWDataSegments[i].ui32SegAllocSize,
		                    asRGXMetaFWDataSegments[i].ui32SegId,
		                    asRGXMetaFWDataSegments[i].pszSegName,
		                    ppui32BootConf); /*write the sequence to the bootldr */
	}
}

/*!
*******************************************************************************

 @Function      RGXFWConfigureMetaCaches

 @Description   Configure and enable the Meta instruction and data caches

 @Input         hPrivate          : Implementation specific data
 @Input         ui32NumThreads    : Number of FW threads in use
 @Input         ui32MainThreadID  : ID of the FW thread in use
                                    (only meaningful if ui32NumThreads == 1)
 @Input         ppui32BootConf    : Pointer to bootloader data

 @Return        void

******************************************************************************/
static void RGXFWConfigureMetaCaches(const void *hPrivate,
                                     IMG_UINT32 ui32NumThreads,
                                     IMG_UINT32 ui32MainThreadID,
                                     IMG_UINT32 **ppui32BootConf)
{
	IMG_UINT32 *pui32BootConf = *ppui32BootConf;
	IMG_UINT32 ui32DCacheT0, ui32ICacheT0;
	IMG_UINT32 ui32DCacheT1, ui32ICacheT1;
	IMG_UINT32 ui32DCacheT2, ui32ICacheT2;
	IMG_UINT32 ui32DCacheT3, ui32ICacheT3;

#define META_CR_MMCU_LOCAL_EBCTRL                        (0x04830600)
#define META_CR_MMCU_LOCAL_EBCTRL_ICWIN                  (0x3 << 14)
#define META_CR_MMCU_LOCAL_EBCTRL_DCWIN                  (0x3 << 6)
#define META_CR_SYSC_DCPART(n)                           (0x04830200 + (n)*0x8)
#define META_CR_SYSC_DCPARTX_CACHED_WRITE_ENABLE         (0x1 << 31)
#define META_CR_SYSC_ICPART(n)                           (0x04830220 + (n)*0x8)
#define META_CR_SYSC_XCPARTX_LOCAL_ADDR_OFFSET_TOP_HALF  (0x8 << 16)
#define META_CR_SYSC_XCPARTX_LOCAL_ADDR_FULL_CACHE       (0xF)
#define META_CR_SYSC_XCPARTX_LOCAL_ADDR_HALF_CACHE       (0x7)
#define META_CR_MMCU_DCACHE_CTRL                         (0x04830018)
#define META_CR_MMCU_ICACHE_CTRL                         (0x04830020)
#define META_CR_MMCU_XCACHE_CTRL_CACHE_HITS_EN           (0x1)

	RGXCommentLog(hPrivate, "********** Meta caches configuration *********");

	/* Initialise I/Dcache settings */
	ui32DCacheT0 = ui32DCacheT1 = META_CR_SYSC_DCPARTX_CACHED_WRITE_ENABLE;
	ui32DCacheT2 = ui32DCacheT3 = META_CR_SYSC_DCPARTX_CACHED_WRITE_ENABLE;
	ui32ICacheT0 = ui32ICacheT1 = ui32ICacheT2 = ui32ICacheT3 = 0;

	if (ui32NumThreads == 1)
	{
		if (ui32MainThreadID == 0)
		{
			ui32DCacheT0 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_FULL_CACHE;
			ui32ICacheT0 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_FULL_CACHE;
		}
		else
		{
			ui32DCacheT1 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_FULL_CACHE;
			ui32ICacheT1 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_FULL_CACHE;
		}
	}
	else
	{
		ui32DCacheT0 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_HALF_CACHE;
		ui32ICacheT0 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_HALF_CACHE;

		ui32DCacheT1 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_HALF_CACHE |
		                META_CR_SYSC_XCPARTX_LOCAL_ADDR_OFFSET_TOP_HALF;
		ui32ICacheT1 |= META_CR_SYSC_XCPARTX_LOCAL_ADDR_HALF_CACHE |
		                META_CR_SYSC_XCPARTX_LOCAL_ADDR_OFFSET_TOP_HALF;
	}

	/* Local region MMU enhanced bypass: WIN-3 mode for code and data caches */
	*pui32BootConf++ = META_CR_MMCU_LOCAL_EBCTRL;
	*pui32BootConf++ = META_CR_MMCU_LOCAL_EBCTRL_ICWIN |
	                   META_CR_MMCU_LOCAL_EBCTRL_DCWIN;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_MMCU_LOCAL_EBCTRL,
	              META_CR_MMCU_LOCAL_EBCTRL_ICWIN | META_CR_MMCU_LOCAL_EBCTRL_DCWIN);

	/* Data cache partitioning thread 0 to 3 */
	*pui32BootConf++ = META_CR_SYSC_DCPART(0);
	*pui32BootConf++ = ui32DCacheT0;
	*pui32BootConf++ = META_CR_SYSC_DCPART(1);
	*pui32BootConf++ = ui32DCacheT1;
	*pui32BootConf++ = META_CR_SYSC_DCPART(2);
	*pui32BootConf++ = ui32DCacheT2;
	*pui32BootConf++ = META_CR_SYSC_DCPART(3);
	*pui32BootConf++ = ui32DCacheT3;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_DCPART(0), ui32DCacheT0);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_DCPART(1), ui32DCacheT1);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_DCPART(2), ui32DCacheT2);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_DCPART(3), ui32DCacheT3);

	/* Enable data cache hits */
	*pui32BootConf++ = META_CR_MMCU_DCACHE_CTRL;
	*pui32BootConf++ = META_CR_MMCU_XCACHE_CTRL_CACHE_HITS_EN;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_MMCU_DCACHE_CTRL,
	              META_CR_MMCU_XCACHE_CTRL_CACHE_HITS_EN);

	/* Instruction cache partitioning thread 0 to 3 */
	*pui32BootConf++ = META_CR_SYSC_ICPART(0);
	*pui32BootConf++ = ui32ICacheT0;
	*pui32BootConf++ = META_CR_SYSC_ICPART(1);
	*pui32BootConf++ = ui32ICacheT1;
	*pui32BootConf++ = META_CR_SYSC_ICPART(2);
	*pui32BootConf++ = ui32ICacheT2;
	*pui32BootConf++ = META_CR_SYSC_ICPART(3);
	*pui32BootConf++ = ui32ICacheT3;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_ICPART(0), ui32ICacheT0);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_ICPART(1), ui32ICacheT1);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_ICPART(2), ui32ICacheT2);
	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_SYSC_ICPART(3), ui32ICacheT3);

	/* Enable instruction cache hits */
	*pui32BootConf++ = META_CR_MMCU_ICACHE_CTRL;
	*pui32BootConf++ = META_CR_MMCU_XCACHE_CTRL_CACHE_HITS_EN;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
	              META_CR_MMCU_ICACHE_CTRL,
	              META_CR_MMCU_XCACHE_CTRL_CACHE_HITS_EN);

	*pui32BootConf++ = 0x040000C0;
	*pui32BootConf++ = 0;

	RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x", 0x040000C0, 0);

	*ppui32BootConf = pui32BootConf;
}

/*!
*******************************************************************************

 @Function      GetCoreMemCodeSizeInUse

 @Description   Process the output of the Meta toolchain in the .LDR format
		for calculating core mem code size

 @Input         ui32MaxCorememSize   : Max core mem size on core
 @Input         pbLDR                : Pointer to FW blob

 @Return        Size of coremem in use

******************************************************************************/
static IMG_UINT32 GetCoreMemCodeSizeInUse(IMG_UINT32 ui32MaxCorememSize, const IMG_BYTE* pbLDR)
{
	RGX_META_LDR_BLOCK_HDR *psHeader = (RGX_META_LDR_BLOCK_HDR *) pbLDR;
	RGX_META_LDR_L1_DATA_BLK *psL1Data =
	    (RGX_META_LDR_L1_DATA_BLK*) ((IMG_UINT8 *) pbLDR + psHeader->ui32SLData);

	IMG_UINT32 ui32CorememSizeInUse = 0;

	while (psL1Data != NULL)
	{
		if (!RGX_META_LDR_BLK_IS_COMMENT(psL1Data->ui16Cmd) &&
				(psL1Data->ui16Cmd & RGX_META_LDR_CMD_MASK) == RGX_META_LDR_CMD_LOADMEM)
		{
			RGX_META_LDR_L2_DATA_BLK *psL2Block =
					(RGX_META_LDR_L2_DATA_BLK*) (((IMG_UINT8 *) pbLDR) + psL1Data->aui32CmdData[1]);
			IMG_UINT32 ui32Offset = psL1Data->aui32CmdData[0];
			IMG_UINT32 ui32DataSize = psL2Block->ui16Length - 6 /* L2 Tag length and checksum */;

			if (RGX_META_IS_COREMEM_CODE(ui32Offset, ui32MaxCorememSize))
			{
				ui32CorememSizeInUse += ui32DataSize;
			}
		}

		if (psL1Data->ui32Next == 0xFFFFFFFF)
		{
			psL1Data = NULL;
		}
		else
		{
			psL1Data = (RGX_META_LDR_L1_DATA_BLK*) (((IMG_UINT8 *) pbLDR) + psL1Data->ui32Next);
		}
	}

	return ui32CorememSizeInUse;
}

/*!
*******************************************************************************

 @Function      ProcessLDRCommandStream

 @Description   Process the output of the Meta toolchain in the .LDR format
                copying code and data sections into their final location and
                passing some information to the Meta bootloader

 @Input         hPrivate             : Implementation specific data
 @Input         pbLDR                : Pointer to FW blob
 @Input         pvHostFWCodeAddr     : Pointer to FW code
 @Input         pvHostFWDataAddr     : Pointer to FW data
 @Input         pvHostFWCorememAddr  : Pointer to FW coremem code
 @Input         ppui32BootConf       : Pointer to bootloader data

 @Return        PVRSRV_ERROR

******************************************************************************/
static PVRSRV_ERROR ProcessLDRCommandStream(const void *hPrivate,
                                            const IMG_BYTE* pbLDR,
                                            void* pvHostFWCodeAddr,
                                            void* pvHostFWDataAddr,
                                            void* pvHostFWCorememAddr,
                                            IMG_UINT32 **ppui32BootConf)
{
	RGX_META_LDR_BLOCK_HDR *psHeader = (RGX_META_LDR_BLOCK_HDR *) pbLDR;
	RGX_META_LDR_L1_DATA_BLK *psL1Data =
	    (RGX_META_LDR_L1_DATA_BLK*) ((IMG_UINT8 *) pbLDR + psHeader->ui32SLData);

	IMG_UINT32 *pui32BootConf  = *ppui32BootConf;
	IMG_UINT32 ui32CorememSize = RGXGetFWCorememSize(hPrivate);
	IMG_UINT32 ui32CorememCodeStartAddr = 0xFFFFFFFF;

	RGXCommentLog(hPrivate, "**********************************************");
	RGXCommentLog(hPrivate, "************** Begin LDR Parsing *************");
	RGXCommentLog(hPrivate, "**********************************************");

	while (psL1Data != NULL)
	{
		RGX_FW_SEGMENT_LIST sRGXFWSegList;
		sRGXFWSegList.psRGXFWCodeSeg = asRGXMetaFWCodeSegments;
		sRGXFWSegList.psRGXFWDataSeg = asRGXMetaFWDataSegments;
		sRGXFWSegList.ui32CodeSegCount = RGXFW_META_NUM_CODE_SEGMENTS;
		sRGXFWSegList.ui32DataSegCount = RGXFW_META_NUM_DATA_SEGMENTS;

		if (RGX_META_LDR_BLK_IS_COMMENT(psL1Data->ui16Cmd))
		{
			/* Don't process comment blocks */
			goto NextBlock;
		}

		switch (psL1Data->ui16Cmd & RGX_META_LDR_CMD_MASK)
		{
			case RGX_META_LDR_CMD_LOADMEM:
			{
				RGX_META_LDR_L2_DATA_BLK *psL2Block =
				    (RGX_META_LDR_L2_DATA_BLK*) (((IMG_UINT8 *) pbLDR) + psL1Data->aui32CmdData[1]);
				IMG_UINT32 ui32Offset = psL1Data->aui32CmdData[0];
				IMG_UINT32 ui32DataSize = psL2Block->ui16Length - 6 /* L2 Tag length and checksum */;
				void *pvWriteAddr;
				PVRSRV_ERROR eError;

				if (RGX_META_IS_COREMEM_CODE(ui32Offset, ui32CorememSize))
				{
					if (ui32Offset < ui32CorememCodeStartAddr)
					{
						if (ui32CorememCodeStartAddr == 0xFFFFFFFF)
						{
							/* Take the first coremem code address as the coremem code start address */
							ui32CorememCodeStartAddr = ui32Offset;

							/* Also check that there is a valid allocation for the coremem code */
							if (pvHostFWCorememAddr == NULL)
							{
								RGXErrorLog(hPrivate,
								            "ProcessLDRCommandStream: Coremem code found"
								            "but no coremem allocation available!");

								return PVRSRV_ERROR_INIT_FAILURE;
							}
						}
						else
						{
							/* The coremem addresses should be ordered in the LDR command stream */
							return PVRSRV_ERROR_INIT_FAILURE;
						}
					}

					/* Copy coremem data to buffer. The FW copies it to the actual coremem */
					ui32Offset -= ui32CorememCodeStartAddr;

					RGXMemCopy(hPrivate,
					           (void*)((IMG_UINT8 *)pvHostFWCorememAddr + ui32Offset),
					           psL2Block->aui32BlockData,
					           ui32DataSize);
				}
				else
				{
					/* Global range is aliased to local range */
					ui32Offset &= ~META_MEM_GLOBAL_RANGE_BIT;

					eError = FindMMUSegment(ui32Offset,
					                        pvHostFWCodeAddr,
					                        pvHostFWDataAddr,
					                        &pvWriteAddr,
					                        &sRGXFWSegList);

					if (eError != PVRSRV_OK)
					{
						RGXErrorLog(hPrivate,
						            "ProcessLDRCommandStream: Addr 0x%x (size: %d) not found in any segment",
						            ui32Offset, ui32DataSize);
						return eError;
					}

					/* Write to FW allocation only if available */
					if (pvWriteAddr)
					{
						RGXMemCopy(hPrivate,
						           pvWriteAddr,
						           psL2Block->aui32BlockData,
						           ui32DataSize);
					}
				}

				break;
			}
			case RGX_META_LDR_CMD_LOADCORE:
			case RGX_META_LDR_CMD_LOADMMREG:
			{
				return PVRSRV_ERROR_INIT_FAILURE;
			}
			case RGX_META_LDR_CMD_START_THREADS:
			{
				/* Don't process this block */
				break;
			}
			case RGX_META_LDR_CMD_ZEROMEM:
			{
				IMG_UINT32 ui32Offset = psL1Data->aui32CmdData[0];
				IMG_UINT32 ui32ByteCount = psL1Data->aui32CmdData[1];
				void *pvWriteAddr;
				PVRSRV_ERROR  eError;

				if (RGX_META_IS_COREMEM_DATA(ui32Offset, ui32CorememSize))
				{
					/* cannot zero coremem directly */
					break;
				}

				/* Global range is aliased to local range */
				ui32Offset &= ~META_MEM_GLOBAL_RANGE_BIT;

				eError = FindMMUSegment(ui32Offset,
				                        pvHostFWCodeAddr,
				                        pvHostFWDataAddr,
				                        &pvWriteAddr,
				                        &sRGXFWSegList);

				if (eError != PVRSRV_OK)
				{
					RGXErrorLog(hPrivate,
					            "ProcessLDRCommandStream: Addr 0x%x (size: %d) not found in any segment",
					            ui32Offset, ui32ByteCount);
					return eError;
				}

				/* Write to FW allocation only if available */
				if (pvWriteAddr)
				{
					RGXMemSet(hPrivate, pvWriteAddr, 0, ui32ByteCount);
				}

				break;
			}
			case RGX_META_LDR_CMD_CONFIG:
			{
				RGX_META_LDR_L2_DATA_BLK *psL2Block =
				    (RGX_META_LDR_L2_DATA_BLK*) (((IMG_UINT8 *) pbLDR) + psL1Data->aui32CmdData[0]);
				RGX_META_LDR_CFG_BLK *psConfigCommand = (RGX_META_LDR_CFG_BLK*) psL2Block->aui32BlockData;
				IMG_UINT32 ui32L2BlockSize = psL2Block->ui16Length - 6 /* L2 Tag length and checksum */;
				IMG_UINT32 ui32CurrBlockSize = 0;

				while (ui32L2BlockSize)
				{
					switch (psConfigCommand->ui32Type)
					{
						case RGX_META_LDR_CFG_PAUSE:
						case RGX_META_LDR_CFG_READ:
						{
							ui32CurrBlockSize = 8;
							return PVRSRV_ERROR_INIT_FAILURE;
						}
						case RGX_META_LDR_CFG_WRITE:
						{
							IMG_UINT32 ui32RegisterOffset = psConfigCommand->aui32BlockData[0];
							IMG_UINT32 ui32RegisterValue  = psConfigCommand->aui32BlockData[1];

							/* Only write to bootloader if we got a valid
							 * pointer to the FW code allocation
							 */
							if (pui32BootConf)
							{
								/* Do register write */
								*pui32BootConf++ = ui32RegisterOffset;
								*pui32BootConf++ = ui32RegisterValue;
							}

							RGXCommentLog(hPrivate, "Meta SP: [0x%08x] = 0x%08x",
							              ui32RegisterOffset, ui32RegisterValue);

							ui32CurrBlockSize = 12;
							break;
						}
						case RGX_META_LDR_CFG_MEMSET:
						case RGX_META_LDR_CFG_MEMCHECK:
						{
							ui32CurrBlockSize = 20;
							return PVRSRV_ERROR_INIT_FAILURE;
						}
						default:
						{
							return PVRSRV_ERROR_INIT_FAILURE;
						}
					}
					ui32L2BlockSize -= ui32CurrBlockSize;
					psConfigCommand = (RGX_META_LDR_CFG_BLK*) (((IMG_UINT8*) psConfigCommand) + ui32CurrBlockSize);
				}

				break;
			}
			default:
			{
				return PVRSRV_ERROR_INIT_FAILURE;
			}
		}

NextBlock:

		if (psL1Data->ui32Next == 0xFFFFFFFF)
		{
			psL1Data = NULL;
		}
		else
		{
			psL1Data = (RGX_META_LDR_L1_DATA_BLK*) (((IMG_UINT8 *) pbLDR) + psL1Data->ui32Next);
		}
	}

	*ppui32BootConf = pui32BootConf;

	RGXCommentLog(hPrivate, "**********************************************");
	RGXCommentLog(hPrivate, "************** End Loader Parsing ************");
	RGXCommentLog(hPrivate, "**********************************************");

	return PVRSRV_OK;
}

/*!
*******************************************************************************

 @Function      ProcessELFCommandStream

 @Description   Process the output of the Mips toolchain in the .ELF format
                copying code and data sections into their final location

 @Input         hPrivate          : Implementation specific data
 @Input         pbELF             : Pointer to FW blob
 @Input         pvHostFWCodeAddr  : Pointer to FW code
 @Input         pvHostFWDataAddr  : Pointer to FW data

 @Return        PVRSRV_ERROR

******************************************************************************/
static PVRSRV_ERROR ProcessELFCommandStream(const void *hPrivate,
                                            const IMG_BYTE *pbELF,
                                            void *pvHostFWCodeAddr,
                                            void *pvHostFWDataAddr)
{
	IMG_UINT32 ui32Entry;
	RGX_MIPS_ELF_HDR *psHeader = (RGX_MIPS_ELF_HDR *)pbELF;
	RGX_MIPS_ELF_PROGRAM_HDR *psProgramHeader =
	    (RGX_MIPS_ELF_PROGRAM_HDR *)(pbELF + psHeader->ui32Ephoff);
	PVRSRV_ERROR eError;

	for (ui32Entry = 0; ui32Entry < psHeader->ui32Ephnum; ui32Entry++, psProgramHeader++)
	{
		void *pvWriteAddr;
		RGX_FW_SEGMENT_LIST sRGXFWSegList;
		sRGXFWSegList.psRGXFWCodeSeg = asRGXMipsFWCodeSegments;
		sRGXFWSegList.psRGXFWDataSeg = asRGXMipsFWDataSegments;
		sRGXFWSegList.ui32CodeSegCount = RGXFW_MIPS_NUM_CODE_SEGMENTS;
		sRGXFWSegList.ui32DataSegCount = RGXFW_MIPS_NUM_DATA_SEGMENTS;

		/* Only consider loadable entries in the ELF segment table */
		if (psProgramHeader->ui32Ptype != ELF_PT_LOAD) continue;

		eError = FindMMUSegment(psProgramHeader->ui32Pvaddr,
		                        pvHostFWCodeAddr,
		                        pvHostFWDataAddr,
		                        &pvWriteAddr,
		                        &sRGXFWSegList);

		if (eError != PVRSRV_OK)
		{
			RGXErrorLog(hPrivate,
			            "%s: Addr 0x%x (size: %d) not found in any segment",__func__,
			            psProgramHeader->ui32Pvaddr,
			            psProgramHeader->ui32Pfilesz);
			return eError;
		}

		/* Write to FW allocation only if available */
		if (pvWriteAddr)
		{
			RGXMemCopy(hPrivate,
			           pvWriteAddr,
			           (IMG_PBYTE)(pbELF + psProgramHeader->ui32Poffset),
			           psProgramHeader->ui32Pfilesz);

			RGXMemSet(hPrivate,
			          (IMG_PBYTE)pvWriteAddr + psProgramHeader->ui32Pfilesz,
			          0,
			          psProgramHeader->ui32Pmemsz - psProgramHeader->ui32Pfilesz);
		}
	}

	return PVRSRV_OK;
}


PVRSRV_ERROR RGXGetFWImageAllocSize(const void *hPrivate,
                                    const IMG_BYTE    *pbRGXFirmware,
                                    IMG_DEVMEM_SIZE_T *puiFWCodeAllocSize,
                                    IMG_DEVMEM_SIZE_T *puiFWDataAllocSize,
                                    IMG_DEVMEM_SIZE_T *puiFWCorememAllocSize)
{
	IMG_UINT32 i, ui32NumCodeSegments = 0, ui32NumDataSegments = 0;
	RGX_FW_SEGMENT *pasRGXFWCodeSegments = NULL, *pasRGXFWDataSegments = NULL;

	IMG_BOOL bMIPS = RGX_DEVICE_HAS_FEATURE(hPrivate, MIPS);

	if (!bMIPS)
	{
		pasRGXFWCodeSegments = asRGXMetaFWCodeSegments;
		pasRGXFWDataSegments = asRGXMetaFWDataSegments;
		ui32NumCodeSegments = RGXFW_META_NUM_CODE_SEGMENTS;
		ui32NumDataSegments = RGXFW_META_NUM_DATA_SEGMENTS;
	}

	if (bMIPS)
	{
		pasRGXFWCodeSegments = asRGXMipsFWCodeSegments;
		pasRGXFWDataSegments = asRGXMipsFWDataSegments;
		ui32NumCodeSegments = RGXFW_MIPS_NUM_CODE_SEGMENTS;
		ui32NumDataSegments = RGXFW_MIPS_NUM_DATA_SEGMENTS;
	}

	*puiFWCodeAllocSize = 0;
	*puiFWDataAllocSize = 0;
	*puiFWCorememAllocSize = 0;

	/* Calculate how much memory the FW needs for its code and data segments */

	for(i = 0; i < ui32NumCodeSegments; i++) {
		*puiFWCodeAllocSize += ((pasRGXFWCodeSegments + i)->ui32SegAllocSize);
	}

	for(i = 0; i < ui32NumDataSegments; i++) {
		*puiFWDataAllocSize += ((pasRGXFWDataSegments + i)->ui32SegAllocSize);
	}

	*puiFWCorememAllocSize = RGXGetFWCorememSize(hPrivate);

	if (*puiFWCorememAllocSize != 0)
	{
		if (pbRGXFirmware != NULL)
		{
			/* Calculate actual core mem in use by parsing FW binary blob */
			*puiFWCorememAllocSize = GetCoreMemCodeSizeInUse(*puiFWCorememAllocSize, pbRGXFirmware);
			if (RGX_DEVICE_HAS_FEATURE(hPrivate, META_DMA))
			{
				/* Align size to DMA block size */
				*puiFWCorememAllocSize = (*puiFWCorememAllocSize + (RGXFW_DMA_BLOCK_SIZE - 1)) & RGXFW_DMA_BLOCK_ALIGNMENT_MASK;
			}
		}
		else
		{
			*puiFWCorememAllocSize = *puiFWCorememAllocSize - RGX_META_COREMEM_DATA_SIZE;
		}
	}

	if (bMIPS)
	{
		if ((*puiFWCodeAllocSize % RGXMIPSFW_PAGE_SIZE) != 0)
		{
			RGXErrorLog(hPrivate,
			            "%s: The MIPS FW code allocation is not"
			            " a multiple of the page size!", __func__);
			return PVRSRV_ERROR_INIT_FAILURE;
		}

		if ((*puiFWDataAllocSize % RGXMIPSFW_PAGE_SIZE) != 0)
		{
			RGXErrorLog(hPrivate,
			            "%s: The MIPS FW data allocation is not"
			            " a multiple of the page size!", __func__);
			return PVRSRV_ERROR_INIT_FAILURE;
		}
	}

	return PVRSRV_OK;
}


PVRSRV_ERROR RGXProcessFWImage(const void           *hPrivate,
                               const IMG_BYTE       *pbRGXFirmware,
                               void                 *pvFWCode,
                               void                 *pvFWData,
                               void                 *pvFWCorememCode,
                               IMG_DEV_VIRTADDR     *psFWCodeDevVAddrBase,
                               IMG_DEV_VIRTADDR     *psFWDataDevVAddrBase,
                               IMG_DEV_VIRTADDR     *psFWCorememDevVAddrBase,
                               RGXFWIF_DEV_VIRTADDR *psFWCorememFWAddr,
                               RGXFWIF_DEV_VIRTADDR *psRGXFwInit,
                               IMG_UINT32           ui32NumThreads,
                               IMG_UINT32           ui32MainThreadID,
                               IMG_UINT32           uiFWCorememCodeSize)
{
	PVRSRV_ERROR eError = PVRSRV_OK;
	IMG_BOOL bMIPS = RGX_DEVICE_HAS_FEATURE(hPrivate, MIPS);

	if (!bMIPS)
	{
		IMG_UINT32 *pui32BootConf = NULL;
		/* Skip bootloader configuration if a pointer to the FW code
		 * allocation is not available
		 */
		if (pvFWCode)
		{
			/* This variable points to the bootloader code which is mostly
			 * a sequence of <register address,register value> pairs
			 */
			pui32BootConf = ((IMG_UINT32*) pvFWCode) + RGXFW_BOOTLDR_CONF_OFFSET;

			/* Slave port and JTAG accesses are privileged */
			*pui32BootConf++ = META_CR_SYSC_JTAG_THREAD;
			*pui32BootConf++ = META_CR_SYSC_JTAG_THREAD_PRIV_EN;

			RGXFWConfigureSegMMU(hPrivate,
			                     psFWCodeDevVAddrBase,
			                     psFWDataDevVAddrBase,
			                     &pui32BootConf);
		}
	
		/* Process FW image data stream */
		eError = ProcessLDRCommandStream(hPrivate,
		                                 pbRGXFirmware,
		                                 pvFWCode,
		                                 pvFWData,
		                                 pvFWCorememCode,
		                                 &pui32BootConf);
		if (eError != PVRSRV_OK)
		{
			RGXErrorLog(hPrivate, "RGXProcessFWImage: Processing FW image failed (%d)", eError);
			return eError;
		}

		/* Skip bootloader configuration if a pointer to the FW code
		 * allocation is not available
		 */
		if (pvFWCode)
		{
			if ((ui32NumThreads == 0) || (ui32NumThreads > 2) || (ui32MainThreadID >= 2))
			{
				RGXErrorLog(hPrivate,
				            "ProcessFWImage: Wrong Meta threads configuration, using one thread only");

				ui32NumThreads = 1;
				ui32MainThreadID = 0;
			}

			RGXFWConfigureMetaCaches(hPrivate,
			                         ui32NumThreads,
			                         ui32MainThreadID,
			                         &pui32BootConf);

			/* Signal the end of the conf sequence */
			*pui32BootConf++ = 0x0;
			*pui32BootConf++ = 0x0;

			/* The FW main argv arguments start here */
			*pui32BootConf++ = psRGXFwInit->ui32Addr;

			if (uiFWCorememCodeSize && (psFWCorememFWAddr != NULL))
			{
				*pui32BootConf++ = psFWCorememFWAddr->ui32Addr;
				*pui32BootConf++ = uiFWCorememCodeSize;
			}
			else
			{
				*pui32BootConf++ = 0;
				*pui32BootConf++ = 0;
			}

			if (RGX_DEVICE_HAS_FEATURE(hPrivate, META_DMA))
			{
				*pui32BootConf++ = (IMG_UINT32) (psFWCorememDevVAddrBase->uiAddr >> 32);
				*pui32BootConf++ = (IMG_UINT32) psFWCorememDevVAddrBase->uiAddr;
			}
			else
			{
				*pui32BootConf++ = 0;
				*pui32BootConf++ = 0;
			}

		}
	}

	if (bMIPS)
	{
		/* Process FW image data stream */
		eError = ProcessELFCommandStream(hPrivate,
		                                 pbRGXFirmware,
		                                 pvFWCode,
		                                 pvFWData);
		if (eError != PVRSRV_OK)
		{
			RGXErrorLog(hPrivate, "RGXProcessFWImage: Processing FW image failed (%d)", eError);
			return eError;
		}
	}

	return eError;
}

