nnsched.c 11 KB
/*============================================================================

		NINTENDO64 TECHNICAL SUPPORT CENTER 
		
		    NINTENDO64 SAMPLE PROGRAM 1

		Copyright (C) 1997, NINTENDO Co,Ltd.

1997/01/23
    ・F3DEX Ver1.20に追加されたgSPLoadUcodeに対応するため
      OS_TASK_LOADABLEフラグを追加
1997/02/03
    ・パフォーマンスチェックでNN_SC_GTASK_NUM,NN_SC_AUTASK_NUMより大きい
    値になった場合、ハングする不具合の修正
1997/12/10
    ・ワーニングメッセージを出ないように修正

============================================================================*/

#include "nnsched.h"

/* スレッド用のスタックの確保 */
u64 nnScStack[NN_SC_STACKSIZE/8];
u64 nnScAudioStack[NN_SC_STACKSIZE/8];
u64 nnScGraphicsStack[NN_SC_STACKSIZE/8];
u32 framecont = 0;

#ifdef NN_SC_PERF
static u32       nnsc_perf_index = 0; /* 内部で使用しているバッファ位置 */
static u32       nnsc_perf_flag = 0;
static NNScPerf  nnsc_perf[NN_SC_PERF_NUM];        /* 計測データ */
static NNScPerf* nnsc_perf_inptr;     /* 内部で使用するポインタ */
NNScPerf* nnsc_perf_ptr;              /* 外部から参照するときのポインタ */
#endif  /* NN_SC_PERF */

/************************************************************
  nnScCreateScheduler() -- スケジューラの作成
************************************************************/
void nnScCreateScheduler(NNSched *sc, u8 videoMode, u8 numFields)
{

  /* 変数の初期化 */
  sc->curGraphicsTask = 0;
  sc->curAudioTask    = 0;
  sc->graphicsTaskSuspended = 0;
  sc->clientList      = 0;
  sc->firstTime       = 1; 
  sc->retraceMsg      = NN_SC_RETRACE_MSG;
  sc->prenmiMsg       = NN_SC_PRE_NMI_MSG;

  /* メッセージキューの作成 */
  osCreateMesgQueue(&sc->retraceMQ, sc->retraceMsgBuf, NN_SC_MAX_MESGS);
  osCreateMesgQueue(&sc->rspMQ, sc->rspMsgBuf, NN_SC_MAX_MESGS);
  osCreateMesgQueue(&sc->rdpMQ, sc->rdpMsgBuf, NN_SC_MAX_MESGS);
  osCreateMesgQueue(&sc->graphicsRequestMQ, sc->graphicsRequestBuf, 
NN_SC_MAX_MESGS);
  osCreateMesgQueue(&sc->audioRequestMQ, sc->audioRequestBuf, 
NN_SC_MAX_MESGS);
  osCreateMesgQueue(&sc->waitMQ, sc->waitMsgBuf, NN_SC_MAX_MESGS);

  /* ビデオモードの設定 */
  osCreateViManager(OS_PRIORITY_VIMGR);    
  osViSetMode(&osViModeTable[videoMode]);
  osViBlack(TRUE);

  /* イベントハンドラの登録 */
  osViSetEvent(&sc->retraceMQ, (OSMesg)VIDEO_MSG, numFields);    
  osSetEventMesg(OS_EVENT_SP, &sc->rspMQ, (OSMesg)RSP_DONE_MSG);
  osSetEventMesg(OS_EVENT_DP, &sc->rdpMQ, (OSMesg)RDP_DONE_MSG);
  osSetEventMesg(OS_EVENT_PRENMI, &sc->retraceMQ, (OSMesg)PRE_NMI_MSG);   


  /* スケジューラスレッドの起動 */
  osCreateThread(&sc->schedulerThread, 19, (void(*)(void*))nnScEventHandler,
		 (void *)sc, nnScStack+NN_SC_STACKSIZE/sizeof(u64),
		 NN_SC_PRI);
  osStartThread(&sc->schedulerThread);

  osCreateThread(&sc->audioThread, 18, (void(*)(void *))nnScExecuteAudio,
		 (void *)sc, nnScAudioStack+NN_SC_STACKSIZE/sizeof(u64),
		 NN_SC_AUDIO_PRI);
  osStartThread(&sc->audioThread);

  osCreateThread(&sc->graphicsThread, 17,(void(*)(void*))nnScExecuteGraphics,
		 (void *)sc, nnScGraphicsStack+NN_SC_STACKSIZE/sizeof(u64),
		 NN_SC_GRAPHICS_PRI);
  osStartThread(&sc->graphicsThread);

}

/************************************************************
  nnScGetAudioMQ() -- オーディオメッセージキューの取得
************************************************************/
OSMesgQueue *nnScGetAudioMQ(NNSched *sc)
{
  return( &sc->audioRequestMQ );
}

/************************************************************
  nnScGetGfxMQ() -- オーディオメッセージキューの取得
************************************************************/
OSMesgQueue *nnScGetGfxMQ(NNSched *sc)
{
  return( &sc->graphicsRequestMQ );
}

/************************************************************
  nnScEventHandler() -- システムイベントの処理
************************************************************/
void nnScEventHandler(NNSched *sc)
{
  OSMesg msg = (OSMesg)0;

  while(1) {
    /* イベントの取得 */
    osRecvMesg(&sc->retraceMQ, &msg, OS_MESG_BLOCK);
    framecont++;


    switch ( (int)msg ) {
    case VIDEO_MSG:		/* リトレース信号の処理 */
      nnScEventBroadcast( sc, &sc->retraceMsg );
#ifdef NN_SC_PERF

      if( nnsc_perf_flag  == 0){
	/* グラフィックタスクがすべて終了するまで初期化しないようにする */
	nnsc_perf_flag++;
	
	/* 計測したバッファのポインタを外部から参照できるようにする */
	nnsc_perf_ptr = &nnsc_perf[nnsc_perf_index];
	
	nnsc_perf_index++;  /* バッファの切り替え */
	nnsc_perf_index %= NN_SC_PERF_NUM;
	
	nnsc_perf_inptr = &nnsc_perf[nnsc_perf_index];
	
	nnsc_perf_inptr->autask_cnt =  0;   
	nnsc_perf_inptr->gtask_cnt = 0;
	
	/* リトレース時間の取得 */
	nnsc_perf_inptr->retrace_time = OS_CYCLES_TO_USEC(osGetTime());
	
      }
#endif /* NN_SC_PERF */

      break;
    case PRE_NMI_MSG:		/* NMI信号の処理 */
      nnScEventBroadcast( sc, &sc->prenmiMsg );
      break;
    }
  }
}

/************************************************************
  nnScAddClient() -- クライアントの登録
************************************************************/
void nnScAddClient(NNSched *sc, NNScClient *c, OSMesgQueue *msgQ)
{
  OSIntMask mask;

  mask = osSetIntMask(OS_IM_NONE);
  c->msgQ = msgQ;
  c->next = sc->clientList;
  sc->clientList = c;
  osSetIntMask(mask);
}

/************************************************************
  nnScRemoveClient() -- クライアントの抹消
************************************************************/
void nnScRemoveClient(NNSched *sc, NNScClient *c)
{
  NNScClient *client = sc->clientList; 
  NNScClient *prev   = 0;
  OSIntMask  mask;

  mask = osSetIntMask(OS_IM_NONE);
    
  while (client != 0) {
    if (client == c) {
      if(prev)
	prev->next = c->next;
      else
	sc->clientList = c->next;
      break;
    }
    prev   = client;
    client = client->next;
  }
  osSetIntMask(mask);
}

/************************************************************
 nnScEventBroadcast() -- クライアントにメッセージを転送
************************************************************/
void nnScEventBroadcast(NNSched *sc, NNScMsg *msg)
{
  NNScClient *client;
  
  /* リトレースメッセージが必要なクライアントに通知 */
  for (client = sc->clientList; client != 0; client = client->next) {
    osSendMesg(client->msgQ, (OSMesg *)msg, OS_MESG_NOBLOCK);
  }
}

/************************************************************
  nnScExecuteAudio() -- オーディオタスクの実行。
************************************************************/
void nnScExecuteAudio(NNSched *sc)
{
  OSMesg msg = (OSMesg)0;
  NNScTask *task = (NNScTask *)0;
  NNScTask *gfxTask = (NNScTask *)0;
  u32 yieldFlag = 0;

#ifdef NN_SC_PERF
  u32 task_cnt;

#endif /* NN_SC_PERF */

  while(1) {

    /* オーディオの実行要求を待つ。*/
    osRecvMesg(&sc->audioRequestMQ, (OSMesg *)&task, OS_MESG_BLOCK);
    osWritebackDCacheAll();	/* キャッシュのフラッシュ */


    /* 現在のRSPステータスの検査。*/
    yieldFlag = 0;
    gfxTask = sc->curGraphicsTask;
    if( gfxTask ) {

      /* グラフィックスタスクの終了(yield)を待つ */
      osSpTaskYield();		/* タスクのyield */
      osRecvMesg(&sc->rspMQ, &msg, OS_MESG_BLOCK);

      /* 実際にタスクがyieldされたかを検査 */
      if (osSpTaskYielded(&gfxTask->list)){
	yieldFlag =1;
      } else {
	yieldFlag =2;
      }
    }

#ifdef NN_SC_PERF
    if(nnsc_perf_inptr->autask_cnt < NN_SC_AUTASK_NUM){
      task_cnt = nnsc_perf_inptr->autask_cnt;
      nnsc_perf_inptr->autask_stime[task_cnt] =
	OS_CYCLES_TO_USEC(osGetTime()) - nnsc_perf_inptr->retrace_time;
    }
#endif /* NN_SC_PERF */


    sc->curAudioTask = task;
    osSpTaskStart(&task->list);        /* タスクの実行。*/

    /* RSPタスクの終了を待つ。 */
    osRecvMesg(&sc->rspMQ, &msg, OS_MESG_BLOCK);
    sc->curAudioTask = (NNScTask *)0;

#ifdef NN_SC_PERF
    if(nnsc_perf_inptr->autask_cnt < NN_SC_AUTASK_NUM){
      nnsc_perf_inptr->autask_etime[task_cnt] =
	OS_CYCLES_TO_USEC(osGetTime()) - nnsc_perf_inptr->retrace_time;
      nnsc_perf_inptr->autask_cnt++;
    }
#endif /* NN_SC_PERF */

    if( sc->graphicsTaskSuspended )
      osSendMesg( &sc->waitMQ, &msg, OS_MESG_BLOCK );

    /* `yield'したグラフィックスタスクの再開 */
    if( yieldFlag == 1 ) {
      osSpTaskStart(&gfxTask->list);    
    } else if ( yieldFlag == 2 ) {
      osSendMesg(&sc->rspMQ, &msg, OS_MESG_BLOCK);
    }

    /* オーディオタスクを起動したスレッドにタスクの終了を通知 */
    osSendMesg(task->msgQ, task->msg, OS_MESG_BLOCK);
  }
}

/************************************************************
 * nnScExecuteGraphics() -- グラフィックスタスクの実行。
************************************************************/
void nnScExecuteGraphics(NNSched *sc)
{
  OSMesg msg = (OSMesg)0;
  NNScTask *task;
#ifdef NN_SC_PERF
  u32 task_cnt;

#endif /* NN_SC_PERF */

  while(1) {

    /* グラフィックタスクの実行要求を待つ。*/
    osRecvMesg(&sc->graphicsRequestMQ, (OSMesg *)&task, OS_MESG_BLOCK);

    /* フレームバッファが利用可能になるのを待つ。 */
    nnScWaitTaskReady(sc, task);

    if( sc->curAudioTask ) {

      sc->graphicsTaskSuspended = task;
      osRecvMesg( &sc->waitMQ, &msg, OS_MESG_BLOCK );
      sc->graphicsTaskSuspended = (NNScTask *)0;
    }

#ifdef NN_SC_PERF
    if(nnsc_perf_inptr->gtask_cnt < NN_SC_GTASK_NUM){
      task_cnt = nnsc_perf_inptr->gtask_cnt;
      nnsc_perf_inptr->gtask_stime[task_cnt] =
	OS_CYCLES_TO_USEC(osGetTime()) - nnsc_perf_inptr->retrace_time;
    }
#endif /* NN_SC_PERF */    

    sc->curGraphicsTask = task;
    osSpTaskStart(&task->list);        /* タスクの実行。*/

    /* RSPタスクの終了を待つ。 */
    osRecvMesg(&sc->rspMQ, &msg, OS_MESG_BLOCK);
    sc->curGraphicsTask = (NNScTask *)0;
#ifdef NN_SC_PERF
    if(nnsc_perf_inptr->gtask_cnt < NN_SC_GTASK_NUM){
      nnsc_perf_inptr->rsp_etime[task_cnt] =
	OS_CYCLES_TO_USEC(osGetTime()) - nnsc_perf_inptr->retrace_time;
    }
#endif /* NN_SC_PERF */

    /* RDPタスクの終了を待つ。 */
    osRecvMesg(&sc->rdpMQ, &msg, OS_MESG_BLOCK);

#ifdef NN_SC_PERF
    if(nnsc_perf_inptr->gtask_cnt < NN_SC_GTASK_NUM){
      nnsc_perf_inptr->rdp_etime[task_cnt] =
	OS_CYCLES_TO_USEC(osGetTime()) - nnsc_perf_inptr->retrace_time;
      nnsc_perf_inptr->gtask_cnt++;
    }
#endif /* NN_SC_PERF */

    /* 初回だけビデオのブラックアウトを無効にする。 */
    if (sc->firstTime) {
      osViBlack(FALSE);
      sc->firstTime = 0;
    }

    /* 次に表示されるフレームバッファの指定 */
    if ( task->flags & NN_SC_SWAPBUFFER ){
      osViSwapBuffer(task->framebuffer);
#ifdef NN_SC_PERF
      nnsc_perf_flag = 0;
#endif /* NN_SC_PERF */
    }
    /* グラフィックスタスクを起動したスレッドにタスクの終了を通知 */
    osSendMesg(task->msgQ, task->msg, OS_MESG_BLOCK);
  }
}

/************************************************************
  nnScWaitTaskReady() -- フレームバッファが利用可能になるのを待つ。
************************************************************/
void nnScWaitTaskReady(NNSched *sc, NNScTask *task)
{
  OSMesg msg = (OSMesg)0;
  NNScClient client;
  void *fb = task->framebuffer;

  /* フレームバッファが空いていなければ次のリトレースまで待つ。 */
  while( osViGetCurrentFramebuffer() == fb || osViGetNextFramebuffer() == 
fb ) {
    nnScAddClient( sc, &client, &sc->waitMQ );  
    osRecvMesg( &sc->waitMQ, &msg, OS_MESG_BLOCK );
    nnScRemoveClient( sc, &client );
  }
}