/*---------------------------------------------------------------------------* Project: NET demo File: sostartup.c Copyright 2007 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: sostartup.c,v $ Revision 1.4 2007/10/26 08:06:20 seiki_masashi Support for REXDEMOGetAnyMixedPadTrigger. Revision 1.3 2007/09/07 02:25:12 seiki_masashi Separated the SafeSOFinish function. Revision 1.2 2007/09/06 13:29:46 seiki_masashi Cleaned up comments. Revision 1.1 2007/09/06 12:58:02 seiki_masashi Added a demo that runs SOStartup and SOCleanup in a different thread. $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include #include #include /* for memset() */ #include "rexdemo/netconfig.h" #include "rexdemo/graphic.h" #include "rexdemo/demokpad.h" /*---------------------------------------------------------------------------* Constant Definitions *---------------------------------------------------------------------------*/ /* heap size for the SO library*/ #define SOCKET_HEAP_SIZE (1024*128) /* priority of the network thread*/ /* the priority must be higher than that of the main thread*/ #define NETWORK_THREAD_PRIORITY 14 /* stack size of the network thread*/ #define NETWORK_THREAD_STACK_SIZE 4096 /* number of steps in the network thread's command queue*/ /* This implementation uses mutual exclusion for normal Startup and Cleanup commands. Since Cleanup at completion is the only request made in parallel, the number of steps in the queue is set to 2.*/ /* */ /* */ #define NETWORK_COMMAND_QUEUE_SIZE 2 /* number of steps in the event notification queue from the network thread*/ #define NETWORK_EVENT_QUEUE_SIZE 2 /* upper limit on network termination retries*/ /* a sufficiently large value is necessary because it is used to fix a blatantly abnormal state*/ #define NETWORK_TERMINATION_RETRY_LIMIT 120 /* wait time (ms) when re-attempting network termination*/ #define NETWORK_TERMINATION_RETRY_WAIT 500 /* Network thread commands*/ enum { NETWORK_COMMAND_KILL_THREAD = 0, NETWORK_COMMAND_STARTUP, NETWORK_COMMAND_CLEANUP }; /* Events for which the network thread will send notifications*/ enum { NETWORK_EVENT_NONE = 0, NETWORK_EVENT_STARTUP, NETWORK_EVENT_CLEANUP /* negative values directly indicate NET_ECODE_**/ }; /* Main thread states*/ enum { STATE_OFFLINE = 0, STATE_SOSTARTUP_IN_PROGRESS, STATE_ONLINE, STATE_SOCLEANUP_IN_PROGRESS, STATE_TERMINATING, STATE_TERMINATED, STATE_ERROR }; /*---------------------------------------------------------------------------* internal variable definitions *---------------------------------------------------------------------------*/ /* heap handle for the SO library*/ static MEMHeapHandle heapHandleSocket = NULL; /* Network thread*/ static OSThread NetworkThread; static u8 NetworkThreadStack[NETWORK_THREAD_STACK_SIZE] ATTRIBUTE_ALIGN(32); /* message queue for communicating with the network thread*/ static OSMessageQueue NetworkCommandQueue; static OSMessage NetworkCommandQueueMessages[NETWORK_COMMAND_QUEUE_SIZE]; static OSMessageQueue NetworkEventQueue; static OSMessage NetworkEventQueueMessages[NETWORK_EVENT_QUEUE_SIZE]; /*---------------------------------------------------------------------------* Function Prototype *---------------------------------------------------------------------------*/ static void InitNetwork( void ); static void TerminateNetwork( void ); static void SafeSOFinish( void ); static s32 ReceiveNetworkEvent( void ); static void* AllocForSocket( u32 name, s32 size ); static void FreeForSocket( u32 name, void* ptr, s32 size ); static BOOL CreateHeapForSocket( u32 size ); static void DestroyHeapForSocket( void ); static const char* StateToString( u32 state ); static void SendCommand( u32 command ); static void* NetworkHandler( void* param ); static void SendNetworkEvent( s32 event ); /*===========================================================================*/ #define REPORT_STATE( state ) REXDEMOReportEx( REXDEMO_COLOR_PURPLE, "state -> %s\n", StateToString(state) ) int main(void) { s32 result = 0; u32 state; /* initialization for graphics (et al.)*/ REXDEMOKPadInit(); REXDEMOInitScreen( FALSE ); REXDEMOUsage( "Push (A) to startup/cleanup socket.\n" "Push (B) to terminate and restart.\n\n" ); restart: REXDEMOReport( "Initialize network features...\n" ); InitNetwork(); state = STATE_OFFLINE; REPORT_STATE( state ); /* Main loop */ /* The main loop operates at 60 frames (for NTSC) due to the relocation of blocking functions, such as SOStartup and SOCleanup, to a separate thread */ while ( TRUE ) { s32 event; REXDEMOKPadRead(); /*************** Input-driven state transitions ****************/ /* (A) button */ /* Call the SOStartup function if there is no network connection and the SOCleanup function if there is a network connection. */ /* */ /* Do nothing during a state transition. */ if ( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_A | (PAD_BUTTON_A << 16)) ) { switch ( state ) { case STATE_OFFLINE: case STATE_ERROR: /* Begin network connection processing in a separate thread*/ state = STATE_SOSTARTUP_IN_PROGRESS; REPORT_STATE( state ); /* send a start request*/ SendCommand( NETWORK_COMMAND_STARTUP ); break; case STATE_ONLINE: /* Begin network termination processing in a separate thread*/ state = STATE_SOCLEANUP_IN_PROGRESS; REPORT_STATE( state ); /* send a termination request*/ SendCommand( NETWORK_COMMAND_CLEANUP ); break; case STATE_SOSTARTUP_IN_PROGRESS: case STATE_SOCLEANUP_IN_PROGRESS: /* in the middle of a state transition, so do nothing*/ break; case STATE_TERMINATING: case STATE_TERMINATED: /* in the middle of termination processing, so do nothing*/ break; default: REXDEMOError( "Unknown state: %d\n", state ); } } /* (B) button*/ /* Immediately exit the main loop if there is no network connection. Call the SOCleanup function if there is a network connection. */ /* */ /* Set up disconnection processing, even during network connection processing */ if ( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_B | (PAD_BUTTON_B << 16)) ) { switch ( state ) { case STATE_OFFLINE: case STATE_SOCLEANUP_IN_PROGRESS: case STATE_ERROR: /* exit the main loop*/ state = STATE_TERMINATED; REPORT_STATE( state ); break; case STATE_SOSTARTUP_IN_PROGRESS: case STATE_ONLINE: /* Begin network termination processing in a separate thread*/ state = STATE_TERMINATING; REPORT_STATE( state ); /* send a termination request*/ SendCommand( NETWORK_COMMAND_CLEANUP ); break; case STATE_TERMINATING: case STATE_TERMINATED: /* in the middle of termination processing, so do nothing*/ break; default: REXDEMOError( "Unknown state: %d\n", state ); } } /* Home button*/ if ( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_HOME | (PAD_BUTTON_START << 16)) ) { /* return to the menu*/ SafeSOFinish(); /* termination processing for the SO library*/ OSReturnToMenu(); } /*************** Event-driven state transitions ****************/ event = ReceiveNetworkEvent(); switch ( event ) { case NETWORK_EVENT_NONE: break; case NETWORK_EVENT_STARTUP: REXDEMOReport( "Received STARTUP event.\n" ); if ( state == STATE_SOSTARTUP_IN_PROGRESS ) { state = STATE_ONLINE; REPORT_STATE( state ); } break; case NETWORK_EVENT_CLEANUP: REXDEMOReport( "Received CLEANUP event.\n" ); if ( state == STATE_SOCLEANUP_IN_PROGRESS ) { state = STATE_OFFLINE; REPORT_STATE( state ); } else if ( state == STATE_TERMINATING ) { state = STATE_TERMINATED; REPORT_STATE( state ); } break; default: if ( event < 0 ) { /* negative values directly indicate NET_ECODE_**/ state = STATE_ERROR; REPORT_STATE( state ); /**********************************/ /** Error code display to the user **/ /**********************************/ REXDEMOError("Network Error Code is %d\n", -event); } else { /* Unknown event */ REXDEMOError("Unknown event: %d\n", event); } } /*************** Screen display ****************/ REXDEMOBeginRender(); REXDEMOSetTextColor( REXDEMO_COLOR_RED ); REXDEMOPrintf( 464, 40, 0, "<>" ); REXDEMOSetTextColor( REXDEMO_COLOR_ORANGE ); REXDEMOPrintf( 400, 60, 0, "%s", StateToString( state ) ); REXDEMOSetTextColor( REXDEMO_COLOR_GRAY ); /* counter color*/ REXDEMOWaitRetrace(); if ( state == STATE_TERMINATED ) { /* Exit the main loop when Cleanup has finished*/ break; } } TerminateNetwork(); REXDEMOReport( "All network features are terminated.\n" ); REXDEMOWaitRetrace(); goto restart; return 0; } /* Utility functions for displaying messages*/ const char* StateToString( u32 state ) { const char* strings[] = { "OFFLINE", "SOSTARTUP_IN_PROGRESS", "ONLINE", "SOCLEANUP_IN_PROGRESS", "TERMINATING", "TERMINATED", "ERROR" }; if ( state < sizeof(strings)/sizeof(strings[0]) ) { return strings[state]; } else { return "N/A"; } } /*===========================================================================*/ /* Network initialization processing*/ void InitNetwork( void ) { s32 result; /* Create the heap to be given to the socket library */ if ( !CreateHeapForSocket( SOCKET_HEAP_SIZE ) ) { REXDEMOError("CreateHeapForSocket() failed.\n"); OSHalt("failed"); } /* Initialize the socket library by specifying the SO allocator*/ { /* the SOInit argument can be an auto variable*/ SOLibraryConfig libConfig = { AllocForSocket, FreeForSocket }; result = SOInit( &libConfig ); if ( result < SO_SUCCESS ) { REXDEMOError("SOInit() failed.(%d)\n", result); OSHalt("failed"); } } /* Create a message queue for issuing commands to the network thread*/ OSInitMessageQueue( &NetworkCommandQueue, NetworkCommandQueueMessages, NETWORK_COMMAND_QUEUE_SIZE ); /* Create a message queue for receiving events from the network thread*/ OSInitMessageQueue( &NetworkEventQueue, NetworkEventQueueMessages, NETWORK_EVENT_QUEUE_SIZE ); /* Create a network thread*/ (void)OSCreateThread( &NetworkThread, NetworkHandler, 0, NetworkThreadStack + sizeof(NetworkThreadStack), sizeof(NetworkThreadStack), NETWORK_THREAD_PRIORITY, 0); /* start the network thread*/ (void)OSResumeThread(&NetworkThread); } /* termination processing for network functions*/ void TerminateNetwork( void ) { /* send a termination request to the network thread*/ SendCommand( NETWORK_COMMAND_KILL_THREAD ); (void) OSJoinThread( &NetworkThread, NULL ); /* Deallocate the socket library resource */ SafeSOFinish(); /* deallocate the heap given to the socket library*/ DestroyHeapForSocket(); } /* the SO library will safely terminate in any state*/ void SafeSOFinish( void ) { s32 iRetry; s32 result; for ( iRetry = NETWORK_TERMINATION_RETRY_LIMIT; iRetry > 0; iRetry-- ) { result = SOFinish(); if ( result == SO_EBUSY || result == SO_EAGAIN || result == SO_EINPROGRESS ) { s32 r; REXDEMOError("SOFinish() failed.(%d)\n", result); /* This is a state in which SOFinish cannot be called. Try SOCleanup.*/ REXDEMOReport( "Try SOCleanup()...\n" ); r = SOCleanup(); if ( r < SO_SUCCESS ) { REXDEMOError("SOCleanup() failed.(%d)\n", r); } /* wait for other processes to release their network-related resources*/ OSSleepMilliseconds( NETWORK_TERMINATION_RETRY_WAIT ); } else { break; } } if ( result == SO_EALREADY ) { /* either SOFinish has already been called, or SOInit was not called in the first place*/ REXDEMOError("SOFinish() already done. (%d)\n", result); } else if ( result < SO_SUCCESS ) { /* an unexpected error in SOFinish that should not occur*/ /* error processing is required*/ REXDEMOError("SOFinish() failed.(%d)\n", result); OSHalt("failed"); } if ( iRetry <= 0 ) { /* The retry limit was reached*/ /* Error processing is required because network termination could not be carried out properly.*/ REXDEMOError("Too many network termination retries; give up to call SOFinish().\n"); OSHalt("failed"); } } /* function for sending commands to the network thread (blocking)*/ void SendCommand( u32 command ) { (void)OSSendMessage( &NetworkCommandQueue, (OSMessage)command, OS_MESSAGE_BLOCK ); } /*===========================================================================*/ /* thread for SOStartup and SOCleanup processing*/ void* NetworkHandler( void* param ) { #pragma unused( param ) BOOL fExit = FALSE; while ( fExit == FALSE ) { OSMessage msg; u32 command; s32 result; (void)OSReceiveMessage( &NetworkCommandQueue, &msg, OS_MESSAGE_BLOCK ); command = (u32)msg; switch ( command ) { case NETWORK_COMMAND_KILL_THREAD: /* make the thread terminate*/ fExit = TRUE; break; case NETWORK_COMMAND_STARTUP: /* Start the network feature of the socket library. */ /* The SOCleanup function may block for more than a few seconds.*/ result = SOStartup(); if ( result >= SO_SUCCESS ) { /* SOStartup succeeded*/ SendNetworkEvent( NETWORK_EVENT_STARTUP ); } else if ( result == SO_EBUSY ) { /* Processing is not currently possible, for reasons such as: SOCleanup is running.*/ REXDEMOError("Network stack is busy. (%d)\n", result); } else if ( result == SO_EALREADY ) { /* SOStartup has already run*/ REXDEMOError("SOStartup() already done. (%d)\n", result); } else { s32 errcode; /* other error states*/ REXDEMOError("SOStartup() failed.(%d)\n", result); /* For display purposes, get the error code and notify the main thread.*/ errcode = NETGetStartupErrorCode(result); SendNetworkEvent( errcode ); } break; case NETWORK_COMMAND_CLEANUP: /* Stop the network function of the socket library */ /* The SOCleanup function may block for more than a few seconds.*/ result = SOCleanup(); if ( result >= SO_SUCCESS ) { /* SOCleanup success*/ SendNetworkEvent( NETWORK_EVENT_CLEANUP ); } else if ( result == SO_EBUSY ) { /* Processing is not currently possible, for reasons such as: SOStartup is running.*/ REXDEMOError("Network stack is busy. (%d)\n", result); } else if ( result == SO_EALREADY ) { /* SOCleanup has already run*/ REXDEMOError("SOCleanup() already done. (%d)\n", result); } else { REXDEMOError("SOCleanup() failed.(%d)\n", result); /* Proceed as is because even if there is a failure, nothing can be done,*/ SendNetworkEvent( NETWORK_EVENT_CLEANUP ); } break; default: /* unknown command*/ REXDEMOError("Unknown command: %d\n", command ); } } return NULL; } /* Function for sending network events (blocking)*/ void SendNetworkEvent( s32 event ) { (void)OSSendMessage( &NetworkEventQueue, (OSMessage)event, OS_MESSAGE_BLOCK ); } /*===========================================================================*/ /* Function for receiving network events (non-blocking)*/ s32 ReceiveNetworkEvent( void ) { OSMessage msg; if ( OSReceiveMessage( &NetworkEventQueue, &msg, OS_MESSAGE_NOBLOCK ) ) { return (s32)msg; } else { /* no events have occurred*/ return NETWORK_EVENT_NONE; } } /*===========================================================================*/ /* Memory allocation function for the SO library*/ void* AllocForSocket( u32 name, s32 size ) { #pragma unused( name ) void* ptr; if (0 < size) { /* This operation is thread-safe because the MEM_HEAP_OPT_THREAD_SAFE option was specified in MEMCreateExpHeapEx(). */ /* */ ptr = MEMAllocFromExpHeapEx( heapHandleSocket, (u32) size, 32 ); return ptr; } else { return NULL; } } /* memory deallocation function for the SO library*/ void FreeForSocket( u32 name, void* ptr, s32 size ) { #pragma unused( name ) if (ptr && 0 < size) { /* This operation is thread-safe because the MEM_HEAP_OPT_THREAD_SAFE option was specified in MEMCreateExpHeapEx(). */ /* */ MEMFreeToExpHeap( heapHandleSocket, ptr ); } } /* create the heap region for the SO library*/ BOOL CreateHeapForSocket( u32 size ) { void* arenaLo; void* arenaHi; /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ /* !!! The SO library heap MUST be allocated from MEM2!!! */ /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ arenaLo = OSGetMEM2ArenaLo(); arenaHi = OSGetMEM2ArenaHi(); if ((u32) arenaHi - (u32) arenaLo < size) { return FALSE; } heapHandleSocket = MEMCreateExpHeapEx( arenaLo, size, MEM_HEAP_OPT_THREAD_SAFE ); if (heapHandleSocket == MEM_HEAP_INVALID_HANDLE) { OSReport("MEMCreateExpHeapEx failed.\n"); return FALSE; } OSSetMEM2ArenaLo((u8*)arenaLo + size); return TRUE; } /* destroy the heap region for the SO library*/ void DestroyHeapForSocket( void ) { s32 heapSize; void* heapPtr; if ( heapHandleSocket == NULL ) return; heapSize = MEMGetHeapTotalSize(heapHandleSocket); heapPtr = MEMDestroyExpHeap(heapHandleSocket); if ( heapPtr != NULL ) { if ( (u32)OSGetMEM2ArenaLo() == (u32)heapPtr + heapSize ) { OSSetMEM2ArenaLo(heapPtr); } } heapHandleSocket = NULL; return; } /*---------------------------------------------------------------------------* End of file *---------------------------------------------------------------------------*/