/*---------------------------------------------------------------------------*
  Project:  NitroSDK - CARD - libraries
  File:     card_common.c

  Copyright 2003,2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: card_common.c,v $
  Revision 1.14  2004/11/12 05:45:48  yosizaki
  add CARDi_CheckValidity().

  Revision 1.13  2004/11/02 10:03:59  yosizaki
  fix typo.

  Revision 1.12  2004/10/08 14:31:41  yosizaki
  add CARD_GetRomHeader().

  Revision 1.11  2004/09/10 01:05:49  yosizaki
  fix declaration position of variables.

  Revision 1.10  2004/09/10 00:00:16  yosizaki
  add assertion checkings.

  Revision 1.9  2004/09/02 11:54:42  yosizaki
  fix CARD include directives.

  Revision 1.8  2004/08/31 10:59:28  yosizaki
  add errorcode CARD_RESULT_SUCCESS in some functions.

  Revision 1.7  2004/08/18 03:24:14  yosizaki
  fix around PXI-INIT command.

  Revision 1.6  2004/07/28 13:18:06  yosizaki
  asynchronous mode support.

  Revision 1.5  2004/07/23 08:28:46  yosizaki
  change backup-routine to ARM7 only.

  Revision 1.4  2004/07/23 01:04:55  yada
  OS_UnLock* -> OS_Unlock*.
  type of lockID for lock functions becomes u16

  Revision 1.3  2004/07/22 11:31:47  yosizaki
  fix CARD access of ARM7.

  Revision 1.2  2004/07/19 13:14:57  yosizaki
  add task thread.

  Revision 1.1  2004/07/10 10:19:55  yosizaki
  (none)


 *---------------------------------------------------------------------------*/


#include <nitro.h>
#include <nitro/mb.h>

#include "../include/card_common.h"
#include "../include/card_spi.h"


/*  Suspend because priority level was not specified when request arrived */
#define	CARD_THREAD_PRIORITY_ABOVE	(OS_THREAD_PRIORITY_MAX + 1)
#define	CARD_THREAD_PRIORITY_BELOW	(OS_THREAD_PRIORITY_MAX + 2)


/******************************************************************************/
/* Variables */

CARDiCommon	cardi_common ATTRIBUTE_ALIGN(32);
static CARDiCommandArg	cardi_arg ATTRIBUTE_ALIGN(32);

static u8	cardi_thread_stack[0x400] ATTRIBUTE_ALIGN(4);


/* multiboot child CARD-ROM header information */
#if	!defined(SDK_SMALL_BUILD) && defined(SDK_ARM9)

#include <nitro/mb.h>

#define	MB_CARD_ROM_HEADER_ADDRESS	0x023fe940
static u8	cardi_own_rom_header[0x160] ATTRIBUTE_ALIGN(32);

#endif	/* !defined(SDK_SMALL_BUILD) && defined(SDK_ARM9) */


/******************************************************************************/
/* Functions */

static void	CARDi_OnFifoRecv(PXIFifoTag tag, u32 data, BOOL err);
static void	CARDi_TaskThread(void *arg);

static void	CARDi_LockResource(CARDiOwner owner, CARDTargetMode target);
static void	CARDi_UnlockResource(CARDiOwner owner, CARDTargetMode target);


/* ARM7/9 common PXI  callback */
static void	CARDi_OnFifoRecv(PXIFifoTag tag, u32 data, BOOL err)
{
	if((tag == PXI_FIFO_TAG_FS) && err) {
		CARDiCommon	* const p = &cardi_common;

#if	defined(SDK_ARM9)

		(void)data;
		SDK_ASSERT(data == CARD_REQ_ACK);
		p->flag &= ~CARD_STAT_REQ;
		OS_WakeupThreadDirect(p->cur_th);

#else

		if(!p->recv_step)
			p->command = (int)data;

		switch(p->command) {

		case CARD_REQ_INIT:
			/* Store common memory */
			switch(p->recv_step) {
			case 0:
				break;
			case 1:
				p->cmd = (CARDiCommandArg*)data;
				p->flag |= CARD_STAT_RECV;
				break;
			}
			break;

		case CARD_REQ_IDENTIFY:
		case CARD_REQ_READ_ID:
		case CARD_REQ_READ_ROM:
		case CARD_REQ_WRITE_ROM:
		case CARD_REQ_READ_BACKUP:
		case CARD_REQ_WRITE_BACKUP:
		case CARD_REQ_PROGRAM_BACKUP:
		case CARD_REQ_VERIFY_BACKUP:
		case CARD_REQ_ERASE_PAGE_BACKUP:
		case CARD_REQ_ERASE_SECTOR_BACKUP:
		case CARD_REQ_ERASE_CHIP_BACKUP:
			p->flag |= CARD_STAT_RECV;
			break;
		}

		if(!(p->flag & CARD_STAT_RECV))
			++p->recv_step;
		else {
			p->recv_step = 0;
			if((p->flag & CARD_STAT_BUSY) != 0)
				OS_WakeupThreadDirect(p->cur_th);
			else
				OS_WakeupThreadDirect(p->thread);
		}

#endif

	}
}


/*
 * Task thread that processes asynchronous API and requests.
 * The calling side (API) takes exclusive control of the task, and internally it is always the only process.
 * Thread priority is determined at the time of the call.
 */
static void	CARDi_TaskThread(void *arg)
{
	CARDiCommon	* const p = &cardi_common;
	(void)arg;

	for( ; ; ) {

#if	defined(SDK_ARM7)
		BOOL	is_req = FALSE;
#endif

		/* Wait for next process */
		OSIntrMode	bak_psr = OS_DisableInterrupts();
		for( ; ; ) {
#if	defined(SDK_ARM7)
			/* If a request arrives, it is processed by the ARM7 */
			if((p->flag & CARD_STAT_BUSY) == 0) {
				if((p->flag & CARD_STAT_RECV) != 0) {
					p->flag |= CARD_STAT_BUSY;
					p->flag &= ~CARD_STAT_RECV;
					is_req = TRUE;
					break;
				}
			}
			/* If there is no request, check for task request.  */
			else
#endif
			{
				if((p->flag & CARD_STAT_TASK) != 0)
					break;
			}
			p->cur_th = p->thread;
			OS_SleepThread(NULL);
		}
		(void)OS_RestoreInterrupts(bak_psr);

#if	defined(SDK_ARM7)
		/* Request processing */
		if(is_req) {
			p->cmd->result = CARD_RESULT_SUCCESS;
			(*cardi_request_table[p->command])(p);
			CARDi_SendPxi(CARD_REQ_ACK);
			CARDi_EndTask(p);
		}
		/* If there is no request, process API task */
		else
#endif
		{
			(*p->task_func)(p);
		}
	}
}

/* Assign task to thread. (The task is already exclusively controlled.) */
void	CARDi_SetTask(void (*task)(CARDiCommon*))
{
	CARDiCommon	* const p = &cardi_common;

	{	/* First, change the task thread priority of sleeping threads */
		u32	priority = p->priority;
		switch(priority) {
		case CARD_THREAD_PRIORITY_ABOVE:
			{
				OSThread	*th = OS_GetCurrentThread();
				priority = OS_GetThreadPriority(th);
				if(priority > OS_THREAD_PRIORITY_MIN)	--priority;
			}
			break;
		case CARD_THREAD_PRIORITY_BELOW:
			{
				OSThread	*th = OS_GetCurrentThread();
				priority = OS_GetThreadPriority(th);
				if(priority < OS_THREAD_PRIORITY_MAX)	++priority;
			}
			break;
		}
		(void)OS_SetThreadPriority(p->thread, priority);
	}

	{	/* Set the task to process and wake the thread.  */
		p->cur_th = p->thread;
		p->task_func = task;
		p->flag |= CARD_STAT_TASK;
		OS_WakeupThreadDirect(p->thread);
	}
}

/* Lock the resource to prevent use by others */
static void	CARDi_LockResource(CARDiOwner owner, CARDTargetMode target)
{
	CARDiCommon	* const p = &cardi_common;
	OSIntrMode	bak_psr = OS_DisableInterrupts();
	if(p->lock_owner == owner) {
		if(p->lock_target != target) {
			OS_Panic("card-lock : can not reuse same ID for locking without unlocking!");
		}
	} else {
		while(p->lock_owner != OS_LOCK_ID_ERROR)
			OS_SleepThread(p->lock_queue);
		p->lock_owner = owner;
		p->lock_target = target;
	}
	++p->lock_ref;
	p->cmd->result = CARD_RESULT_SUCCESS;
	(void)OS_RestoreInterrupts(bak_psr);
}

/* Unlock single resource  */
static void	CARDi_UnlockResource(CARDiOwner owner, CARDTargetMode target)
{
	CARDiCommon	*p = &cardi_common;
	OSIntrMode	bak_psr = OS_DisableInterrupts();
	if((p->lock_owner != owner) || !p->lock_ref) {
		OS_Panic("card-unlock : specified ID for unlocking is not locking one!");
	} else {
		if(p->lock_target != target) {
			OS_Panic("card-unlock : locking target and unlocking one are different!");
		}
		if(!--p->lock_ref) {
			p->lock_owner = OS_LOCK_ID_ERROR;
			p->lock_target = CARD_TARGET_NONE;
			OS_WakeupThread(p->lock_queue);
		}
	}
	p->cmd->result = CARD_RESULT_SUCCESS;
	(void)OS_RestoreInterrupts(bak_psr);
}

/* Initialize exclusive control  */
void	CARDi_InitCommon(void)
{
	CARDiCommon	*p = &cardi_common;

	/* Initialize lock target */
	p->lock_owner = OS_LOCK_ID_ERROR;
	p->lock_ref = 0;
	p->lock_target = CARD_TARGET_NONE;

	/* Set shared memory */
#if	defined(SDK_ARM9)
	p->cmd = &cardi_arg;
	MI_CpuFillFast(&cardi_arg, 0x00, sizeof(cardi_arg));
	DC_FlushRange(&cardi_arg, sizeof(cardi_arg));
#else
	p->cmd = NULL;
	p->recv_step = 0;
#endif

#if	!defined(SDK_SMALL_BUILD) && defined(SDK_ARM9)
	/* save appropriate CARD-ROM header information */
	MI_CpuCopy8((const void*)(MB_IsMultiBootChild() ?
		MB_CARD_ROM_HEADER_ADDRESS : HW_ROM_HEADER_BUF),
		cardi_own_rom_header, sizeof(cardi_own_rom_header));
#endif	/* !defined(SDK_SMALL_BUILD) && defined(SDK_ARM9) */

#if	!defined(SDK_NO_THREAD)
	/* Start the task thread */
	OS_InitThreadQueue(p->lock_queue);
	OS_InitThreadQueue(p->busy_q);
	p->priority = CARD_THREAD_PRIORITY_DEFAULT;
	OS_CreateThread(p->thread,
		CARDi_TaskThread, NULL,
		cardi_thread_stack + sizeof(cardi_thread_stack),
		sizeof(cardi_thread_stack),
		p->priority);
	OS_WakeupThreadDirect(p->thread);
#endif

	/* Register callback */
	PXI_SetFifoRecvCallback(PXI_FIFO_TAG_FS, CARDi_OnFifoRecv);
}

/*
 * Assesses whether a CARD is a proper environment for access.
 *
 * As of now the following have not been discussed for wireless download children.
 *
 * 1. What is the default status,
 * 2. how is it assessed,
 * 3. and how far can access go.
 *
 *
 * Therefore, at present,
 *
 * 1. by default it will always be in all prohibited state.
 * 2. to the extent that CARDi_CheckValidity() is not exchanged externally at will,
 * 3. no access will be performed..
 *
 *
 * We would like to inform developers who wish to simply validate without waiting for these discussions
 * of the intent that this function be a weak link.
 */
__declspec(weak) BOOL	CARDi_CheckValidity(void)
{
	return	!MB_IsMultiBootChild();
}

/* API ************************************************************************/

BOOL	CARDi_WaitAsync(void)
{
	CARDiCommon	* const p = &cardi_common;
	SDK_ASSERT(CARD_IsAvailable());

	{
		OSIntrMode	bak_psr = OS_DisableInterrupts();
		while((p->flag & CARD_STAT_BUSY) != 0)
			OS_SleepThread(p->busy_q);
		(void)OS_RestoreInterrupts(bak_psr);
	}
	return	(p->cmd->result == CARD_RESULT_SUCCESS);
}

BOOL	CARDi_TryWaitAsync(void)
{
	CARDiCommon	* const p = &cardi_common;
	SDK_ASSERT(CARD_IsAvailable());

	return	!(p->flag & CARD_STAT_BUSY);
}

/* gets card access enabled state */
BOOL	CARD_IsAvailable(void)
{
	CARDiCommon	* const p = &cardi_common;
	return	(p->flag != 0);
}

/* Obtain the error code of the most recently called function */
CARDResult	CARD_GetResultCode(void)
{
	CARDiCommon	* const p = &cardi_common;
	SDK_ASSERT(CARD_IsAvailable());

	return	p->cmd->result;
}

/* Obtain the priority level of the thread used by the card's asynchronous functions */
u32	CARD_GetThreadPriority(void)
{
	CARDiCommon	* const p = &cardi_common;
	SDK_ASSERT(CARD_IsAvailable());

	return	p->priority;
}

/* Set the priority level of the thread used by the card's asynchronous functions */
u32	CARD_SetThreadPriority(u32 prior)
{
	CARDiCommon	* const p = &cardi_common;
	SDK_ASSERT(CARD_IsAvailable());

	{
		OSIntrMode	bak_psr = OS_DisableInterrupts();
		u32	ret = p->priority;
		SDK_ASSERT((prior >= OS_THREAD_PRIORITY_MIN) && (prior <= OS_THREAD_PRIORITY_MAX));
		p->priority = prior;
		(void)OS_SetThreadPriority(p->thread, p->priority);
		(void)OS_RestoreInterrupts(bak_psr);
		return	ret;
	}
}

/* Secure access rights to the ROM device */
void	CARD_LockRom(u16 lock_id)
{
	SDK_ASSERT(CARD_IsAvailable());

	/* Lock the resource */
	CARDi_LockResource(lock_id, CARD_TARGET_ROM);
	/* Lock the card bus */
#if	defined(SDK_TEG)
	if(!CARDi_IsTrueRom())
		(void)OS_LockCartridge(lock_id);
	else
#endif
	{
		(void)OS_LockCard(lock_id);
	}
}

/* Release access rights to the ROM device */
void	CARD_UnlockRom(u16 lock_id)
{
	SDK_ASSERT(CARD_IsAvailable());
	SDK_ASSERT(cardi_common.lock_target == CARD_TARGET_ROM);

	/* unlock card bus */
#if	defined(SDK_TEG)
	if(!CARDi_IsTrueRom())
		(void)OS_UnLockCartridge(lock_id);
	else
#endif
	{
		(void)OS_UnlockCard(lock_id);
	}
	/* unlock resource */
	CARDi_UnlockResource(lock_id, CARD_TARGET_ROM);

}

/* secure access rights to backup device */
void	CARD_LockBackup(u16 lock_id)
{
	SDK_ASSERT(CARD_IsAvailable());

	/* take backup exclusion/ROM in processor */
	CARDi_LockResource(lock_id, CARD_TARGET_BACKUP);
	/* ARM7 always accesses */
#if	defined(SDK_ARM7)
	(void)OS_LockCard(lock_id);
#endif
}

/* release access rights to ROM device */
void	CARD_UnlockBackup(u16 lock_id)
{
	SDK_ASSERT(CARD_IsAvailable());
	SDK_ASSERT(cardi_common.lock_target == CARD_TARGET_BACKUP);

	/* ARM7 always accesses */
#if	defined(SDK_ARM7)
	(void)OS_UnlockCard(lock_id);
#endif
	/* unlock resources */
	CARDi_UnlockResource(lock_id, CARD_TARGET_BACKUP);
}


#if	!defined(SDK_SMALL_BUILD) && defined(SDK_ARM9)


/*---------------------------------------------------------------------------*
  Name:         CARD_GetRomHeader

  Description:  get data buffer which contains own ROM header information.
                if wireless-downloaded program call this function,
                the data is not equal to HW_ROM_HEADER_BUF's.

  Arguments:    None.

  Returns:      pointer of CARD-ROM header information buffer.
 *---------------------------------------------------------------------------*/
const u8*	CARD_GetRomHeader(void)
{
	return	cardi_own_rom_header;
}


#endif	/* !defined(SDK_SMALL_BUILD) && defined(SDK_ARM9) */
