/*---------------------------------------------------------------------------* Project: Horizon File: osl_IpcIpcDispatcher.h Copyright (C)2009 Nintendo Co., Ltd. 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. $Rev: 13955 $ *---------------------------------------------------------------------------*/ #ifndef NN_NET_OSL_OSL_IPCDISPATCHER_H_ #define NN_NET_OSL_OSL_IPCDISPATCHER_H_ #include #include #include #include #include #include #include #include namespace nn { namespace net { namespace osl { class IpcDispatcherBase : public os::ipc::Port { public: virtual Result Launch() = 0; }; class IpcDispatcherExecutor { public: static const size_t DISPATCHERS_MAX = 8; static const size_t WAITOBJECTS_MAX = 8; IpcDispatcherExecutor(IpcDispatcherBase* ppDispatchers[], s32 countDispatchers) : m_countDispatchers(countDispatchers) { NN_MIN_ASSERT(countDispatchers, 0); NN_MAX_ASSERT(countDispatchers, DISPATCHERS_MAX - 1); for (s32 i = 0; i < m_countDispatchers; ++i) { NN_POINTER_ASSERT(ppDispatchers[i]); m_ppDispatchers[i] = ppDispatchers[i]; } } IpcDispatcherExecutor(IpcDispatcherBase* pDispatcher) : m_countDispatchers(1) { NN_POINTER_ASSERT(pDispatcher); m_ppDispatchers[0] = pDispatcher; } s32 WaitAny(nn::os::WaitObject* ppObjects[], s32 countWaitObjects) { NN_MIN_ASSERT(countWaitObjects, 0); NN_MAX_ASSERT(countWaitObjects, WAITOBJECTS_MAX - 1); nn::os::WaitObject* ppObjectsToWait[DISPATCHERS_MAX + WAITOBJECTS_MAX]; for (s32 i = 0; i < m_countDispatchers; ++i) { NN_POINTER_ASSERT(m_ppDispatchers[i]); ppObjectsToWait[i] = m_ppDispatchers[i]; } for (s32 i = 0; i < countWaitObjects; ++i) { NN_POINTER_ASSERT(ppObjects[i]); ppObjectsToWait[m_countDispatchers + i] = ppObjects[i]; } s32 index; while(true) { index = nn::os::WaitObject::WaitAny(ppObjectsToWait, m_countDispatchers + countWaitObjects); if (index < m_countDispatchers) { Result result = m_ppDispatchers[index]->Launch(); if (result.IsFailure()) { NN_LOG_WARN("IPC dispatch failed.\n"); nn::dbg::PrintResult(result); } } else { return index - m_countDispatchers; } } } private: IpcDispatcherBase* m_ppDispatchers[DISPATCHERS_MAX]; s32 m_countDispatchers; s32 m_countWaitObjects; s32 m_indexNotification; }; template class IpcDispatcher : public IpcDispatcherBase { typedef IpcDispatcher Base; class Worker : public fnd::IntrusiveLinkedList::Item { public: Worker(IpcDispatcher* pDispatcher) : m_pDispatcher(pDispatcher) , m_bInitialized(false) { }; ~Worker() { if (m_bInitialized) { Finalize(); } } inline bool IsInitialized(void) const { return m_bInitialized; } Result TryInitialize(os::ipc::Port& port, s32 priority = os::DEFAULT_THREAD_PRIORITY) { NN_ASSERT(!m_bInitialized); Result result; result = port.TryAccept(&m_session, false); NN_UTIL_RETURN_IF_FAILED(result); result = m_thread.TryStart(SessionThread, this, m_stack, priority); if (result.IsFailure()) { m_session.Close(); return result; } m_bInitialized = true; return ResultSuccess(); } void Finalize(void) { NN_ASSERT(m_bInitialized); Wait(); m_thread.Finalize(); m_bInitialized = false; } void Wait(void) { m_thread.Join(); } static void* operator new(size_t size, nn::fnd::IAllocator& allocator) throw() { return allocator.Allocate(size, sizeof(u64)); } static void operator delete(void* p) throw() { NN_ASSERT(p && reinterpret_cast(p)->m_pDispatcher); reinterpret_cast(p)->m_pDispatcher->m_allocator.Free(p); } private: static void SessionThread(Worker* pWorker) { pWorker->SessionThreadImpl(); } void SessionThreadImpl(void) { ImplT* pImpl = reinterpret_cast(&m_implStorage); new (pImpl) ImplT(); { s32 resultIndex; Handle handles[] = { m_session.GetHandle() }; Result result = pSessionLoopFunction(&resultIndex, pImpl, handles, 1, 0); if (result.IsFailure() && result.GetDescription() != nn::os::DESCRIPTION_SESSION_CLOSED) { NN_UTIL_PANIC_IF_FAILED(result); } } pImpl->~ImplT(); m_session.Close(); m_pDispatcher->TakeBackFreeWorker(this); } typedef nn::os::StackBuffer StackT; StackT m_stack; u64 m_implStorage[sizeof(ImplT)/sizeof(u64) + 1]; nn::os::Thread m_thread; nn::os::ipc::Session m_session; IpcDispatcher* m_pDispatcher; bool m_bInitialized; u8 padding[7]; }; typedef fnd::IntrusiveLinkedList WorkerList; friend class Worker; public: IpcDispatcher(const char* pServiceName, nn::fnd::IAllocator& allocator, s32 maxThreads = 1, s32 priority = os::DEFAULT_THREAD_PRIORITY) : m_cs(nn::WithInitialize()) , m_allocator(allocator) , m_maxThreads(maxThreads) , m_priority(priority) , m_pServiceName(pServiceName) { Result result; Handle hPort; ConstructWorkerList(maxThreads); NN_LOG_INFO("Registering service: %s\n", pServiceName); result = nn::srv::RegisterService(&hPort, maxThreads, pServiceName); NN_UTIL_PANIC_IF_FAILED(result); WaitObject::SetHandle(hPort); } ~IpcDispatcher() { if (IsValid()) { Finalize(); } } void Finalize(void) { Result result; DestructWorkerList(); result = nn::srv::UnregisterService(m_pServiceName); NN_UTIL_PANIC_IF_FAILED(result); WaitObject::Close(); } virtual Result Launch(void) { NN_LOG_INFO("Accepting %s\n", m_pServiceName); Worker* pWorker = GetFreeWorker(); if (!pWorker) { NN_LOG_WARN("No free worker.\n"); nn::os::Thread::Sleep(nn::fnd::TimeSpan::FromMilliSeconds(1000)); return nn::MakeStatusResult(nn::Result::SUMMARY_OUT_OF_RESOURCE, nn::Result::MODULE_NN_OS, nn::Result::DESCRIPTION_OUT_OF_MEMORY); } Result result = pWorker->TryInitialize(*this, m_priority); if (result.IsFailure()) { TakeBackFreeWorker(pWorker); return result; } return ResultSuccess(); } private: Worker* GetFreeWorker(void) { os::CriticalSection::ScopedLock locker(m_cs); if (m_freeList.IsEmpty()) { return NULL; } Worker* pWorker = m_freeList.PopFront(); m_activeList.PushBack(pWorker); // If already used, then call Finalize once. if (pWorker->IsInitialized()) { pWorker->Finalize(); } return pWorker; } void TakeBackFreeWorker(Worker* pWorker) { os::CriticalSection::ScopedLock locker(m_cs); // In this context, it will deadlock when joined, so call Finalize when using it again. m_activeList.Erase(pWorker); m_freeList.PushBack(pWorker); } void ConstructWorkerList(s32 maxThreads) { NN_ASSERT(maxThreads > 0); for(s32 i = 0; i < maxThreads; ++i) { Worker* pWorker = new (m_allocator) Worker(this); if (pWorker == NULL) { NN_LOG_WARN("Failed to allocate memory for Woker.(%d/%d)", i, maxThreads); NN_ASSERT(pWorker); break; } m_freeList.PushBack(pWorker); } } void DestructWorkerList(void) { os::CriticalSection::ScopedLock locker(m_cs); while(!m_freeList.IsEmpty() || !m_activeList.IsEmpty()) { while(!m_freeList.IsEmpty()) { Worker* pWorker = m_freeList.PopFront(); delete pWorker; } if(!m_activeList.IsEmpty()) { Worker* pWorker = m_activeList.GetFront(); { m_cs.Leave(); pWorker->Wait(); m_cs.Enter(); } } } } nn::os::CriticalSection m_cs; nn::fnd::IAllocator& m_allocator; WorkerList m_freeList; WorkerList m_activeList; s32 m_maxThreads; s32 m_priority; const char* m_pServiceName; }; } // end of namespace osl } // end of namespace net } // end of namespace nn #endif // NN_NET_OSL_OSL_IPCDISPATCHER_H_