/*---------------------------------------------------------------------------*
 Project:  NitroSDK - MB - demos - mb_child
 File:     main.c

 Copyright 2003-2006 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.

 Revision 1.11  2006/01/18 02:11:28  kitase_hirotake
 do-indent

 Revision 1.10  2005/11/24 00:44:56  yosizaki
 fix warnings about implicit cast.

 Revision 1.9  2005/11/21 10:50:53  kitase_hirotake
 PXI_Init の削除
 SVC_WaitVBlankIntr を OS_WaitVBlankIntr に変更

 Revision 1.8  2005/02/28 05:26:19  yosizaki
 do-indent.

 Revision 1.7  2004/10/01 11:06:31  terui
 起動時に無線使用許可チャンネルを計算する機能を追加。

 Revision 1.6  2004/10/01 03:01:34  terui
 Marionea2.10.00以降ではWM_sp_initでスレッド優先度の引数が変更された件に対応。

 Revision 1.5  2004/09/22 10:50:05  sato_masaki
 IPL_branch1.4.4.1とマージ

 Revision 1.4.4.1  2004/09/21 07:10:22  miya
 small fix

 Revision 1.4  2004/09/07 04:12:15  sato_masaki
 SPI_Initの引数変化に対処。

 Revision 1.3  2004/09/03 07:09:25  sato_masaki
 MBライブラリのファイル分割による更新。NVRAMまわりのリネームに対応。

 Revision 1.2  2004/09/01 08:57:15  sato_masaki
 自機のユーザーネームをNVRAMに保存されている設定データから取得するように変更。

 Revision 1.1  2004/08/31 11:00:12  sato_masaki
 initial check-in

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

#include <nitro.h>
#include <nitro/wm.h>
#include <nitro/mb.h>
#include "spi.h"
#include "nvram_sp.h"

#include "loader.h"
#include "boot.h"

//#define USE_WM_OVERLAY

/* コンパイル時チェック */
#define SDK_STATIC_ASSERT(expr) \
extern  sdk_static_assert_ ## __LINE__ (char [(expr) ? +1 : -1])

/* デバッグ出力 */
#if defined(PRINT_DEBUG)
#define MB_DEBUG_OUTPUT         OS_Printf
#else
#define MB_DEBUG_OUTPUT( ... )  ((void)0)
#endif

/* definitions */
#define VBLANKINTR

#define THREAD_PRIO_SPI                 2
#define THREAD_PRIO_SPI_TP              3
#define THREAD_PRIO_SPI_NVRAM           13
#define THREAD_PRIO_SPI_MIC             1
#define THREAD_PRIO_SPI_PM              10
#define THREAD_PRIO_RTC                 12

#define FS_THREAD_PRIORITY              (OS_THREAD_LAUNCHER_PRIORITY - 1)
#define FS_DEFAULT_DMA                  FS_DMA_NOT_USE

#define HEAP_ARENA  OS_ARENA_WRAM_SUBPRIV

//#define USE_MAINMEM_WL_WORK     /* TEGではダメ */
//#define USE_MAINMEM_WL_CAMTABLE /* TEGではダメ */

//#define USE_HEAP_WL_STACK
//#define USE_HEAP_WM_STACK

#define WL_WORK_SIZE        0x800
#define WL_STACK_SIZE       0x400
#define WL_THREAD_PRIO      8
#define WM_REQ_PRIO             9
#define WM_IND_PRIO             7

#define WM_STACK_SIZE       0x400
#define WM_THREAD_PRIO      10
#define WM_SUB_THREAD_PRIO  8
#define WM_DMA_NO       3
#define WM_CAMTABLE_NUM     16

/* static variables */

#ifndef USE_MAINMEM_WL_WORK
static u32 wlWorkRam[WL_WORK_SIZE / 4] ATTRIBUTE_ALIGN(4);
#endif /*  USE_MAINMEM_WL_WORK */

#ifndef USE_MAINMEM_WL_CAMTABLE
static WlStaElement wlStaElement[WM_CAMTABLE_NUM] ATTRIBUTE_ALIGN(4);
#endif /*  USE_MAINMEM_WL_CAMTABLE */

#ifndef USE_HEAP_WL_STACK
static u32 wlStack[WL_STACK_SIZE / sizeof(u32)] ATTRIBUTE_ALIGN(8);
#endif /* USE_HEAP_WL_STACK */

#ifndef USE_HEAP_WM_STACK
static u64 wmStack[WM_STACK_SIZE / sizeof(u64)] ATTRIBUTE_ALIGN(8);
#endif /* USE_HEAP_WM_STACK */

// ===== CRC算出用テーブル（ GetCRC16で使用 ) =====
static const u16 crc16_table[16] = {
    0x0000, 0xCC01, 0xD801, 0x1400,
    0xF001, 0x3C00, 0x2800, 0xE401,
    0xA001, 0x6C00, 0x7800, 0xB401,
    0x5000, 0x9C01, 0x8801, 0x4400,
};


/* static functions */
static void mb_loader_callback(void);
static OSHeapHandle InitializeAllocateSystem(void);
static void ReadUserInfo(void);
static s32 CheckCorrectNCD(NVRAMConfig *ncdsp);
static u16 GetCRC16(u16 start_value, u16 *datap, u32 size);

#ifdef VBLANKINTR
static void VBlankIntr(void);
static u32 v_blank_intr_counter = 0;
#endif

#ifdef USE_WM_OVERLAY
FS_EXTERN_OVERLAY(wm_overlay);
#endif


/*---------------------------------------------------------------------------*
  Name:         mb_loader_callback

  Description:  マルチブート準備完了コールバック

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/

static void mb_loader_callback(void)
{
    BOOL    result;
    MB_DEBUG_OUTPUT("(ARM7)boot start!\n");
    (void)LOADER_Start();
    result = BOOT_Start();             // ARM7側のブート処理を行う
    if (result == FALSE)
    {
        MB_DEBUG_OUTPUT("(ARM7)multi boot jump failed!\n");
    }
}


/*---------------------------------------------------------------------------*
  Name:         NitroSpMain

  Description:  Initialize and do main

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/

void NitroSpMain(void)
{
    WlInit  wlInit;
    WmInit  wmInit;
    OSHeapHandle heapHandle;

    //------------------------------------------------------
    // OS関連の初期化
    OS_Init();
    OS_InitThread();

    // NVRAMからユーザー情報を読み出し
    ReadUserInfo();

    MB_DEBUG_OUTPUT("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    LOADER_Init(mb_loader_callback);

    // メモリアロケートシステムの初期化
    heapHandle = InitializeAllocateSystem();

    (void)PAD_InitXYButton();          // X,Yボタン読み出し

    // 割り込み許可
#ifdef VBLANKINTR
    (void)OS_SetIrqFunction(OS_IE_V_BLANK, VBlankIntr);
    (void)OS_EnableIrqMask(OS_IE_V_BLANK);
    (void)GX_VBlankIntr(TRUE);
#endif
    (void)OS_EnableIrq();
    (void)OS_EnableInterrupts();

    FS_Init(FS_DEFAULT_DMA);

    FS_CreateReadServerThread(FS_THREAD_PRIORITY);

#ifdef USE_WM_OVERLAY
    // ----- Overlay読み込み
    if (!FS_LoadOverlay(MI_PROCESSOR_ARM7, FS_OVERLAY_ID(wm_overlay)))
    {
        MB_DEBUG_OUTPUT("FS: load segment error 0;\n");
    }
    else
    {
        MB_DEBUG_OUTPUT("FS: load segment success 0\n");
    }
#endif

    // RTC初期化
    RTC_Init(THREAD_PRIO_RTC);

    // WirelessManager初期化処理

#ifdef USE_MAINMEM_WL_WORK
    wlInit.workingMemAdrs = (u32)OS_AllocFromHeap(HEAP_ARENA, heapHandle, WL_WORK_SIZE);
#else
    wlInit.workingMemAdrs = (u32)wlWorkRam;
#endif
#ifdef USE_HEAP_WL_STACK
    wlInit.stack =
        (void *)(WL_STACK_SIZE + (u32)OS_AllocFromHeap(HEAP_ARENA, heapHandle, WL_STACK_SIZE));
#else
    wlInit.stack = &wlStack[WL_STACK_SIZE / 4];
#endif /* USE_HEAP_WL_STACK */

    wlInit.stacksize = WL_STACK_SIZE;
    wlInit.priority = 9;
    wlInit.heapType = WL_HEAP_OS;
    wlInit.heapFunc.os.id = HEAP_ARENA;
    wlInit.heapFunc.os.heapHandle = heapHandle;

#ifdef USE_MAINMEM_WL_CAMTABLE
    wlInit.camAdrs =
        OS_AllocFromHeap(HEAP_ARENA, heapHandle, sizeof(WlStaElement) * WM_CAMTABLE_NUM);
    wlInit.camSize = sizeof(WlStaElement) * WM_CAMTABLE_NUM;
#else
    wlInit.camAdrs = wlStaElement;
    wlInit.camSize = sizeof(wlStaElement);
#endif

#if ( SDK_VERSION_WL >= 20900 )
    wmInit.indPrio_high = WM_IND_PRIO;
    wmInit.wlPrio_high = WL_THREAD_PRIO;
    wmInit.reqPrio_high = WM_REQ_PRIO;
    wmInit.indPrio_low = WM_IND_PRIO;
    wmInit.wlPrio_low = WL_THREAD_PRIO;
    wmInit.reqPrio_low = WM_REQ_PRIO;
#else
    wmInit.reqPrio = WM_REQ_PRIO;
    wmInit.indPrio = WM_IND_PRIO;
#endif
    wmInit.dmaNo = WM_DMA_NO;          // DMA番号

    WM_sp_init(&wlInit, &wmInit);      // WirelessManagerの起動 (WLの起動を含む)

    /*  無線ライブラリが初期化時に長時間メインメモリを使用するため、
       PM_Initを後にすることでARM9側のOS_Initで初期化完了を待たせる。  */
    // SPI初期化
    SPI_Init(THREAD_PRIO_SPI);

    // IDLEスレッドになる
    (void)OS_SetThreadPriority(OS_GetCurrentThread(), (u32)OS_THREAD_PRIORITY_MAX);


    while (1)
    {
#ifdef VBLANKINTR
        OS_WaitVBlankIntr();
#endif
    }
}

#ifdef VBLANKINTR
extern BOOL PMi_Initialized;
void    PM_SelfBlinkProc(void);

static void VBlankIntr(void)
{
    //---- 割り込みチェックフラグvoid OS_Halt( void );

    OS_SetIrqCheckFlag(OS_IE_V_BLANK);
    v_blank_intr_counter++;
    //---- LED blink system
    if (PMi_Initialized)
    {
        PM_SelfBlinkProc();
    }
}
#endif

/*---------------------------------------------------------------------------*
  Name:         InitializeAllocateSystem

  Description:  メモリ割当てシステムを初期化する。

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static OSHeapHandle InitializeAllocateSystem(void)
{
    void   *tempLo;
    OSHeapHandle hh;

    // メモリ割当て初期化
    tempLo = OS_InitAlloc(OS_ARENA_WRAM_SUBPRIV,
                          OS_GetWramSubPrivArenaLo(), OS_GetWramSubPrivArenaHi(), 1);

    // アリーナを0クリア
    MI_CpuClear8(tempLo, (u32)OS_GetWramSubPrivArenaHi() - (u32)tempLo);

    // アリーナ下位アドレスを設定
    OS_SetArenaLo(OS_ARENA_WRAM_SUBPRIV, tempLo);

    // ヒープ作成
    hh = OS_CreateHeap(OS_ARENA_WRAM_SUBPRIV,
                       OS_GetWramSubPrivArenaLo(), OS_GetWramSubPrivArenaHi());

    OS_Printf("ARM7: create heap %08x - %08x\n", OS_GetWramSubPrivArenaLo(),
              OS_GetWramSubPrivArenaHi());

    if (hh < 0)
    {
        OS_Panic("ARM7: Fail to create heap.\n");
    }

    // カレントヒープに設定
    (void)OS_SetCurrentHeap(OS_ARENA_WRAM_SUBPRIV, hh);

    return hh;
}

#ifdef  WM_PRECALC_ALLOWEDCHANNEL
extern u16 WMSP_GetAllowedChannel(u16 bitField);
#endif
/*---------------------------------------------------------------------------*
  Name:         ReadUserInfo

  Description:  NVRAMからユーザー情報を読み出し、共有領域に展開する。
                ミラーリングされているバッファが両方壊れている場合は、
                共有領域のユーザー情報格納場所をクリアする。

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
static void ReadUserInfo(void)
{
    s32     offset;
    NVRAMConfig temp[2];
    s32     check;
    u8     *p = OS_GetSystemWork()->nvramUserInfo;

    // オフセット読み出し
#ifdef  NVRAM_CONFIG_CONST_ADDRESS
    offset = NVRAM_CONFIG_DATA_ADDRESS_DUMMY;
#else
    NVRAM_ReadDataBytes(NVRAM_CONFIG_DATA_OFFSET_ADDRESS, NVRAM_CONFIG_DATA_OFFSET_SIZE,
                        (u8 *)(&offset));
    offset <<= NVRAM_CONFIG_DATA_OFFSET_SHIFT;
#endif

    // ミラーされた２つのデータを読み出し
    NVRAM_ReadDataBytes((u32)offset, sizeof(NVRAMConfig), (u8 *)(&temp[0]));
    NVRAM_ReadDataBytes((u32)(offset + SPI_NVRAM_PAGE_SIZE), sizeof(NVRAMConfig), (u8 *)(&temp[1]));

    // ２つの内どちらを使うか判断
    check = CheckCorrectNCD(temp);

    if (check)
    {
        // 共有領域にストア
        MI_CpuCopy32(&temp[check - 1], p, sizeof(NVRAMConfig));
    }
    else
    {
        // 共有領域をクリア
        MI_CpuClear32(p, sizeof(NVRAMConfig));
    }

    // 無線MACアドレスをユーザー情報の後ろに展開
    {
        u8      src[6];
        u8      dst[6];
        s32     i;

        // NVRAMからMACアドレスを読み出し
        NVRAM_ReadDataBytes(NVRAM_CONFIG_MACADDRESS_ADDRESS, 6, src);
        // WMライブラリに合わせて2バイトごとにスワップ
        for (i = 0; i < 6; i++)
        {
            dst[i] = src[i + 1 - (i % 2) * 2];
        }

        // 展開先アドレスを計算
        p = (u8 *)((u32)p + ((sizeof(NVRAMConfig) + 3) & ~0x00000003));
        // 共有領域に展開
        MI_CpuCopy8(dst, p, 6);
    }
#ifdef  WM_PRECALC_ALLOWEDCHANNEL
    // 使用可能チャンネルから使用許可チャンネルを計算
    {
        u16     enableChannel;
        u16     allowedChannel;

        // 使用可能チャンネルを読み出し
        NVRAM_ReadDataBytes(NVRAM_CONFIG_ENABLECHANNEL_ADDRESS, 2, (u8 *)(&enableChannel));
        // 使用許可チャンネルを計算
        allowedChannel = WMSP_GetAllowedChannel((u16)(enableChannel >> 1));
        // 展開先アドレスを計算(MACアドレスの後ろの2バイト)
        p = (u8 *)((u32)p + 6);
        // 共有領域に展開
        *((u16 *)p) = allowedChannel;
    }
#endif
}

/*---------------------------------------------------------------------------*
  Name:         CheckCorrectNCD

  Description:  ミラーリングされているユーザー情報のどちらを使うべきか判定する。

  Arguments:    nvdsp   - 比較するコンフィグデータ２つの配列。

  Returns:      s32     - 0: 両方不適切。
                          1: 配列[ 0 ]が適切。
                          2: 配列[ 1 ]が適切。
 *---------------------------------------------------------------------------*/
static s32 CheckCorrectNCD(NVRAMConfig *ncdsp)
{
    u16     i;
    u16     calc_crc;
    s32     crc_flag = 0;
    u16     saveCount;

    // 各ミラーデータのCRC & saveCount正当性チェック
    for (i = 0; i < 2; i++)
    {
        calc_crc = GetCRC16(0xffff, (void *)(&ncdsp[i].ncd), sizeof(NVRAMConfigData));

        if ((ncdsp[i].crc16 == calc_crc) && (ncdsp[i].saveCount < NVRAM_CONFIG_SAVE_COUNT_MAX))
        {
            // CRCが正しく、saveCount値が0x80以下のデータを正当と判断。
            crc_flag |= (1 << i);
        }
    }

    // 正当なデータのうちどのデータが有効かを判定する。
    switch (crc_flag)
    {
    case 1:
    case 2:
        // 片方のCRCだけ正常
        return crc_flag;

    case 3:
        // 両方ともCRCが正しければどちらが最新のデータか判断する。
        saveCount = (u8)((ncdsp[0].saveCount + 1) & NVRAM_CONFIG_SAVE_COUNT_MASK);
        if (saveCount == ncdsp[1].saveCount)
        {
            return 2;
        }
        return 1;
    }

    // 両方ともCRCが不正
    return 0;
}


/*---------------------------------------------------------------------------*
  Name:         GetCRC16

  Description:  16ビットCRCを算出する。

  Arguments:    start - CRC計算開始時の値を指定。
                datap - データへのポインタ。
                size  - 計算するデータのサイズ。

  Returns:      u16   - CRC計算結果。
 *---------------------------------------------------------------------------*/
static u16 GetCRC16(u16 start, u16 *datap, u32 size)
{
    u16    *dataEndp = (u16 *)(datap + (size / 2));
    u16     data16;
    u16     r1;
    u16     crc16 = start;
    u32     shift = 0;

    while (datap < dataEndp)
    {
        if (!shift)
        {
            data16 = *datap;
        }

        // 4bit単位
        r1 = crc16_table[crc16 & 0xf];
        crc16 = (u16)(crc16 >> 4);
        crc16 = (u16)(crc16 ^ r1 ^ crc16_table[(data16 >> shift) & 0xf]);

        shift += 4;
        if (shift >= 0x10)
        {
            shift = 0;
            datap++;
        }
    }
    return crc16;
}
