mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge branch 'threading' of https://github.com/bunnei/citra
Conflicts: src/core/hle/function_wrappers.h src/core/hle/service/gsp.cpp
This commit is contained in:
		
						commit
						004df76795
					
				
					 41 changed files with 1279 additions and 1235 deletions
				
			
		|  | @ -55,7 +55,7 @@ inline void Read(T &var, const u32 addr) { | |||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         ERROR_LOG(HLE, "unknown ConfigMem::Read%d @ 0x%08X", sizeof(var) * 8, addr); | ||||
|         ERROR_LOG(HLE, "unknown addr=0x%08X", addr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ s32 CallMRC(u32 instruction) { | |||
|         return GetThreadCommandBuffer(); | ||||
| 
 | ||||
|     default: | ||||
|         //DEBUG_LOG(OSHLE, "unknown MRC call 0x%08X", instruction);
 | ||||
|         DEBUG_LOG(OSHLE, "unknown MRC call 0x%08X", instruction); | ||||
|         break; | ||||
|     } | ||||
|     return -1; | ||||
|  |  | |||
|  | @ -1,19 +1,6 @@ | |||
| // Copyright (c) 2012- PPSSPP Project.
 | ||||
| 
 | ||||
| // This program is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, version 2.0 or later versions.
 | ||||
| 
 | ||||
| // This program is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License 2.0 for more details.
 | ||||
| 
 | ||||
| // A copy of the GPL 2.0 should have been included with the program.
 | ||||
| // If not, see http://www.gnu.org/licenses/
 | ||||
| 
 | ||||
| // Official git repository and contact information can be found at
 | ||||
| // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
 | ||||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
|  | @ -21,725 +8,107 @@ | |||
| #include "core/mem_map.h" | ||||
| #include "core/hle/hle.h" | ||||
| 
 | ||||
| // For easy parameter parsing and return value processing.
 | ||||
| namespace HLE { | ||||
| 
 | ||||
| //32bit wrappers
 | ||||
| template<void func()> void WrapV_V() { | ||||
|     func(); | ||||
| #define PARAM(n)    Core::g_app_core->GetReg(n) | ||||
| #define RETURN(n)   Core::g_app_core->SetReg(0, n) | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Function wrappers that return type s32
 | ||||
| 
 | ||||
| template<s32 func(u32, u32, u32, u32)> void Wrap() { | ||||
|     RETURN(func(PARAM(0), PARAM(1), PARAM(2), PARAM(3))); | ||||
| } | ||||
| 
 | ||||
| template<u32 func()> void WrapU_V() { | ||||
| template<s32 func(u32, u32, u32, u32, u32)> void Wrap() { | ||||
|     RETURN(func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32*, u32, u32, u32, u32, u32)> void Wrap(){ | ||||
|     u32 param_1 = 0; | ||||
|     u32 retval = func(¶m_1, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     Core::g_app_core->SetReg(1, param_1); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(s32*, u32*, s32, bool, s64)> void Wrap() { | ||||
|     s32 param_1 = 0; | ||||
|     s32 retval = func(¶m_1, (Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2),  | ||||
|         (PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0))); | ||||
|     Core::g_app_core->SetReg(1, (u32)param_1); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| // TODO(bunnei): Is this correct? Probably not
 | ||||
| template<s32 func(u32, u32, u32, u32, s64)> void Wrap() { | ||||
|     RETURN(func(PARAM(5), PARAM(1), PARAM(2), PARAM(3), (((s64)PARAM(4) << 32) | PARAM(0)))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32, s64)> void Wrap() { | ||||
|     RETURN(func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2)))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(void*, void*, u32)> void Wrap(){ | ||||
|     RETURN(func(Memory::GetPointer(PARAM(0)), Memory::GetPointer(PARAM(1)), PARAM(2))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(s32*, u32)> void Wrap(){ | ||||
|     s32 param_1 = 0; | ||||
|     u32 retval = func(¶m_1, PARAM(1)); | ||||
|     Core::g_app_core->SetReg(1, param_1); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32, s32)> void Wrap() { | ||||
|     RETURN(func(PARAM(0), (s32)PARAM(1))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32*, u32)> void Wrap(){ | ||||
|     u32 param_1 = 0; | ||||
|     u32 retval = func(¶m_1, PARAM(1)); | ||||
|     Core::g_app_core->SetReg(1, param_1); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32)> void Wrap() { | ||||
|     RETURN(func(PARAM(0))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(void*)> void Wrap() { | ||||
|     RETURN(func(Memory::GetPointer(PARAM(0)))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(s64*, u32, void*, s32)> void Wrap(){ | ||||
|     RETURN(func((s64*)Memory::GetPointer(PARAM(0)), PARAM(1), Memory::GetPointer(PARAM(2)),  | ||||
|         (s32)PARAM(3))); | ||||
| } | ||||
| 
 | ||||
| template<s32 func(u32*, const char*)> void Wrap() { | ||||
|     u32 param_1 = 0; | ||||
|     u32 retval = func(¶m_1, Memory::GetCharPointer(PARAM(1))); | ||||
|     Core::g_app_core->SetReg(1, param_1); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Function wrappers that return type u32
 | ||||
| 
 | ||||
| template<u32 func()> void Wrap() { | ||||
|     RETURN(func()); | ||||
| } | ||||
| 
 | ||||
| template<int func(void *, const char *)> void WrapI_VC() { | ||||
|     u32 retval = func(Memory::GetPointer(PARAM(0)), Memory::GetCharPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, void *, int)> void WrapU_IVI() { | ||||
|     u32 retval = func(PARAM(0), Memory::GetPointer(PARAM(1)), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, int, int, u32)> void WrapI_CIIU() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, const char *, u32, void *, void *, u32, int)> void WrapI_ICUVVUI() { | ||||
|     u32 retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), Memory::GetPointer(PARAM(3)),Memory::GetPointer(PARAM(4)), PARAM(5), PARAM(6) ); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| // Hm, do so many params get passed in registers?
 | ||||
| template<int func(const char *, int, const char *, int, int, int, int, int)> void WrapI_CICIIIII() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), Memory::GetCharPointer(PARAM(2)), | ||||
|         PARAM(3), PARAM(4), PARAM(5), PARAM(6), PARAM(7)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| // Hm, do so many params get passed in registers?
 | ||||
| template<int func(const char *, int, int, int, int, int, int)> void WrapI_CIIIIII() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|         PARAM(3), PARAM(4), PARAM(5), PARAM(6)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| // Hm, do so many params get passed in registers?
 | ||||
| template<int func(int, int, int, int, int, int, u32)> void WrapI_IIIIIIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| // Hm, do so many params get passed in registers?
 | ||||
| template<int func(int, int, int, int, int, int, int, int, u32)> void WrapI_IIIIIIIIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6), PARAM(7), PARAM(8)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, void *)> void WrapU_IV() { | ||||
|     u32 retval = func(PARAM(0), Memory::GetPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32)> void WrapU_U() { | ||||
|     u32 retval = func(PARAM(0)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int)> void WrapU_UI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32)> void WrapI_U() { | ||||
|     int retval = func(PARAM(0)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int)> void WrapI_UI() { | ||||
|     int retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, int, u32)> void WrapI_UIIU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, u32, int)> void WrapU_IUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32)> void WrapI_UU() { | ||||
|     int retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, u32)> void WrapI_UUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, u32, int)> void WrapI_UUUI() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, u32, int, int, int,int )> void WrapI_UUUIIII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, u32, u32)> void WrapI_UUUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, u32, u32, u32)> void WrapI_UUUUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*)> void WrapI_V() { | ||||
|     u32 retval = func(Memory::GetPointer(PARAM(0))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int)> void WrapU_I() { | ||||
|     u32 retval = func(PARAM(0)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, int, u32)> void WrapU_IIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int)> void WrapI_I() { | ||||
|     int retval = func(PARAM(0)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32)> void WrapV_U() { | ||||
|     func(PARAM(0)); | ||||
| } | ||||
| 
 | ||||
| template<void func(int)> void WrapV_I() { | ||||
|     func(PARAM(0)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, u32)> void WrapV_UU() { | ||||
|     func(PARAM(0), PARAM(1)); | ||||
| } | ||||
| 
 | ||||
| template<void func(int, int)> void WrapV_II() { | ||||
|     func(PARAM(0), PARAM(1)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, const char *)> void WrapV_UC() { | ||||
|     func(PARAM(0), Memory::GetCharPointer(PARAM(1))); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, const char *)> void WrapI_UC() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, const char *, int)> void WrapI_UCI() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int , int , int, int, int)> void WrapU_UIIIII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int , int , int, u32)> void WrapU_UIIIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int , int , int, int, int, int)> void WrapU_UIIIIII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32)> void WrapU_UU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, int)> void WrapU_UUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, int, int)> void WrapU_UUII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, u32, u32, u32)> void WrapU_CUUU() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int, u32, int, int)> void WrapV_UIUII() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, u32, int, int)> void WrapU_UIUII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, u32, int, int)> void WrapI_UIUII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, u32, int)> void WrapU_UIUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, u32, int)> void WrapI_UIUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, u32)> void WrapU_UIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, u32, u32)> void WrapU_UIUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, int)> void WrapU_UII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, int, u32)> void WrapU_UIIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, int, u32, u32)> void WrapI_UIIUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, int, int)> void WrapI_UUII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, int, int, int)> void WrapI_UUIII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int, int, int)> void WrapV_UIII() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int, int, int, int, int)> void WrapV_UIIIII() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int, int)> void WrapV_UII() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2)); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, u32)> void WrapU_IU() { | ||||
|     int retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32)> void WrapI_IU() { | ||||
|     int retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, int)> void WrapI_UUI() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, u32, int, u32)> void WrapI_UUIU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, int)> void WrapI_II() { | ||||
|     int retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, int, int)> void WrapI_III() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, int)> void WrapI_IUI() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, int, int, int)> void WrapI_IIII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, int, int)> void WrapI_UIII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, int, int, u32, int)> void WrapI_IIIUI() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, u32, int, int)> void WrapI_IUUII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, const char *, int, u32, u32)> void WrapI_ICIUU() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, int, u32)> void WrapI_IIU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(int, u32)> void WrapV_IU() { | ||||
|     func(PARAM(0), PARAM(1)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int)> void WrapV_UI() { | ||||
|     func(PARAM(0), PARAM(1)); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *)> void WrapU_C() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, const char *, const char *, u32)> void WrapU_CCCU() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), | ||||
|             Memory::GetCharPointer(PARAM(1)), Memory::GetCharPointer(PARAM(2)), | ||||
|             PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *)> void WrapI_C() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32)> void WrapI_CU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, int)> void WrapI_CUI() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, const char *, int, u32)> void WrapI_ICIU() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, int, u32)> void WrapI_CIU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, u32)> void WrapI_CUU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, u32, u32)> void WrapI_CUUU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, const char*, int, int)> void WrapI_CCII() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, u32, int, u32, u32)> void WrapI_CUUIUU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, int, int, u32, int, int)> void WrapI_CIIUII() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|         PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, int, u32, u32, u32)> void WrapI_CIUUU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, u32, u32, u32, u32)> void WrapI_CUUUUU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, u32)> void WrapU_CU() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1)); | ||||
|     RETURN((u32) retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, const char *)> void WrapU_UC() { | ||||
|     u32 retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, u32, u32)> void WrapU_CUU() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN((u32) retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, int, int)> void WrapU_III() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, int)> void WrapU_II() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, int, int, int)> void WrapU_IIII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, u32, u32)> void WrapU_IUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, u32, u32, u32)> void WrapU_IUUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(int, u32, u32, u32, u32)> void WrapU_IUUUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32)> void WrapU_UUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(int, u32, u32)> void WrapV_IUU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2)); | ||||
| } | ||||
| 
 | ||||
| template<void func(int, int, u32)> void WrapV_IIU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, int, u32)> void WrapV_UIU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2)); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, u32)> void WrapI_UIU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(int, u32, u32, u32, u32)> void WrapV_IUUUU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, u32, u32)> void WrapV_UUU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2)); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, u32, u32, u32)> void WrapV_UUUU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
| } | ||||
| 
 | ||||
| template<void func(const char *, u32, int, u32)> void WrapV_CUIU() { | ||||
|     func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), PARAM(3)); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, int, u32)> void WrapI_CUIU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, const char *, u32, int, u32)> void WrapV_UCUIU() { | ||||
|     func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3), | ||||
|             PARAM(4)); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, const char *, u32, int, u32)> void WrapI_UCUIU() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), | ||||
|             PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(const char *, u32, int, int, u32)> void WrapV_CUIIU() { | ||||
|     func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), PARAM(3), | ||||
|             PARAM(4)); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, u32, int, int, u32)> void WrapI_CUIIU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, u32)> void WrapU_UUUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, const char *, u32, u32)> void WrapU_UCUU() { | ||||
|     u32 retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, int)> void WrapU_UUUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, int, u32)> void WrapU_UUUIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, int, u32, int)> void WrapU_UUUIUI() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, int, u32)> void WrapU_UUIU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, int, int, int)> void WrapU_UIII() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, u32, u32, u32)> void WrapI_IUUUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, u32, u32, u32, u32)> void WrapI_IUUUUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, int, int)> void WrapI_IUII() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| template<u32 func(u32, u32, u32, u32, u32)> void WrapU_UUUUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<void func(u32, u32, u32, u32, u32)> void WrapV_UUUUU() { | ||||
|     func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
| } | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| /// Function wrappers that return type void
 | ||||
| 
 | ||||
| template<u32 func(const char *, const char *)> void WrapU_CC() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), | ||||
|             Memory::GetCharPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| template<void func(s64)> void Wrap() { | ||||
|     func(((s64)PARAM(1) << 32) | PARAM(0)); | ||||
| } | ||||
| 
 | ||||
| template<void func(const char*)> void WrapV_C() { | ||||
| template<void func(const char*)> void Wrap() { | ||||
|     func(Memory::GetCharPointer(PARAM(0))); | ||||
| } | ||||
| 
 | ||||
| template<void func(const char *, int)> void WrapV_CI() { | ||||
|     func(Memory::GetCharPointer(PARAM(0)), PARAM(1)); | ||||
| } | ||||
| #undef PARAM | ||||
| #undef RETURN | ||||
| 
 | ||||
| template<u32 func(const char *, int)> void WrapU_CI() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, int, int)> void WrapU_CII() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(const char *, int, u32, int, u32)> void WrapU_CIUIU() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(const char *, int, u32, int, u32, int)> void WrapU_CIUIUI() { | ||||
|     u32 retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2), | ||||
|             PARAM(3), PARAM(4), PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, u32, u32, u32)> void WrapU_UUUUUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), | ||||
|             PARAM(5)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, u32, u32)> void WrapI_IUUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, u32, u32)> void WrapI_IUU() { | ||||
|     int retval = func(PARAM(0), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<u32 func(u32, u32, u32, u32, u32, u32, u32)> void WrapU_UUUUUUU() { | ||||
|   u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5), PARAM(6)); | ||||
|   RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, int, u32, u32)> void WrapI_UIUU() { | ||||
|     u32 retval = func(PARAM(0), PARAM(1), PARAM(2), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, const char *)> void WrapI_IC() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1))); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template <int func(int, const char *, const char *, u32, int)> void WrapI_ICCUI() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), Memory::GetCharPointer(PARAM(2)), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template <int func(int, const char *, const char *, int)> void WrapI_ICCI() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), Memory::GetCharPointer(PARAM(2)), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template <int func(const char *, int, int)> void WrapI_CII() { | ||||
|     int retval = func(Memory::GetCharPointer(PARAM(0)), PARAM(1), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template <int func(int, const char *, int)> void WrapI_ICI() { | ||||
|     int retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, void *, void *, void *, void *, u32, int)> void WrapI_IVVVVUI(){ | ||||
|     u32 retval = func(PARAM(0), Memory::GetPointer(PARAM(1)), Memory::GetPointer(PARAM(2)), Memory::GetPointer(PARAM(3)), Memory::GetPointer(PARAM(4)), PARAM(5), PARAM(6) ); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(int, const char *, u32, void *, int, int, int)> void WrapI_ICUVIII(){ | ||||
|     u32 retval = func(PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), Memory::GetPointer(PARAM(3)), PARAM(4), PARAM(5), PARAM(6)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*, u32)> void WrapI_VU(){ | ||||
|     u32 retval = func(Memory::GetPointer(PARAM(0)), PARAM(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*, void*, u32)> void WrapI_VVU(){ | ||||
|     u32 retval = func(Memory::GetPointer(PARAM(0)), Memory::GetPointer(PARAM(1)), PARAM(2)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*, u32, void*, int)> void WrapI_VUVI(){ | ||||
|     u32 retval = func(Memory::GetPointer(PARAM(0)), PARAM(1), Memory::GetPointer(PARAM(2)), PARAM(3)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*, u32, u32, u32, u32, u32)> void WrapI_VUUUUU(){ | ||||
|     u32 retval = func(NULL, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(u32, s64)> void WrapI_US64() { | ||||
|     int retval = func(PARAM(0), PARAM64(1)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| 
 | ||||
| template<int func(void*, void*, u32, u32, s64)> void WrapI_VVUUS64() { | ||||
|     int retval = func(Memory::GetPointer(PARAM(0)), Memory::GetPointer(PARAM(1)), PARAM(2), PARAM(3), PARAM(4)); | ||||
|     RETURN(retval); | ||||
| } | ||||
| } // namespace HLE
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "core/mem_map.h" | ||||
| #include "core/hle/hle.h" | ||||
| #include "core/hle/svc.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -15,11 +16,13 @@ namespace HLE { | |||
| 
 | ||||
| static std::vector<ModuleDef> g_module_db; | ||||
| 
 | ||||
| bool g_reschedule = false;  ///< If true, immediately reschedules the CPU to a new thread
 | ||||
| 
 | ||||
| const FunctionDef* GetSVCInfo(u32 opcode) { | ||||
|     u32 func_num = opcode & 0xFFFFFF; // 8 bits
 | ||||
|     if (func_num > 0xFF) { | ||||
|         ERROR_LOG(HLE,"Unknown SVC: 0x%02X", func_num);  | ||||
|         return NULL; | ||||
|         ERROR_LOG(HLE,"unknown svc=0x%02X", func_num);  | ||||
|         return nullptr; | ||||
|     } | ||||
|     return &g_module_db[0].func_table[func_num]; | ||||
| } | ||||
|  | @ -33,19 +36,16 @@ void CallSVC(u32 opcode) { | |||
|     if (info->func) { | ||||
|         info->func(); | ||||
|     } else { | ||||
|         ERROR_LOG(HLE, "Unimplemented SVC function %s(..)", info->name.c_str()); | ||||
|         ERROR_LOG(HLE, "unimplemented SVC function %s(..)", info->name.c_str()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void EatCycles(u32 cycles) { | ||||
|     // TODO: ImplementMe
 | ||||
| } | ||||
| 
 | ||||
| void ReSchedule(const char *reason) { | ||||
| void Reschedule(const char *reason) { | ||||
| #ifdef _DEBUG | ||||
|     _dbg_assert_msg_(HLE, reason != 0 && strlen(reason) < 256, "ReSchedule: Invalid or too long reason."); | ||||
|     _dbg_assert_msg_(HLE, reason != 0 && strlen(reason) < 256, "Reschedule: Invalid or too long reason."); | ||||
| #endif | ||||
|     // TODO: ImplementMe
 | ||||
|     Core::g_app_core->PrepareReschedule(); | ||||
|     g_reschedule = true; | ||||
| } | ||||
| 
 | ||||
| void RegisterModule(std::string name, int num_functions, const FunctionDef* func_table) { | ||||
|  |  | |||
|  | @ -9,14 +9,10 @@ | |||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #define PARAM(n)        Core::g_app_core->GetReg(n) | ||||
| #define PARAM64(n)      (Core::g_app_core->GetReg(n) | ((u64)Core::g_app_core->GetReg(n + 1) << 32)) | ||||
| #define RETURN(n)       Core::g_app_core->SetReg(0, n) | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| namespace HLE { | ||||
| 
 | ||||
| extern bool g_reschedule;   ///< If true, immediately reschedules the CPU to a new thread
 | ||||
| 
 | ||||
| typedef u32 Addr; | ||||
| typedef void (*Func)(); | ||||
| 
 | ||||
|  | @ -36,9 +32,7 @@ void RegisterModule(std::string name, int num_functions, const FunctionDef *func | |||
| 
 | ||||
| void CallSVC(u32 opcode); | ||||
| 
 | ||||
| void EatCycles(u32 cycles); | ||||
| 
 | ||||
| void ReSchedule(const char *reason); | ||||
| void Reschedule(const char *reason); | ||||
| 
 | ||||
| void Init(); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										159
									
								
								src/core/hle/kernel/event.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/core/hle/kernel/event.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,159 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.  
 | ||||
| 
 | ||||
| #include <map> | ||||
| #include <algorithm> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "common/common.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class Event : public Object { | ||||
| public: | ||||
|     const char* GetTypeName() const { return "Event"; } | ||||
|     const char* GetName() const { return name.c_str(); } | ||||
| 
 | ||||
|     static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Event; } | ||||
|     Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Event; } | ||||
| 
 | ||||
|     ResetType intitial_reset_type;          ///< ResetType specified at Event initialization
 | ||||
|     ResetType reset_type;                   ///< Current ResetType
 | ||||
| 
 | ||||
|     bool locked;                            ///< Event signal wait
 | ||||
|     bool permanent_locked;                  ///< Hack - to set event permanent state (for easy passthrough)
 | ||||
|     std::vector<Handle> waiting_threads;    ///< Threads that are waiting for the event
 | ||||
|     std::string name;                       ///< Name of event (optional)
 | ||||
| 
 | ||||
|     /**
 | ||||
|      * Wait for kernel object to synchronize | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result WaitSynchronization(bool* wait) { | ||||
|         *wait = locked; | ||||
|         if (locked) { | ||||
|             Handle thread = GetCurrentThreadHandle(); | ||||
|             if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { | ||||
|                 waiting_threads.push_back(thread); | ||||
|             } | ||||
|             Kernel::WaitCurrentThread(WAITTYPE_EVENT); | ||||
|         } | ||||
|         if (reset_type != RESETTYPE_STICKY && !permanent_locked) { | ||||
|             locked = true; | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Hackish function to set an events permanent lock state, used to pass through synch blocks | ||||
|  * @param handle Handle to event to change | ||||
|  * @param permanent_locked Boolean permanent locked value to set event | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SetPermanentLock(Handle handle, const bool permanent_locked) { | ||||
|     Event* evt = g_object_pool.GetFast<Event>(handle); | ||||
|     _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); | ||||
| 
 | ||||
|     evt->permanent_locked = permanent_locked; | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Changes whether an event is locked or not | ||||
|  * @param handle Handle to event to change | ||||
|  * @param locked Boolean locked value to set event | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SetEventLocked(const Handle handle, const bool locked) { | ||||
|     Event* evt = g_object_pool.GetFast<Event>(handle); | ||||
|     _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); | ||||
| 
 | ||||
|     if (!evt->permanent_locked) { | ||||
|         evt->locked = locked; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Signals an event | ||||
|  * @param handle Handle to event to signal | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SignalEvent(const Handle handle) { | ||||
|     Event* evt = g_object_pool.GetFast<Event>(handle); | ||||
|     _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); | ||||
| 
 | ||||
|     // Resume threads waiting for event to signal
 | ||||
|     bool event_caught = false; | ||||
|     for (size_t i = 0; i < evt->waiting_threads.size(); ++i) { | ||||
|         ResumeThreadFromWait( evt->waiting_threads[i]); | ||||
| 
 | ||||
|         // If any thread is signalled awake by this event, assume the event was "caught" and reset 
 | ||||
|         // the event. This will result in the next thread waiting on the event to block. Otherwise,
 | ||||
|         // the event will not be reset, and the next thread to call WaitSynchronization on it will
 | ||||
|         // not block. Not sure if this is correct behavior, but it seems to work.
 | ||||
|         event_caught = true; | ||||
|     } | ||||
|     evt->waiting_threads.clear(); | ||||
| 
 | ||||
|     if (!evt->permanent_locked) { | ||||
|         evt->locked = event_caught; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Clears an event | ||||
|  * @param handle Handle to event to clear | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result ClearEvent(Handle handle) { | ||||
|     Event* evt = g_object_pool.GetFast<Event>(handle); | ||||
|     _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); | ||||
| 
 | ||||
|     if (!evt->permanent_locked) { | ||||
|         evt->locked = true; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates an event | ||||
|  * @param handle Reference to handle for the newly created mutex | ||||
|  * @param reset_type ResetType describing how to create event | ||||
|  * @param name Optional name of event | ||||
|  * @return Newly created Event object | ||||
|  */ | ||||
| Event* CreateEvent(Handle& handle, const ResetType reset_type, const std::string& name) { | ||||
|     Event* evt = new Event; | ||||
| 
 | ||||
|     handle = Kernel::g_object_pool.Create(evt); | ||||
| 
 | ||||
|     evt->locked = true; | ||||
|     evt->permanent_locked = false; | ||||
|     evt->reset_type = evt->intitial_reset_type = reset_type; | ||||
|     evt->name = name; | ||||
| 
 | ||||
|     return evt; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates an event | ||||
|  * @param reset_type ResetType describing how to create event | ||||
|  * @param name Optional name of event | ||||
|  * @return Handle to newly created Event object | ||||
|  */ | ||||
| Handle CreateEvent(const ResetType reset_type, const std::string& name) { | ||||
|     Handle handle; | ||||
|     Event* evt = CreateEvent(handle, reset_type, name); | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
							
								
								
									
										52
									
								
								src/core/hle/kernel/event.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/hle/kernel/event.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.  
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/svc.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /**
 | ||||
|  * Changes whether an event is locked or not | ||||
|  * @param handle Handle to event to change | ||||
|  * @param locked Boolean locked value to set event | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SetEventLocked(const Handle handle, const bool locked); | ||||
| 
 | ||||
| /**
 | ||||
|  * Hackish function to set an events permanent lock state, used to pass through synch blocks | ||||
|  * @param handle Handle to event to change | ||||
|  * @param permanent_locked Boolean permanent locked value to set event | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SetPermanentLock(Handle handle, const bool permanent_locked); | ||||
| 
 | ||||
| /**
 | ||||
|  * Signals an event | ||||
|  * @param handle Handle to event to signal | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result SignalEvent(const Handle handle); | ||||
| 
 | ||||
| /**
 | ||||
|  * Clears an event | ||||
|  * @param handle Handle to event to clear | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result ClearEvent(Handle handle); | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates an event | ||||
|  * @param reset_type ResetType describing how to create event | ||||
|  * @param name Optional name of event | ||||
|  * @return Handle to newly created Event object | ||||
|  */ | ||||
| Handle CreateEvent(const ResetType reset_type, const std::string& name="Unknown"); | ||||
| 
 | ||||
| } // namespace
 | ||||
|  | @ -2,8 +2,6 @@ | |||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.  
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "common/common.h" | ||||
|  | @ -14,6 +12,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| Handle g_main_thread = 0; | ||||
| ObjectPool g_object_pool; | ||||
| 
 | ||||
| ObjectPool::ObjectPool() { | ||||
|  | @ -127,16 +126,20 @@ Object* ObjectPool::CreateByIDType(int type) { | |||
| 
 | ||||
|     default: | ||||
|         ERROR_LOG(COMMON, "Unable to load state: could not find object type %d.", type); | ||||
|         return NULL; | ||||
|         return nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Initialize the kernel
 | ||||
| void Init() { | ||||
|     Kernel::ThreadingInit(); | ||||
| } | ||||
| 
 | ||||
| /// Shutdown the kernel
 | ||||
| void Shutdown() { | ||||
|     Kernel::ThreadingShutdown(); | ||||
| 
 | ||||
|     g_object_pool.Clear(); // Free all kernel objects
 | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -150,7 +153,7 @@ bool LoadExec(u32 entry_point) { | |||
|     Core::g_app_core->SetPC(entry_point); | ||||
| 
 | ||||
|     // 0x30 is the typical main thread priority I've seen used so far
 | ||||
|     Handle thread = Kernel::SetupMainThread(0x30); | ||||
|     g_main_thread = Kernel::SetupMainThread(0x30); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,11 @@ typedef s32 Result; | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| enum KernelHandle { | ||||
|     CurrentThread   = 0xFFFF8000, | ||||
|     CurrentProcess  = 0xFFFF8001, | ||||
| }; | ||||
| 
 | ||||
| enum class HandleType : u32 { | ||||
|     Unknown         = 0, | ||||
|     Port            = 1, | ||||
|  | @ -39,9 +44,26 @@ class Object : NonCopyable { | |||
| public: | ||||
|     virtual ~Object() {} | ||||
|     Handle GetHandle() const { return handle; } | ||||
|     virtual const char *GetTypeName() { return "[BAD KERNEL OBJECT TYPE]"; } | ||||
|     virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; } | ||||
|     virtual const char* GetTypeName() const { return "[BAD KERNEL OBJECT TYPE]"; } | ||||
|     virtual const char* GetName() const { return "[UNKNOWN KERNEL OBJECT]"; } | ||||
|     virtual Kernel::HandleType GetHandleType() const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Synchronize kernel object  | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     virtual Result SyncRequest(bool* wait) { | ||||
|         ERROR_LOG(KERNEL, "(UNIMPLEMENTED)"); | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Wait for kernel object to synchronize | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     virtual Result WaitSynchronization(bool* wait) = 0; | ||||
| }; | ||||
| 
 | ||||
| class ObjectPool : NonCopyable { | ||||
|  | @ -143,6 +165,13 @@ private: | |||
| }; | ||||
| 
 | ||||
| extern ObjectPool g_object_pool; | ||||
| extern Handle g_main_thread; | ||||
| 
 | ||||
| /// Initialize the kernel
 | ||||
| void Init(); | ||||
| 
 | ||||
| /// Shutdown the kernel
 | ||||
| void Shutdown(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Loads executable stored at specified address | ||||
|  |  | |||
|  | @ -8,21 +8,51 @@ | |||
| #include "common/common.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/mutex.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class Mutex : public Object { | ||||
| public: | ||||
|     const char* GetTypeName() { return "Mutex"; } | ||||
|     const char* GetTypeName() const { return "Mutex"; } | ||||
|     const char* GetName() const { return name.c_str(); } | ||||
| 
 | ||||
|     static Kernel::HandleType GetStaticHandleType() {  return Kernel::HandleType::Mutex; } | ||||
|     static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Mutex; } | ||||
|     Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Mutex; } | ||||
| 
 | ||||
|     bool initial_locked;                        ///< Initial lock state when mutex was created
 | ||||
|     bool locked;                                ///< Current locked state
 | ||||
|     Handle lock_thread;                         ///< Handle to thread that currently has mutex
 | ||||
|     std::vector<Handle> waiting_threads;        ///< Threads that are waiting for the mutex
 | ||||
|     std::string name;                           ///< Name of mutex (optional)
 | ||||
| 
 | ||||
|     /**
 | ||||
|      * Synchronize kernel object  | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result SyncRequest(bool* wait) { | ||||
|         // TODO(bunnei): ImplementMe
 | ||||
|         locked = true; | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Wait for kernel object to synchronize | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result WaitSynchronization(bool* wait) { | ||||
|         // TODO(bunnei): ImplementMe
 | ||||
|         *wait = locked; | ||||
| 
 | ||||
|         if (locked) { | ||||
|             Kernel::WaitCurrentThread(WAITTYPE_MUTEX); | ||||
|         } | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -70,10 +100,10 @@ bool ReleaseMutexForThread(Mutex* mutex, Handle thread) { | |||
| bool ReleaseMutex(Mutex* mutex) { | ||||
|     MutexEraseLock(mutex); | ||||
|     bool woke_threads = false; | ||||
|     auto iter = mutex->waiting_threads.begin(); | ||||
| 
 | ||||
|     // Find the next waiting thread for the mutex...
 | ||||
|     while (!woke_threads && !mutex->waiting_threads.empty()) { | ||||
|         std::vector<Handle>::iterator iter = mutex->waiting_threads.begin(); | ||||
|         woke_threads |= ReleaseMutexForThread(mutex, *iter); | ||||
|         mutex->waiting_threads.erase(iter); | ||||
|     } | ||||
|  | @ -91,6 +121,9 @@ bool ReleaseMutex(Mutex* mutex) { | |||
|  */ | ||||
| Result ReleaseMutex(Handle handle) { | ||||
|     Mutex* mutex = Kernel::g_object_pool.GetFast<Mutex>(handle); | ||||
| 
 | ||||
|     _assert_msg_(KERNEL, (mutex != nullptr), "ReleaseMutex tried to release a nullptr mutex!"); | ||||
| 
 | ||||
|     if (!ReleaseMutex(mutex)) { | ||||
|         return -1; | ||||
|     } | ||||
|  | @ -101,12 +134,15 @@ Result ReleaseMutex(Handle handle) { | |||
|  * Creates a mutex | ||||
|  * @param handle Reference to handle for the newly created mutex | ||||
|  * @param initial_locked Specifies if the mutex should be locked initially | ||||
|  * @param name Optional name of mutex | ||||
|  * @return Pointer to new Mutex object | ||||
|  */ | ||||
| Mutex* CreateMutex(Handle& handle, bool initial_locked) { | ||||
| Mutex* CreateMutex(Handle& handle, bool initial_locked, const std::string& name) { | ||||
|     Mutex* mutex = new Mutex; | ||||
|     handle = Kernel::g_object_pool.Create(mutex); | ||||
| 
 | ||||
|     mutex->locked = mutex->initial_locked = initial_locked; | ||||
|     mutex->name = name; | ||||
| 
 | ||||
|     // Acquire mutex with current thread if initialized as locked...
 | ||||
|     if (mutex->locked) { | ||||
|  | @ -122,10 +158,12 @@ Mutex* CreateMutex(Handle& handle, bool initial_locked) { | |||
| /**
 | ||||
|  * Creates a mutex | ||||
|  * @param initial_locked Specifies if the mutex should be locked initially | ||||
|  * @param name Optional name of mutex | ||||
|  * @return Handle to newly created object | ||||
|  */ | ||||
| Handle CreateMutex(bool initial_locked) { | ||||
| Handle CreateMutex(bool initial_locked, const std::string& name) { | ||||
|     Handle handle; | ||||
|     Mutex* mutex = CreateMutex(handle, initial_locked); | ||||
|     Mutex* mutex = CreateMutex(handle, initial_locked, name); | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,14 +13,16 @@ namespace Kernel { | |||
| /**
 | ||||
|  * Releases a mutex | ||||
|  * @param handle Handle to mutex to release | ||||
|  * @return Result of operation, 0 on success, otherwise error code | ||||
|  */ | ||||
| Result ReleaseMutex(Handle handle); | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates a mutex | ||||
|  * @param handle Reference to handle for the newly created mutex | ||||
|  * @param initial_locked Specifies if the mutex should be locked initially | ||||
|  * @param name Optional name of mutex | ||||
|  * @return Handle to newly created object | ||||
|  */ | ||||
| Handle CreateMutex(bool initial_locked); | ||||
| Handle CreateMutex(bool initial_locked, const std::string& name="Unknown"); | ||||
| 
 | ||||
| } // namespace
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #include <list> | ||||
| #include <algorithm> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <string> | ||||
|  | @ -24,10 +25,10 @@ namespace Kernel { | |||
| class Thread : public Kernel::Object { | ||||
| public: | ||||
| 
 | ||||
|     const char* GetName() { return name; } | ||||
|     const char* GetTypeName() { return "Thread"; } | ||||
|     const char* GetName() const { return name; } | ||||
|     const char* GetTypeName() const { return "Thread"; } | ||||
| 
 | ||||
|     static Kernel::HandleType GetStaticHandleType() {  return Kernel::HandleType::Thread; } | ||||
|     static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Thread; } | ||||
|     Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Thread; } | ||||
| 
 | ||||
|     inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; } | ||||
|  | @ -36,6 +37,23 @@ public: | |||
|     inline bool IsWaiting() const { return (status & THREADSTATUS_WAIT) != 0; } | ||||
|     inline bool IsSuspended() const { return (status & THREADSTATUS_SUSPEND) != 0; } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Wait for kernel object to synchronize | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result WaitSynchronization(bool* wait) { | ||||
|         if (status != THREADSTATUS_DORMANT) { | ||||
|             Handle thread = GetCurrentThreadHandle(); | ||||
|             if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { | ||||
|                 waiting_threads.push_back(thread); | ||||
|             } | ||||
|             WaitCurrentThread(WAITTYPE_THREADEND, this->GetHandle()); | ||||
|             *wait = true; | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     ThreadContext context; | ||||
| 
 | ||||
|     u32 status; | ||||
|  | @ -49,6 +67,9 @@ public: | |||
|     s32 processor_id; | ||||
| 
 | ||||
|     WaitType wait_type; | ||||
|     Handle wait_handle; | ||||
| 
 | ||||
|     std::vector<Handle> waiting_threads; | ||||
| 
 | ||||
|     char name[Kernel::MAX_NAME_LENGTH + 1]; | ||||
| }; | ||||
|  | @ -62,7 +83,6 @@ Common::ThreadQueueList<Handle> g_thread_ready_queue; | |||
| Handle g_current_thread_handle; | ||||
| Thread* g_current_thread; | ||||
| 
 | ||||
| 
 | ||||
| /// Gets the current thread
 | ||||
| inline Thread* GetCurrentThread() { | ||||
|     return g_current_thread; | ||||
|  | @ -94,15 +114,15 @@ void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { | |||
|     memset(&t->context, 0, sizeof(ThreadContext)); | ||||
| 
 | ||||
|     t->context.cpu_registers[0] = arg; | ||||
|     t->context.pc = t->entry_point; | ||||
|     t->context.pc = t->context.reg_15 = t->entry_point; | ||||
|     t->context.sp = t->stack_top; | ||||
|     t->context.cpsr = 0x1F; // Usermode
 | ||||
|      | ||||
|     if (t->current_priority < lowest_priority) { | ||||
|         t->current_priority = t->initial_priority; | ||||
|     } | ||||
|          | ||||
|     t->wait_type = WAITTYPE_NONE; | ||||
|     t->wait_handle = 0; | ||||
| } | ||||
| 
 | ||||
| /// Change a thread to "ready" state
 | ||||
|  | @ -122,6 +142,37 @@ void ChangeReadyState(Thread* t, bool ready) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Verify that a thread has not been released from waiting
 | ||||
| inline bool VerifyWait(const Handle& handle, WaitType type, Handle wait_handle) { | ||||
|     Thread* thread = g_object_pool.GetFast<Thread>(handle); | ||||
|     _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); | ||||
| 
 | ||||
|     if (type != thread->wait_type || wait_handle != thread->wait_handle)  | ||||
|         return false; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /// Stops the current thread
 | ||||
| void StopThread(Handle handle, const char* reason) { | ||||
|     Thread* thread = g_object_pool.GetFast<Thread>(handle); | ||||
|     _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); | ||||
|      | ||||
|     ChangeReadyState(thread, false); | ||||
|     thread->status = THREADSTATUS_DORMANT; | ||||
|     for (size_t i = 0; i < thread->waiting_threads.size(); ++i) { | ||||
|         const Handle waiting_thread = thread->waiting_threads[i]; | ||||
|         if (VerifyWait(waiting_thread, WAITTYPE_THREADEND, handle)) { | ||||
|             ResumeThreadFromWait(waiting_thread); | ||||
|         } | ||||
|     } | ||||
|     thread->waiting_threads.clear(); | ||||
| 
 | ||||
|     // Stopped threads are never waiting.
 | ||||
|     thread->wait_type = WAITTYPE_NONE; | ||||
|     thread->wait_handle = 0; | ||||
| } | ||||
| 
 | ||||
| /// Changes a threads state
 | ||||
| void ChangeThreadState(Thread* t, ThreadStatus new_status) { | ||||
|     if (!t || t->status == new_status) { | ||||
|  | @ -132,7 +183,7 @@ void ChangeThreadState(Thread* t, ThreadStatus new_status) { | |||
|      | ||||
|     if (new_status == THREADSTATUS_WAIT) { | ||||
|         if (t->wait_type == WAITTYPE_NONE) { | ||||
|             printf("ERROR: Waittype none not allowed here\n"); | ||||
|             ERROR_LOG(KERNEL, "Waittype none not allowed"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -166,7 +217,7 @@ void SwitchContext(Thread* t) { | |||
|         t->wait_type = WAITTYPE_NONE; | ||||
|         LoadContext(t->context); | ||||
|     } else { | ||||
|         SetCurrentThread(NULL); | ||||
|         SetCurrentThread(nullptr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -181,26 +232,43 @@ Thread* NextThread() { | |||
|         next = g_thread_ready_queue.pop_first(); | ||||
|     } | ||||
|     if (next == 0) { | ||||
|         return NULL; | ||||
|         return nullptr; | ||||
|     } | ||||
|     return Kernel::g_object_pool.GetFast<Thread>(next); | ||||
| } | ||||
| 
 | ||||
| /// Puts the current thread in the wait state for the given type
 | ||||
| void WaitCurrentThread(WaitType wait_type) { | ||||
|     Thread* t = GetCurrentThread(); | ||||
|     t->wait_type = wait_type; | ||||
|     ChangeThreadState(t, ThreadStatus(THREADSTATUS_WAIT | (t->status & THREADSTATUS_SUSPEND))); | ||||
| void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     thread->wait_type = wait_type; | ||||
|     thread->wait_handle = wait_handle; | ||||
|     ChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->status & THREADSTATUS_SUSPEND))); | ||||
| } | ||||
| 
 | ||||
| /// Resumes a thread from waiting by marking it as "ready"
 | ||||
| void ResumeThreadFromWait(Handle handle) { | ||||
|     u32 error; | ||||
|     Thread* t = Kernel::g_object_pool.Get<Thread>(handle, error); | ||||
|     if (t) { | ||||
|         t->status &= ~THREADSTATUS_WAIT; | ||||
|         if (!(t->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { | ||||
|             ChangeReadyState(t, true); | ||||
|     Thread* thread = Kernel::g_object_pool.Get<Thread>(handle, error); | ||||
|     if (thread) { | ||||
|         thread->status &= ~THREADSTATUS_WAIT; | ||||
|         if (!(thread->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { | ||||
|             ChangeReadyState(thread, true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Prints the thread queue for debugging purposes
 | ||||
| void DebugThreadQueue() { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     if (!thread) { | ||||
|         return; | ||||
|     } | ||||
|     INFO_LOG(KERNEL, "0x%02X 0x%08X (current)", thread->current_priority, GetCurrentThreadHandle()); | ||||
|     for (u32 i = 0; i < g_thread_queue.size(); i++) { | ||||
|         Handle handle = g_thread_queue[i]; | ||||
|         s32 priority = g_thread_ready_queue.contains(handle); | ||||
|         if (priority != -1) { | ||||
|             INFO_LOG(KERNEL, "0x%02X 0x%08X", priority, handle); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -212,32 +280,34 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio | |||
|     _assert_msg_(KERNEL, (priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST),  | ||||
|         "CreateThread priority=%d, outside of allowable range!", priority) | ||||
| 
 | ||||
|     Thread* t = new Thread; | ||||
|      | ||||
|     handle = Kernel::g_object_pool.Create(t); | ||||
|      | ||||
|     Thread* thread = new Thread; | ||||
| 
 | ||||
|     handle = Kernel::g_object_pool.Create(thread); | ||||
| 
 | ||||
|     g_thread_queue.push_back(handle); | ||||
|     g_thread_ready_queue.prepare(priority); | ||||
|      | ||||
|     t->status = THREADSTATUS_DORMANT; | ||||
|     t->entry_point = entry_point; | ||||
|     t->stack_top = stack_top; | ||||
|     t->stack_size = stack_size; | ||||
|     t->initial_priority = t->current_priority = priority; | ||||
|     t->processor_id = processor_id; | ||||
|     t->wait_type = WAITTYPE_NONE; | ||||
|      | ||||
|     strncpy(t->name, name, Kernel::MAX_NAME_LENGTH); | ||||
|     t->name[Kernel::MAX_NAME_LENGTH] = '\0'; | ||||
|      | ||||
|     return t; | ||||
| 
 | ||||
|     thread->status = THREADSTATUS_DORMANT; | ||||
|     thread->entry_point = entry_point; | ||||
|     thread->stack_top = stack_top; | ||||
|     thread->stack_size = stack_size; | ||||
|     thread->initial_priority = thread->current_priority = priority; | ||||
|     thread->processor_id = processor_id; | ||||
|     thread->wait_type = WAITTYPE_NONE; | ||||
|     thread->wait_handle = 0; | ||||
| 
 | ||||
|     strncpy(thread->name, name, Kernel::MAX_NAME_LENGTH); | ||||
|     thread->name[Kernel::MAX_NAME_LENGTH] = '\0'; | ||||
| 
 | ||||
|     return thread; | ||||
| } | ||||
| 
 | ||||
| /// Creates a new thread - wrapper for external user
 | ||||
| Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id, | ||||
|     u32 stack_top, int stack_size) { | ||||
|     if (name == NULL) { | ||||
|         ERROR_LOG(KERNEL, "CreateThread(): NULL name"); | ||||
| 
 | ||||
|     if (name == nullptr) { | ||||
|         ERROR_LOG(KERNEL, "CreateThread(): nullptr name"); | ||||
|         return -1; | ||||
|     } | ||||
|     if ((u32)stack_size < 0x200) { | ||||
|  | @ -258,31 +328,67 @@ Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s3 | |||
|         return -1; | ||||
|     } | ||||
|     Handle handle; | ||||
|     Thread* t = CreateThread(handle, name, entry_point, priority, processor_id, stack_top,  | ||||
|     Thread* thread = CreateThread(handle, name, entry_point, priority, processor_id, stack_top,  | ||||
|         stack_size); | ||||
| 
 | ||||
|     ResetThread(t, arg, 0); | ||||
|     ResetThread(thread, arg, 0); | ||||
|     CallThread(thread); | ||||
| 
 | ||||
|     HLE::EatCycles(32000); | ||||
| 
 | ||||
|     // This won't schedule to the new thread, but it may to one woken from eating cycles.
 | ||||
|     // Technically, this should not eat all at once, and reschedule in the middle, but that's hard.
 | ||||
|     HLE::ReSchedule("thread created"); | ||||
| 
 | ||||
|     CallThread(t); | ||||
|      | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| /// Get the priority of the thread specified by handle
 | ||||
| u32 GetThreadPriority(const Handle handle) { | ||||
|     Thread* thread = g_object_pool.GetFast<Thread>(handle); | ||||
|     _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); | ||||
|     return thread->current_priority; | ||||
| } | ||||
| 
 | ||||
| /// Set the priority of the thread specified by handle
 | ||||
| Result SetThreadPriority(Handle handle, s32 priority) { | ||||
|     Thread* thread = nullptr; | ||||
|     if (!handle) { | ||||
|         thread = GetCurrentThread(); // TODO(bunnei): Is this correct behavior?
 | ||||
|     } else { | ||||
|         thread = g_object_pool.GetFast<Thread>(handle); | ||||
|     } | ||||
|     _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); | ||||
| 
 | ||||
|     // If priority is invalid, clamp to valid range
 | ||||
|     if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { | ||||
|         s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); | ||||
|         WARN_LOG(KERNEL, "invalid priority=0x%08X, clamping to %08X", priority, new_priority); | ||||
|         // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm
 | ||||
|         // validity of this
 | ||||
|         priority = new_priority; | ||||
|     } | ||||
| 
 | ||||
|     // Change thread priority
 | ||||
|     s32 old = thread->current_priority; | ||||
|     g_thread_ready_queue.remove(old, handle); | ||||
|     thread->current_priority = priority; | ||||
|     g_thread_ready_queue.prepare(thread->current_priority); | ||||
| 
 | ||||
|     // Change thread status to "ready" and push to ready queue
 | ||||
|     if (thread->IsRunning()) { | ||||
|         thread->status = (thread->status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY; | ||||
|     } | ||||
|     if (thread->IsReady()) { | ||||
|         g_thread_ready_queue.push_back(thread->current_priority, handle); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Sets up the primary application thread
 | ||||
| Handle SetupMainThread(s32 priority, int stack_size) { | ||||
|     Handle handle; | ||||
|      | ||||
|     // Initialize new "main" thread
 | ||||
|     Thread* t = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority,  | ||||
|     Thread* thread = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority,  | ||||
|         THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); | ||||
|      | ||||
|     ResetThread(t, 0, 0); | ||||
|     ResetThread(thread, 0, 0); | ||||
|      | ||||
|     // If running another thread already, set it to "ready" state
 | ||||
|     Thread* cur = GetCurrentThread(); | ||||
|  | @ -291,24 +397,31 @@ Handle SetupMainThread(s32 priority, int stack_size) { | |||
|     } | ||||
|      | ||||
|     // Run new "main" thread
 | ||||
|     SetCurrentThread(t); | ||||
|     t->status = THREADSTATUS_RUNNING; | ||||
|     LoadContext(t->context); | ||||
|     SetCurrentThread(thread); | ||||
|     thread->status = THREADSTATUS_RUNNING; | ||||
|     LoadContext(thread->context); | ||||
| 
 | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /// Reschedules to the next available thread (call after current thread is suspended)
 | ||||
| void Reschedule() { | ||||
|     Thread* prev = GetCurrentThread(); | ||||
|     Thread* next = NextThread(); | ||||
|     HLE::g_reschedule = false; | ||||
|     if (next > 0) { | ||||
|         INFO_LOG(KERNEL, "context switch 0x%08X -> 0x%08X", prev->GetHandle(), next->GetHandle()); | ||||
|          | ||||
|         SwitchContext(next); | ||||
| 
 | ||||
|         // Hack - automatically change previous thread (which would have been in "wait" state) to
 | ||||
|         // "ready" state, so that we can immediately resume to it when new thread yields. FixMe to
 | ||||
|         // actually wait for whatever event it is supposed to be waiting on.
 | ||||
|         ChangeReadyState(prev, true); | ||||
|         // Hack - There is no mechanism yet to waken the primary thread if it has been put to sleep
 | ||||
|         // by a simulated VBLANK thread switch. So, we'll just immediately set it to "ready" again.
 | ||||
|         // This results in the current thread yielding on a VBLANK once, and then it will be 
 | ||||
|         // immediately placed back in the queue for execution.
 | ||||
|         if (prev->wait_type == WAITTYPE_VBLANK) { | ||||
|             ResumeThreadFromWait(prev->GetHandle()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ enum WaitType { | |||
|     WAITTYPE_NONE, | ||||
|     WAITTYPE_SLEEP, | ||||
|     WAITTYPE_SEMA, | ||||
|     WAITTYPE_EVENTFLAG, | ||||
|     WAITTYPE_EVENT, | ||||
|     WAITTYPE_THREADEND, | ||||
|     WAITTYPE_VBLANK, | ||||
|     WAITTYPE_MUTEX, | ||||
|  | @ -53,8 +53,8 @@ Handle SetupMainThread(s32 priority, int stack_size=Kernel::DEFAULT_STACK_SIZE); | |||
| /// Reschedules to the next available thread (call after current thread is suspended)
 | ||||
| void Reschedule(); | ||||
| 
 | ||||
| /// Puts the current thread in the wait state for the given type
 | ||||
| void WaitCurrentThread(WaitType wait_type); | ||||
| /// Stops the current thread
 | ||||
| void StopThread(Handle thread, const char* reason); | ||||
| 
 | ||||
| /// Resumes a thread from waiting by marking it as "ready"
 | ||||
| void ResumeThreadFromWait(Handle handle); | ||||
|  | @ -62,9 +62,18 @@ void ResumeThreadFromWait(Handle handle); | |||
| /// Gets the current thread handle
 | ||||
| Handle GetCurrentThreadHandle(); | ||||
| 
 | ||||
| /// Puts the current thread in the wait state for the given type
 | ||||
| void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); | ||||
| 
 | ||||
| /// Put current thread in a wait state - on WaitSynchronization
 | ||||
| void WaitThread_Synchronization(); | ||||
| 
 | ||||
| /// Get the priority of the thread specified by handle
 | ||||
| u32 GetThreadPriority(const Handle handle); | ||||
| 
 | ||||
| /// Set the priority of the thread specified by handle
 | ||||
| Result SetThreadPriority(Handle handle, s32 priority); | ||||
| 
 | ||||
| /// Initialize threading
 | ||||
| void ThreadingInit(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "common/common.h" | ||||
| 
 | ||||
| #include "core/hle/hle.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/mutex.h" | ||||
| #include "core/hle/service/apt.h" | ||||
| 
 | ||||
|  | @ -15,96 +16,120 @@ | |||
| namespace APT_U { | ||||
| 
 | ||||
| void Initialize(Service::Interface* self) { | ||||
|     NOTICE_LOG(OSHLE, "APT_U::Sync - Initialize"); | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     DEBUG_LOG(KERNEL, "called"); | ||||
|      | ||||
|     cmd_buff[3] = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Menu");  // APT menu event handle
 | ||||
|     cmd_buff[4] = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Pause"); // APT pause event handle
 | ||||
| 
 | ||||
|     Kernel::SetEventLocked(cmd_buff[3], true); | ||||
|     Kernel::SetEventLocked(cmd_buff[4], false); // Fire start event
 | ||||
| 
 | ||||
|     cmd_buff[1] = 0; // No error
 | ||||
| } | ||||
| 
 | ||||
| void GetLockHandle(Service::Interface* self) { | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field
 | ||||
|     cmd_buff[1] = 0; // No error
 | ||||
|     cmd_buff[5] = Kernel::CreateMutex(false); | ||||
|     DEBUG_LOG(KERNEL, "APT_U::GetLockHandle called : created handle 0x%08X", cmd_buff[5]); | ||||
|     cmd_buff[5] = Kernel::CreateMutex(false, "APT_U:Lock"); | ||||
|     DEBUG_LOG(KERNEL, "called handle=0x%08X", cmd_buff[5]); | ||||
| } | ||||
| 
 | ||||
| void Enable(Service::Interface* self) { | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     u32 unk = cmd_buff[1]; // TODO(bunnei): What is this field used for?
 | ||||
|     cmd_buff[1] = 0; // No error
 | ||||
|     ERROR_LOG(KERNEL, "(UNIMPEMENTED) called unk=0x%08X", unk); | ||||
| } | ||||
| 
 | ||||
| void InquireNotification(Service::Interface* self) { | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     u32 app_id = cmd_buff[2]; | ||||
|     cmd_buff[1] = 0; // No error
 | ||||
|     cmd_buff[3] = 0; // Signal type
 | ||||
|     ERROR_LOG(KERNEL, "(UNIMPEMENTED) called app_id=0x%08X", app_id); | ||||
| } | ||||
| 
 | ||||
| const Interface::FunctionInfo FunctionTable[] = { | ||||
|     {0x00010040, GetLockHandle, "GetLockHandle"}, | ||||
|     {0x00020080, Initialize,    "Initialize"}, | ||||
|     {0x00030040, NULL,          "Enable"}, | ||||
|     {0x00040040, NULL,          "Finalize"}, | ||||
|     {0x00050040, NULL,          "GetAppletManInfo"}, | ||||
|     {0x00060040, NULL,          "GetAppletInfo"}, | ||||
|     {0x00070000, NULL,          "GetLastSignaledAppletId"}, | ||||
|     {0x00080000, NULL,          "CountRegisteredApplet"}, | ||||
|     {0x00090040, NULL,          "IsRegistered"}, | ||||
|     {0x000A0040, NULL,          "GetAttribute"}, | ||||
|     {0x000B0040, NULL,          "InquireNotification"}, | ||||
|     {0x000C0104, NULL,          "SendParameter"}, | ||||
|     {0x000D0080, NULL,          "ReceiveParameter"}, | ||||
|     {0x000E0080, NULL,          "GlanceParameter"}, | ||||
|     {0x000F0100, NULL,          "CancelParameter"}, | ||||
|     {0x001000C2, NULL,          "DebugFunc"}, | ||||
|     {0x001100C0, NULL,          "MapProgramIdForDebug"}, | ||||
|     {0x00120040, NULL,          "SetHomeMenuAppletIdForDebug"}, | ||||
|     {0x00130000, NULL,          "GetPreparationState"}, | ||||
|     {0x00140040, NULL,          "SetPreparationState"}, | ||||
|     {0x00150140, NULL,          "PrepareToStartApplication"}, | ||||
|     {0x00160040, NULL,          "PreloadLibraryApplet"}, | ||||
|     {0x00170040, NULL,          "FinishPreloadingLibraryApplet"}, | ||||
|     {0x00180040, NULL,          "PrepareToStartLibraryApplet"}, | ||||
|     {0x00190040, NULL,          "PrepareToStartSystemApplet"}, | ||||
|     {0x001A0000, NULL,          "PrepareToStartNewestHomeMenu"}, | ||||
|     {0x001B00C4, NULL,          "StartApplication"}, | ||||
|     {0x001C0000, NULL,          "WakeupApplication"}, | ||||
|     {0x001D0000, NULL,          "CancelApplication"}, | ||||
|     {0x001E0084, NULL,          "StartLibraryApplet"}, | ||||
|     {0x001F0084, NULL,          "StartSystemApplet"}, | ||||
|     {0x00200044, NULL,          "StartNewestHomeMenu"}, | ||||
|     {0x00210000, NULL,          "OrderToCloseApplication"}, | ||||
|     {0x00220040, NULL,          "PrepareToCloseApplication"}, | ||||
|     {0x00230040, NULL,          "PrepareToJumpToApplication"}, | ||||
|     {0x00240044, NULL,          "JumpToApplication"}, | ||||
|     {0x002500C0, NULL,          "PrepareToCloseLibraryApplet"}, | ||||
|     {0x00260000, NULL,          "PrepareToCloseSystemApplet"}, | ||||
|     {0x00270044, NULL,          "CloseApplication"}, | ||||
|     {0x00280044, NULL,          "CloseLibraryApplet"}, | ||||
|     {0x00290044, NULL,          "CloseSystemApplet"}, | ||||
|     {0x002A0000, NULL,          "OrderToCloseSystemApplet"}, | ||||
|     {0x002B0000, NULL,          "PrepareToJumpToHomeMenu"}, | ||||
|     {0x002C0044, NULL,          "JumpToHomeMenu"}, | ||||
|     {0x002D0000, NULL,          "PrepareToLeaveHomeMenu"}, | ||||
|     {0x002E0044, NULL,          "LeaveHomeMenu"}, | ||||
|     {0x002F0040, NULL,          "PrepareToLeaveResidentApplet"}, | ||||
|     {0x00300044, NULL,          "LeaveResidentApplet"}, | ||||
|     {0x00310100, NULL,          "PrepareToDoApplicationJump"}, | ||||
|     {0x00320084, NULL,          "DoApplicationJump"}, | ||||
|     {0x00330000, NULL,          "GetProgramIdOnApplicationJump"}, | ||||
|     {0x00340084, NULL,          "SendDeliverArg"}, | ||||
|     {0x00350080, NULL,          "ReceiveDeliverArg"}, | ||||
|     {0x00360040, NULL,          "LoadSysMenuArg"}, | ||||
|     {0x00370042, NULL,          "StoreSysMenuArg"}, | ||||
|     {0x00380040, NULL,          "PreloadResidentApplet"}, | ||||
|     {0x00390040, NULL,          "PrepareToStartResidentApplet"}, | ||||
|     {0x003A0044, NULL,          "StartResidentApplet"}, | ||||
|     {0x003B0040, NULL,          "CancelLibraryApplet"}, | ||||
|     {0x003C0042, NULL,          "SendDspSleep"}, | ||||
|     {0x003D0042, NULL,          "SendDspWakeUp"}, | ||||
|     {0x003E0080, NULL,          "ReplySleepQuery"}, | ||||
|     {0x003F0040, NULL,          "ReplySleepNotificationComplete"}, | ||||
|     {0x00400042, NULL,          "SendCaptureBufferInfo"}, | ||||
|     {0x00410040, NULL,          "ReceiveCaptureBufferInfo"}, | ||||
|     {0x00420080, NULL,          "SleepSystem"}, | ||||
|     {0x00430040, NULL,          "NotifyToWait"}, | ||||
|     {0x00440000, NULL,          "GetSharedFont"}, | ||||
|     {0x00450040, NULL,          "GetWirelessRebootInfo"}, | ||||
|     {0x00460104, NULL,          "Wrap"}, | ||||
|     {0x00470104, NULL,          "Unwrap"}, | ||||
|     {0x00480100, NULL,          "GetProgramInfo"}, | ||||
|     {0x00490180, NULL,          "Reboot"}, | ||||
|     {0x004A0040, NULL,          "GetCaptureInfo"}, | ||||
|     {0x004B00C2, NULL,          "AppletUtility"}, | ||||
|     {0x004C0000, NULL,          "SetFatalErrDispMode"}, | ||||
|     {0x004D0080, NULL,          "GetAppletProgramInfo"}, | ||||
|     {0x004E0000, NULL,          "HardwareResetAsync"}, | ||||
|     {0x00010040, GetLockHandle,         "GetLockHandle"}, | ||||
|     {0x00020080, Initialize,            "Initialize"}, | ||||
|     {0x00030040, Enable,                "Enable"}, | ||||
|     {0x00040040, nullptr,               "Finalize"}, | ||||
|     {0x00050040, nullptr,               "GetAppletManInfo"}, | ||||
|     {0x00060040, nullptr,               "GetAppletInfo"}, | ||||
|     {0x00070000, nullptr,               "GetLastSignaledAppletId"}, | ||||
|     {0x00080000, nullptr,               "CountRegisteredApplet"}, | ||||
|     {0x00090040, nullptr,               "IsRegistered"}, | ||||
|     {0x000A0040, nullptr,               "GetAttribute"}, | ||||
|     {0x000B0040, InquireNotification,   "InquireNotification"}, | ||||
|     {0x000C0104, nullptr,                "SendParameter"}, | ||||
|     {0x000D0080, nullptr,                "ReceiveParameter"}, | ||||
|     {0x000E0080, nullptr,                "GlanceParameter"}, | ||||
|     {0x000F0100, nullptr,                "CancelParameter"}, | ||||
|     {0x001000C2, nullptr,                "DebugFunc"}, | ||||
|     {0x001100C0, nullptr,                "MapProgramIdForDebug"}, | ||||
|     {0x00120040, nullptr,                "SetHomeMenuAppletIdForDebug"}, | ||||
|     {0x00130000, nullptr,                "GetPreparationState"}, | ||||
|     {0x00140040, nullptr,                "SetPreparationState"}, | ||||
|     {0x00150140, nullptr,                "PrepareToStartApplication"}, | ||||
|     {0x00160040, nullptr,                "PreloadLibraryApplet"}, | ||||
|     {0x00170040, nullptr,                "FinishPreloadingLibraryApplet"}, | ||||
|     {0x00180040, nullptr,                "PrepareToStartLibraryApplet"}, | ||||
|     {0x00190040, nullptr,                "PrepareToStartSystemApplet"}, | ||||
|     {0x001A0000, nullptr,                "PrepareToStartNewestHomeMenu"}, | ||||
|     {0x001B00C4, nullptr,                "StartApplication"}, | ||||
|     {0x001C0000, nullptr,                "WakeupApplication"}, | ||||
|     {0x001D0000, nullptr,                "CancelApplication"}, | ||||
|     {0x001E0084, nullptr,                "StartLibraryApplet"}, | ||||
|     {0x001F0084, nullptr,                "StartSystemApplet"}, | ||||
|     {0x00200044, nullptr,                "StartNewestHomeMenu"}, | ||||
|     {0x00210000, nullptr,                "OrderToCloseApplication"}, | ||||
|     {0x00220040, nullptr,                "PrepareToCloseApplication"}, | ||||
|     {0x00230040, nullptr,                "PrepareToJumpToApplication"}, | ||||
|     {0x00240044, nullptr,                "JumpToApplication"}, | ||||
|     {0x002500C0, nullptr,                "PrepareToCloseLibraryApplet"}, | ||||
|     {0x00260000, nullptr,                "PrepareToCloseSystemApplet"}, | ||||
|     {0x00270044, nullptr,                "CloseApplication"}, | ||||
|     {0x00280044, nullptr,                "CloseLibraryApplet"}, | ||||
|     {0x00290044, nullptr,                "CloseSystemApplet"}, | ||||
|     {0x002A0000, nullptr,                "OrderToCloseSystemApplet"}, | ||||
|     {0x002B0000, nullptr,                "PrepareToJumpToHomeMenu"}, | ||||
|     {0x002C0044, nullptr,                "JumpToHomeMenu"}, | ||||
|     {0x002D0000, nullptr,                "PrepareToLeaveHomeMenu"}, | ||||
|     {0x002E0044, nullptr,                "LeaveHomeMenu"}, | ||||
|     {0x002F0040, nullptr,                "PrepareToLeaveResidentApplet"}, | ||||
|     {0x00300044, nullptr,                "LeaveResidentApplet"}, | ||||
|     {0x00310100, nullptr,                "PrepareToDoApplicationJump"}, | ||||
|     {0x00320084, nullptr,                "DoApplicationJump"}, | ||||
|     {0x00330000, nullptr,                "GetProgramIdOnApplicationJump"}, | ||||
|     {0x00340084, nullptr,                "SendDeliverArg"}, | ||||
|     {0x00350080, nullptr,                "ReceiveDeliverArg"}, | ||||
|     {0x00360040, nullptr,                "LoadSysMenuArg"}, | ||||
|     {0x00370042, nullptr,                "StoreSysMenuArg"}, | ||||
|     {0x00380040, nullptr,                "PreloadResidentApplet"}, | ||||
|     {0x00390040, nullptr,                "PrepareToStartResidentApplet"}, | ||||
|     {0x003A0044, nullptr,                "StartResidentApplet"}, | ||||
|     {0x003B0040, nullptr,                "CancelLibraryApplet"}, | ||||
|     {0x003C0042, nullptr,                "SendDspSleep"}, | ||||
|     {0x003D0042, nullptr,                "SendDspWakeUp"}, | ||||
|     {0x003E0080, nullptr,                "ReplySleepQuery"}, | ||||
|     {0x003F0040, nullptr,                "ReplySleepNotificationComplete"}, | ||||
|     {0x00400042, nullptr,                "SendCaptureBufferInfo"}, | ||||
|     {0x00410040, nullptr,                "ReceiveCaptureBufferInfo"}, | ||||
|     {0x00420080, nullptr,                "SleepSystem"}, | ||||
|     {0x00430040, nullptr,                "NotifyToWait"}, | ||||
|     {0x00440000, nullptr,                "GetSharedFont"}, | ||||
|     {0x00450040, nullptr,                "GetWirelessRebootInfo"}, | ||||
|     {0x00460104, nullptr,                "Wrap"}, | ||||
|     {0x00470104, nullptr,                "Unwrap"}, | ||||
|     {0x00480100, nullptr,                "GetProgramInfo"}, | ||||
|     {0x00490180, nullptr,                "Reboot"}, | ||||
|     {0x004A0040, nullptr,                "GetCaptureInfo"}, | ||||
|     {0x004B00C2, nullptr,                "AppletUtility"}, | ||||
|     {0x004C0000, nullptr,                "SetFatalErrDispMode"}, | ||||
|     {0x004D0080, nullptr,                "GetAppletProgramInfo"}, | ||||
|     {0x004E0000, nullptr,                "HardwareResetAsync"}, | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| 
 | ||||
| #include "core/mem_map.h" | ||||
| #include "core/hle/hle.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/service/gsp.h" | ||||
| 
 | ||||
| #include "core/hw/gpu.h" | ||||
|  | @ -60,6 +61,7 @@ void GX_FinishCommand(u32 thread_id) { | |||
| 
 | ||||
| namespace GSP_GPU { | ||||
| 
 | ||||
| Handle g_event_handle = 0; | ||||
| u32 g_thread_id = 0; | ||||
| 
 | ||||
| enum { | ||||
|  | @ -96,7 +98,7 @@ void ReadHWRegs(Service::Interface* self) { | |||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         ERROR_LOG(GSP, "ReadHWRegs unknown register read at address %08X", reg_addr); | ||||
|         ERROR_LOG(GSP, "unknown register read at address %08X", reg_addr); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -104,7 +106,19 @@ void ReadHWRegs(Service::Interface* self) { | |||
| void RegisterInterruptRelayQueue(Service::Interface* self) { | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     u32 flags = cmd_buff[1]; | ||||
|     u32 event_handle = cmd_buff[3]; // TODO(bunnei): Implement event handling
 | ||||
|     u32 event_handle = cmd_buff[3]; | ||||
| 
 | ||||
|     _assert_msg_(GSP, (event_handle != 0), "called, but event is nullptr!"); | ||||
| 
 | ||||
|     g_event_handle = event_handle; | ||||
| 
 | ||||
|     Kernel::SetEventLocked(event_handle, false); | ||||
| 
 | ||||
|     // Hack - This function will permanently set the state of the GSP event such that GPU command 
 | ||||
|     // synchronization barriers always passthrough. Correct solution would be to set this after the 
 | ||||
|     // GPU as processed all queued up commands, but due to the emulator being single-threaded they
 | ||||
|     // will always be ready.
 | ||||
|     Kernel::SetPermanentLock(event_handle, true); | ||||
| 
 | ||||
|     cmd_buff[2] = g_thread_id;          // ThreadID
 | ||||
| } | ||||
|  | @ -150,43 +164,43 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
|     } | ||||
| 
 | ||||
|     default: | ||||
|         ERROR_LOG(GSP, "TriggerCmdReqQueue unknown command 0x%08X", cmd_buff[0]); | ||||
|         ERROR_LOG(GSP, "unknown command 0x%08X", cmd_buff[0]); | ||||
|     } | ||||
| 
 | ||||
|     GX_FinishCommand(g_thread_id); | ||||
| } | ||||
| 
 | ||||
| const Interface::FunctionInfo FunctionTable[] = { | ||||
|     {0x00010082, NULL,                          "WriteHWRegs"}, | ||||
|     {0x00020084, NULL,                          "WriteHWRegsWithMask"}, | ||||
|     {0x00030082, NULL,                          "WriteHWRegRepeat"}, | ||||
|     {0x00010082, nullptr,                       "WriteHWRegs"}, | ||||
|     {0x00020084, nullptr,                       "WriteHWRegsWithMask"}, | ||||
|     {0x00030082, nullptr,                       "WriteHWRegRepeat"}, | ||||
|     {0x00040080, ReadHWRegs,                    "ReadHWRegs"}, | ||||
|     {0x00050200, NULL,                          "SetBufferSwap"}, | ||||
|     {0x00060082, NULL,                          "SetCommandList"}, | ||||
|     {0x000700C2, NULL,                          "RequestDma"}, | ||||
|     {0x00080082, NULL,                          "FlushDataCache"}, | ||||
|     {0x00090082, NULL,                          "InvalidateDataCache"}, | ||||
|     {0x000A0044, NULL,                          "RegisterInterruptEvents"}, | ||||
|     {0x000B0040, NULL,                          "SetLcdForceBlack"}, | ||||
|     {0x00050200, nullptr,                       "SetBufferSwap"}, | ||||
|     {0x00060082, nullptr,                       "SetCommandList"}, | ||||
|     {0x000700C2, nullptr,                       "RequestDma"}, | ||||
|     {0x00080082, nullptr,                       "FlushDataCache"}, | ||||
|     {0x00090082, nullptr,                       "InvalidateDataCache"}, | ||||
|     {0x000A0044, nullptr,                       "RegisterInterruptEvents"}, | ||||
|     {0x000B0040, nullptr,                       "SetLcdForceBlack"}, | ||||
|     {0x000C0000, TriggerCmdReqQueue,            "TriggerCmdReqQueue"}, | ||||
|     {0x000D0140, NULL,                          "SetDisplayTransfer"}, | ||||
|     {0x000E0180, NULL,                          "SetTextureCopy"}, | ||||
|     {0x000F0200, NULL,                          "SetMemoryFill"}, | ||||
|     {0x00100040, NULL,                          "SetAxiConfigQoSMode"}, | ||||
|     {0x00110040, NULL,                          "SetPerfLogMode"}, | ||||
|     {0x00120000, NULL,                          "GetPerfLog"}, | ||||
|     {0x000D0140, nullptr,                       "SetDisplayTransfer"}, | ||||
|     {0x000E0180, nullptr,                       "SetTextureCopy"}, | ||||
|     {0x000F0200, nullptr,                       "SetMemoryFill"}, | ||||
|     {0x00100040, nullptr,                       "SetAxiConfigQoSMode"}, | ||||
|     {0x00110040, nullptr,                       "SetPerfLogMode"}, | ||||
|     {0x00120000, nullptr,                       "GetPerfLog"}, | ||||
|     {0x00130042, RegisterInterruptRelayQueue,   "RegisterInterruptRelayQueue"}, | ||||
|     {0x00140000, NULL,                          "UnregisterInterruptRelayQueue"}, | ||||
|     {0x00150002, NULL,                          "TryAcquireRight"}, | ||||
|     {0x00160042, NULL,                          "AcquireRight"}, | ||||
|     {0x00170000, NULL,                          "ReleaseRight"}, | ||||
|     {0x00180000, NULL,                          "ImportDisplayCaptureInfo"}, | ||||
|     {0x00190000, NULL,                          "SaveVramSysArea"}, | ||||
|     {0x001A0000, NULL,                          "RestoreVramSysArea"}, | ||||
|     {0x001B0000, NULL,                          "ResetGpuCore"}, | ||||
|     {0x001C0040, NULL,                          "SetLedForceOff"}, | ||||
|     {0x001D0040, NULL,                          "SetTestCommand"}, | ||||
|     {0x001E0080, NULL,                          "SetInternalPriorities"}, | ||||
|     {0x00140000, nullptr,                       "UnregisterInterruptRelayQueue"}, | ||||
|     {0x00150002, nullptr,                       "TryAcquireRight"}, | ||||
|     {0x00160042, nullptr,                       "AcquireRight"}, | ||||
|     {0x00170000, nullptr,                       "ReleaseRight"}, | ||||
|     {0x00180000, nullptr,                       "ImportDisplayCaptureInfo"}, | ||||
|     {0x00190000, nullptr,                       "SaveVramSysArea"}, | ||||
|     {0x001A0000, nullptr,                       "RestoreVramSysArea"}, | ||||
|     {0x001B0000, nullptr,                       "ResetGpuCore"}, | ||||
|     {0x001C0040, nullptr,                       "SetLedForceOff"}, | ||||
|     {0x001D0040, nullptr,                       "SetTestCommand"}, | ||||
|     {0x001E0080, nullptr,                       "SetInternalPriorities"}, | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  |  | |||
|  | @ -13,11 +13,11 @@ | |||
| namespace HID_User { | ||||
| 
 | ||||
| const Interface::FunctionInfo FunctionTable[] = { | ||||
|     {0x000A0000, NULL, "GetIPCHandles"}, | ||||
|     {0x00110000, NULL, "EnableAccelerometer"}, | ||||
|     {0x00130000, NULL, "EnableGyroscopeLow"}, | ||||
|     {0x00150000, NULL, "GetGyroscopeLowRawToDpsCoefficient"}, | ||||
|     {0x00160000, NULL, "GetGyroscopeLowCalibrateParam"}, | ||||
|     {0x000A0000, nullptr, "GetIPCHandles"}, | ||||
|     {0x00110000, nullptr, "EnableAccelerometer"}, | ||||
|     {0x00130000, nullptr, "EnableGyroscopeLow"}, | ||||
|     {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, | ||||
|     {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  |  | |||
							
								
								
									
										32
									
								
								src/core/hle/service/ndm.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/core/hle/service/ndm.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/log.h" | ||||
| 
 | ||||
| #include "core/hle/hle.h" | ||||
| #include "core/hle/service/ndm.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Namespace NDM_U
 | ||||
| 
 | ||||
| namespace NDM_U { | ||||
| 
 | ||||
| const Interface::FunctionInfo FunctionTable[] = { | ||||
|     {0x00060040, nullptr, "SuspendDaemons"}, | ||||
|     {0x00080040, nullptr, "DisableWifiUsage"}, | ||||
|     {0x00090000, nullptr, "EnableWifiUsage"}, | ||||
|     {0x00140040, nullptr, "OverrideDefaultDaemons"}, | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Interface class
 | ||||
| 
 | ||||
| Interface::Interface() { | ||||
|     Register(FunctionTable, ARRAY_SIZE(FunctionTable)); | ||||
| } | ||||
| 
 | ||||
| Interface::~Interface() { | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
							
								
								
									
										33
									
								
								src/core/hle/service/ndm.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/hle/service/ndm.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Namespace NDM
 | ||||
| 
 | ||||
| // No idea what this is
 | ||||
| 
 | ||||
| namespace NDM_U { | ||||
| 
 | ||||
| class Interface : public Service::Interface { | ||||
| public: | ||||
| 
 | ||||
|     Interface(); | ||||
| 
 | ||||
|     ~Interface(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the string port name used by CTROS for the service | ||||
|      * @return Port name of service | ||||
|      */ | ||||
|     const char *GetPortName() const { | ||||
|         return "ndm:u"; | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace
 | ||||
|  | @ -12,13 +12,14 @@ | |||
| #include "core/hle/service/apt.h" | ||||
| #include "core/hle/service/gsp.h" | ||||
| #include "core/hle/service/hid.h" | ||||
| #include "core/hle/service/ndm.h" | ||||
| #include "core/hle/service/srv.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| 
 | ||||
| namespace Service { | ||||
| 
 | ||||
| Manager* g_manager = NULL;  ///< Service manager
 | ||||
| Manager* g_manager = nullptr;  ///< Service manager
 | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Service Manager class
 | ||||
|  | @ -55,7 +56,7 @@ Interface* Manager::FetchFromHandle(Handle handle) { | |||
| Interface* Manager::FetchFromPortName(std::string port_name) { | ||||
|     auto itr = m_port_map.find(port_name); | ||||
|     if (itr == m_port_map.end()) { | ||||
|         return NULL; | ||||
|         return nullptr; | ||||
|     } | ||||
|     return FetchFromHandle(itr->second); | ||||
| } | ||||
|  | @ -72,14 +73,15 @@ void Init() { | |||
|     g_manager->AddService(new APT_U::Interface); | ||||
|     g_manager->AddService(new GSP_GPU::Interface); | ||||
|     g_manager->AddService(new HID_User::Interface); | ||||
|     g_manager->AddService(new NDM_U::Interface); | ||||
| 
 | ||||
|     NOTICE_LOG(HLE, "Services initialized OK"); | ||||
|     NOTICE_LOG(HLE, "initialized OK"); | ||||
| } | ||||
| 
 | ||||
| /// Shutdown ServiceManager
 | ||||
| void Shutdown() { | ||||
|     delete g_manager; | ||||
|     NOTICE_LOG(HLE, "Services shutdown OK"); | ||||
|     NOTICE_LOG(HLE, "shutdown OK"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,8 +39,8 @@ class Interface  : public Kernel::Object { | |||
|     friend class Manager; | ||||
| public: | ||||
|      | ||||
|     const char *GetName() { return GetPortName(); } | ||||
|     const char *GetTypeName() { return GetPortName(); } | ||||
|     const char *GetName() const { return GetPortName(); } | ||||
|     const char *GetTypeName() const { return GetPortName(); } | ||||
| 
 | ||||
|     static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Service; } | ||||
|     Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Service; } | ||||
|  | @ -76,22 +76,31 @@ public: | |||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Called when svcSendSyncRequest is called, loads command buffer and executes comand | ||||
|      * @return Return result of svcSendSyncRequest passed back to user app | ||||
|      * Synchronize kernel object  | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result Sync() { | ||||
|     Result SyncRequest(bool* wait) { | ||||
|         u32* cmd_buff = GetCommandBuffer(); | ||||
|         auto itr = m_functions.find(cmd_buff[0]); | ||||
| 
 | ||||
|         if (itr == m_functions.end()) { | ||||
|             ERROR_LOG(OSHLE, "Unknown/unimplemented function: port = %s, command = 0x%08X!",  | ||||
|             ERROR_LOG(OSHLE, "unknown/unimplemented function: port=%s, command=0x%08X",  | ||||
|                 GetPortName(), cmd_buff[0]); | ||||
|             return -1; | ||||
| 
 | ||||
|             // TODO(bunnei): Hack - ignore error
 | ||||
|             u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|             cmd_buff[1] = 0; | ||||
|             return 0;  | ||||
|         } | ||||
|         if (itr->second.func == NULL) { | ||||
|             ERROR_LOG(OSHLE, "Unimplemented function: port = %s, name = %s!",  | ||||
|         if (itr->second.func == nullptr) { | ||||
|             ERROR_LOG(OSHLE, "unimplemented function: port=%s, name=%s",  | ||||
|                 GetPortName(), itr->second.name.c_str()); | ||||
|             return -1; | ||||
| 
 | ||||
|             // TODO(bunnei): Hack - ignore error
 | ||||
|             u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|             cmd_buff[1] = 0; | ||||
|             return 0;  | ||||
|         }  | ||||
| 
 | ||||
|         itr->second.func(this); | ||||
|  | @ -99,6 +108,17 @@ public: | |||
|         return 0; // TODO: Implement return from actual function
 | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Wait for kernel object to synchronize | ||||
|      * @param wait Boolean wait set if current thread should wait as a result of sync operation | ||||
|      * @return Result of operation, 0 on success, otherwise error code | ||||
|      */ | ||||
|     Result WaitSynchronization(bool* wait) { | ||||
|         // TODO(bunnei): ImplementMe
 | ||||
|         ERROR_LOG(OSHLE, "unimplemented function"); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
| 
 | ||||
|     /**
 | ||||
|  |  | |||
|  | @ -5,21 +5,28 @@ | |||
| #include "core/hle/hle.h" | ||||
| #include "core/hle/service/srv.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/mutex.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Namespace SRV
 | ||||
| 
 | ||||
| namespace SRV { | ||||
| 
 | ||||
| Handle g_mutex = 0; | ||||
| 
 | ||||
| void Initialize(Service::Interface* self) { | ||||
|     NOTICE_LOG(OSHLE, "SRV::Sync - Initialize"); | ||||
|     DEBUG_LOG(OSHLE, "called"); | ||||
|     if (!g_mutex) { | ||||
|         g_mutex = Kernel::CreateMutex(true, "SRV:Lock"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GetProcSemaphore(Service::Interface* self) { | ||||
|     DEBUG_LOG(OSHLE, "called"); | ||||
|     // Get process semaphore?
 | ||||
|     u32* cmd_buff = Service::GetCommandBuffer(); | ||||
|     cmd_buff[3] = 0xDEADBEEF; // Return something... 0 == NULL, raises an exception
 | ||||
|     cmd_buff[1] = 0;        // No error
 | ||||
|     cmd_buff[3] = g_mutex;  // Return something... 0 == nullptr, raises an exception
 | ||||
| } | ||||
| 
 | ||||
| void GetServiceHandle(Service::Interface* self) { | ||||
|  | @ -29,25 +36,21 @@ void GetServiceHandle(Service::Interface* self) { | |||
|     std::string port_name = std::string((const char*)&cmd_buff[1], 0, Service::kMaxPortSize); | ||||
|     Service::Interface* service = Service::g_manager->FetchFromPortName(port_name); | ||||
| 
 | ||||
|     NOTICE_LOG(OSHLE, "SRV::Sync - GetHandle - port: %s, handle: 0x%08X", port_name.c_str(),  | ||||
|         service->GetHandle()); | ||||
| 
 | ||||
|     if (NULL != service) { | ||||
|     if (nullptr != service) { | ||||
|         cmd_buff[3] = service->GetHandle(); | ||||
|         DEBUG_LOG(OSHLE, "called port=%s, handle=0x%08X", port_name.c_str(), cmd_buff[3]); | ||||
|     } else { | ||||
|         ERROR_LOG(OSHLE, "Service %s does not exist", port_name.c_str()); | ||||
|         ERROR_LOG(OSHLE, "(UNIMPLEMENTED) called port=%s", port_name.c_str()); | ||||
|         res = -1; | ||||
|     } | ||||
|     cmd_buff[1] = res; | ||||
| 
 | ||||
|     //return res;
 | ||||
| } | ||||
| 
 | ||||
| const Interface::FunctionInfo FunctionTable[] = { | ||||
|     {0x00010002, Initialize,        "Initialize"}, | ||||
|     {0x00020000, GetProcSemaphore,  "GetProcSemaphore"}, | ||||
|     {0x00030100, NULL,              "RegisterService"}, | ||||
|     {0x000400C0, NULL,              "UnregisterService"}, | ||||
|     {0x00030100, nullptr,           "RegisterService"}, | ||||
|     {0x000400C0, nullptr,           "UnregisterService"}, | ||||
|     {0x00050100, GetServiceHandle,  "GetServiceHandle"}, | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,12 +26,6 @@ public: | |||
|         return "srv:"; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Called when svcSendSyncRequest is called, loads command buffer and executes comand | ||||
|      * @return Return result of svcSendSyncRequest passed back to user app | ||||
|      */ | ||||
|     Result Sync(); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| 
 | ||||
| #include "core/mem_map.h" | ||||
| 
 | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/mutex.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
|  | @ -16,7 +17,6 @@ | |||
| #include "core/hle/function_wrappers.h" | ||||
| #include "core/hle/svc.h" | ||||
| #include "core/hle/service/service.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Namespace SVC
 | ||||
|  | @ -34,40 +34,32 @@ enum MapMemoryPermission { | |||
| }; | ||||
| 
 | ||||
| /// Map application or GSP heap memory
 | ||||
| Result ControlMemory(void* _outaddr, u32 operation, u32 addr0, u32 addr1, u32 size, u32 permissions) { | ||||
|     u32* outaddr = (u32*)_outaddr; | ||||
|     u32 virtual_address = 0x00000000; | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "ControlMemory called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=%08X, permissions=0x%08X",  | ||||
| Result ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 addr1, u32 size, u32 permissions) { | ||||
|     DEBUG_LOG(SVC,"called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=%08X, permissions=0x%08X",  | ||||
|         operation, addr0, addr1, size, permissions); | ||||
| 
 | ||||
|     switch (operation) { | ||||
| 
 | ||||
|     // Map normal heap memory
 | ||||
|     case MEMORY_OPERATION_HEAP: | ||||
|         virtual_address = Memory::MapBlock_Heap(size, operation, permissions); | ||||
|         *out_addr = Memory::MapBlock_Heap(size, operation, permissions); | ||||
|         break; | ||||
| 
 | ||||
|     // Map GSP heap memory
 | ||||
|     case MEMORY_OPERATION_GSP_HEAP: | ||||
|         virtual_address = Memory::MapBlock_HeapGSP(size, operation, permissions); | ||||
|         *out_addr = Memory::MapBlock_HeapGSP(size, operation, permissions); | ||||
|         break; | ||||
| 
 | ||||
|     // Unknown ControlMemory operation
 | ||||
|     default: | ||||
|         ERROR_LOG(SVC, "ControlMemory unknown operation=0x%08X", operation); | ||||
|         ERROR_LOG(SVC, "unknown operation=0x%08X", operation); | ||||
|     } | ||||
|     if (NULL != outaddr) { | ||||
|         *outaddr = virtual_address; | ||||
|     } | ||||
|     Core::g_app_core->SetReg(1, virtual_address); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Maps a memory block to specified address
 | ||||
| Result MapMemoryBlock(Handle memblock, u32 addr, u32 mypermissions, u32 otherpermission) { | ||||
|     DEBUG_LOG(SVC, "MapMemoryBlock called memblock=0x08X, addr=0x%08X, mypermissions=0x%08X, otherpermission=%d",  | ||||
|     DEBUG_LOG(SVC, "called memblock=0x08X, addr=0x%08X, mypermissions=0x%08X, otherpermission=%d",  | ||||
|         memblock, addr, mypermissions, otherpermission); | ||||
|     switch (mypermissions) { | ||||
|     case MEMORY_PERMISSION_NORMAL: | ||||
|  | @ -76,88 +68,146 @@ Result MapMemoryBlock(Handle memblock, u32 addr, u32 mypermissions, u32 otherper | |||
|         Memory::MapBlock_Shared(memblock, addr, mypermissions); | ||||
|         break; | ||||
|     default: | ||||
|         ERROR_LOG(OSHLE, "MapMemoryBlock unknown permissions=0x%08X", mypermissions); | ||||
|         ERROR_LOG(OSHLE, "unknown permissions=0x%08X", mypermissions); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Connect to an OS service given the port name, returns the handle to the port to out
 | ||||
| Result ConnectToPort(void* out, const char* port_name) { | ||||
| Result ConnectToPort(Handle* out, const char* port_name) { | ||||
|     Service::Interface* service = Service::g_manager->FetchFromPortName(port_name); | ||||
|     if (service) { | ||||
|         Core::g_app_core->SetReg(1, service->GetHandle()); | ||||
|     } else { | ||||
|         PanicYesNo("ConnectToPort called port_name=%s, but it is not implemented!", port_name); | ||||
|     } | ||||
|     DEBUG_LOG(SVC, "ConnectToPort called port_name=%s", port_name); | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "called port_name=%s", port_name); | ||||
|     _assert_msg_(KERNEL, (service != nullptr), "called, but service is not implemented!"); | ||||
| 
 | ||||
|     *out = service->GetHandle(); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Synchronize to an OS service
 | ||||
| Result SendSyncRequest(Handle handle) { | ||||
|     DEBUG_LOG(SVC, "SendSyncRequest called handle=0x%08X"); | ||||
|     Service::Interface* service = Service::g_manager->FetchFromHandle(handle); | ||||
|     service->Sync(); | ||||
|     return 0; | ||||
|     Kernel::Object* object = Kernel::g_object_pool.GetFast<Kernel::Object>(handle); | ||||
| 
 | ||||
|     _assert_msg_(KERNEL, (object != nullptr), "called, but kernel object is nullptr!"); | ||||
|     DEBUG_LOG(SVC, "called handle=0x%08X(%s)", handle, object->GetTypeName()); | ||||
| 
 | ||||
|     bool wait = false; | ||||
|     Result res = object->SyncRequest(&wait); | ||||
|     if (wait) { | ||||
|         Kernel::WaitCurrentThread(WAITTYPE_SYNCH); // TODO(bunnei): Is this correct?
 | ||||
|     } | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| /// Close a handle
 | ||||
| Result CloseHandle(Handle handle) { | ||||
|     // ImplementMe
 | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) CloseHandle called handle=0x%08X", handle); | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called handle=0x%08X", handle); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Wait for a handle to synchronize, timeout after the specified nanoseconds
 | ||||
| Result WaitSynchronization1(Handle handle, s64 nano_seconds) { | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) WaitSynchronization1 called handle=0x%08X, nanoseconds=%d",  | ||||
|         handle, nano_seconds); | ||||
|     Kernel::WaitCurrentThread(WAITTYPE_SYNCH); // TODO(bunnei): Is this correct?
 | ||||
|     return 0; | ||||
|     // TODO(bunnei): Do something with nano_seconds, currently ignoring this
 | ||||
|     bool wait = false; | ||||
|     bool wait_infinite = (nano_seconds == -1); // Used to wait until a thread has terminated
 | ||||
| 
 | ||||
|     Kernel::Object* object = Kernel::g_object_pool.GetFast<Kernel::Object>(handle); | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "called handle=0x%08X(%s:%s), nanoseconds=%d", handle, object->GetTypeName(),  | ||||
|             object->GetName(), nano_seconds); | ||||
| 
 | ||||
|     _assert_msg_(KERNEL, (object != nullptr), "called, but kernel object is nullptr!"); | ||||
| 
 | ||||
|     Result res = object->WaitSynchronization(&wait); | ||||
| 
 | ||||
|     // Check for next thread to schedule
 | ||||
|     if (wait) { | ||||
|         HLE::Reschedule(__func__); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| /// Wait for the given handles to synchronize, timeout after the specified nanoseconds
 | ||||
| Result WaitSynchronizationN(void* _out, void* _handles, u32 handle_count, u32 wait_all, s64 nano_seconds) { | ||||
|     s32* out = (s32*)_out; | ||||
|     Handle* handles = (Handle*)_handles; | ||||
| Result WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all,  | ||||
|     s64 nano_seconds) { | ||||
|     // TODO(bunnei): Do something with nano_seconds, currently ignoring this
 | ||||
|     bool unlock_all = true; | ||||
|     bool wait_infinite = (nano_seconds == -1); // Used to wait until a thread has terminated
 | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) WaitSynchronizationN called handle_count=%d, wait_all=%s, nanoseconds=%d %s",  | ||||
|     DEBUG_LOG(SVC, "called handle_count=%d, wait_all=%s, nanoseconds=%d",  | ||||
|         handle_count, (wait_all ? "true" : "false"), nano_seconds); | ||||
| 
 | ||||
|     for (u32 i = 0; i < handle_count; i++) { | ||||
|         DEBUG_LOG(SVC, "\thandle[%d]=0x%08X", i, handles[i]); | ||||
|     // Iterate through each handle, synchronize kernel object
 | ||||
|     for (s32 i = 0; i < handle_count; i++) { | ||||
|         bool wait = false; | ||||
|         Kernel::Object* object = Kernel::g_object_pool.GetFast<Kernel::Object>(handles[i]); | ||||
| 
 | ||||
|         _assert_msg_(KERNEL, (object != nullptr), "called handle=0x%08X, but kernel object " | ||||
|             "is nullptr!", handles[i]); | ||||
| 
 | ||||
|         DEBUG_LOG(SVC, "\thandle[%d] = 0x%08X(%s:%s)", i, handles[i], object->GetTypeName(),  | ||||
|             object->GetName()); | ||||
| 
 | ||||
|         Result res = object->WaitSynchronization(&wait); | ||||
| 
 | ||||
|         if (!wait && !wait_all) { | ||||
|             *out = i; | ||||
|             return 0; | ||||
|         } else { | ||||
|             unlock_all = false; | ||||
|         } | ||||
|     } | ||||
|     Kernel::WaitCurrentThread(WAITTYPE_SYNCH); // TODO(bunnei): Is this correct?
 | ||||
| 
 | ||||
|     if (wait_all && unlock_all) { | ||||
|         *out = handle_count; | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     // Check for next thread to schedule
 | ||||
|     HLE::Reschedule(__func__); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Create an address arbiter (to allocate access to shared resources)
 | ||||
| Result CreateAddressArbiter(void* arbiter) { | ||||
|     // ImplementMe
 | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) CreateAddressArbiter called"); | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called"); | ||||
|     Core::g_app_core->SetReg(1, 0xFABBDADD); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Arbitrate address
 | ||||
| Result ArbitrateAddress(Handle arbiter, u32 addr, u32 _type, u32 value, s64 nanoseconds) { | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called"); | ||||
|     ArbitrationType type = (ArbitrationType)_type; | ||||
|     Memory::Write32(addr, type); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Used to output a message on a debug hardware unit - does nothing on a retail unit
 | ||||
| void OutputDebugString(const char* string) { | ||||
|     NOTICE_LOG(SVC, "## OSDEBUG: %08X %s", Core::g_app_core->GetPC(), string); | ||||
|     OS_LOG(SVC, "%s", string); | ||||
| } | ||||
| 
 | ||||
| /// Get resource limit
 | ||||
| Result GetResourceLimit(void* resource_limit, Handle process) { | ||||
| Result GetResourceLimit(Handle* resource_limit, Handle process) { | ||||
|     // With regards to proceess values:
 | ||||
|     // 0xFFFF8001 is a handle alias for the current KProcess, and 0xFFFF8000 is a handle alias for 
 | ||||
|     // the current KThread.
 | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) GetResourceLimit called process=0x%08X", process); | ||||
|     Core::g_app_core->SetReg(1, 0xDEADBEEF); | ||||
|     *resource_limit = 0xDEADBEEF; | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called process=0x%08X", process); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Get resource limit current values
 | ||||
| Result GetResourceLimitCurrentValues(void* _values, Handle resource_limit, void* names, s32 name_count) { | ||||
|     //s64* values = (s64*)_values;
 | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) GetResourceLimitCurrentValues called resource_limit=%08X, names=%s, name_count=%d", | ||||
| Result GetResourceLimitCurrentValues(s64* values, Handle resource_limit, void* names,  | ||||
|     s32 name_count) { | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called resource_limit=%08X, names=%s, name_count=%d", | ||||
|         resource_limit, names, name_count); | ||||
|     Memory::Write32(Core::g_app_core->GetReg(0), 0); // Normmatt: Set used memory to 0 for now
 | ||||
|     return 0; | ||||
|  | @ -180,179 +230,234 @@ Result CreateThread(u32 priority, u32 entry_point, u32 arg, u32 stack_top, u32 p | |||
| 
 | ||||
|     Core::g_app_core->SetReg(1, thread); | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "CreateThread called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " | ||||
|         "threadpriority=0x%08X, processorid=0x%08X : created handle 0x%08X", entry_point,  | ||||
|     DEBUG_LOG(SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " | ||||
|         "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point,  | ||||
|         name.c_str(), arg, stack_top, priority, processor_id, thread); | ||||
|      | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Called when a thread exits
 | ||||
| u32 ExitThread() { | ||||
|     Handle thread = Kernel::GetCurrentThreadHandle(); | ||||
| 
 | ||||
|     DEBUG_LOG(SVC, "called, pc=0x%08X", Core::g_app_core->GetPC()); // PC = 0x0010545C
 | ||||
| 
 | ||||
|     Kernel::StopThread(thread, __func__); | ||||
|     HLE::Reschedule(__func__); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Gets the priority for the specified thread
 | ||||
| Result GetThreadPriority(s32* priority, Handle handle) { | ||||
|     *priority = Kernel::GetThreadPriority(handle); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Sets the priority for the specified thread
 | ||||
| Result SetThreadPriority(Handle handle, s32 priority) { | ||||
|     return Kernel::SetThreadPriority(handle, priority); | ||||
| } | ||||
| 
 | ||||
| /// Create a mutex
 | ||||
| Result CreateMutex(void* _mutex, u32 initial_locked) { | ||||
|     Handle* mutex = (Handle*)_mutex; | ||||
| Result CreateMutex(Handle* mutex, u32 initial_locked) { | ||||
|     *mutex = Kernel::CreateMutex((initial_locked != 0)); | ||||
|     Core::g_app_core->SetReg(1, *mutex); | ||||
|     DEBUG_LOG(SVC, "CreateMutex called initial_locked=%s : created handle 0x%08X",  | ||||
|     DEBUG_LOG(SVC, "called initial_locked=%s : created handle=0x%08X",  | ||||
|         initial_locked ? "true" : "false", *mutex); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Release a mutex
 | ||||
| Result ReleaseMutex(Handle handle) { | ||||
|     DEBUG_LOG(SVC, "ReleaseMutex called handle=0x%08X", handle); | ||||
|     DEBUG_LOG(SVC, "called handle=0x%08X", handle); | ||||
|     _assert_msg_(KERNEL, (handle != 0), "called, but handle is nullptr!"); | ||||
|     Kernel::ReleaseMutex(handle); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Get current thread ID
 | ||||
| Result GetThreadId(void* thread_id, u32 thread) { | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) GetThreadId called thread=0x%08X", thread); | ||||
| Result GetThreadId(u32* thread_id, Handle thread) { | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called thread=0x%08X", thread); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Query memory
 | ||||
| Result QueryMemory(void *_info, void *_out, u32 addr) { | ||||
|     MemoryInfo* info = (MemoryInfo*) _info; | ||||
|     PageInfo* out = (PageInfo*) _out; | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) QueryMemory called addr=0x%08X", addr); | ||||
| Result QueryMemory(void* info, void* out, u32 addr) { | ||||
|     ERROR_LOG(SVC, "(UNIMPLEMENTED) called addr=0x%08X", addr); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Create an event
 | ||||
| Result CreateEvent(void* _event, u32 reset_type) { | ||||
|     Handle* event = (Handle*)_event; | ||||
|     DEBUG_LOG(SVC, "(UNIMPLEMENTED) CreateEvent called reset_type=0x%08X", reset_type); | ||||
|     Core::g_app_core->SetReg(1, 0xBADC0DE0); | ||||
| Result CreateEvent(Handle* evt, u32 reset_type) { | ||||
|     *evt = Kernel::CreateEvent((ResetType)reset_type); | ||||
|     DEBUG_LOG(SVC, "called reset_type=0x%08X : created handle=0x%08X",  | ||||
|         reset_type, *evt); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Duplicates a kernel handle
 | ||||
| Result DuplicateHandle(Handle* out, Handle handle) { | ||||
|     DEBUG_LOG(SVC, "called handle=0x%08X", handle); | ||||
| 
 | ||||
|     // Translate kernel handles -> real handles
 | ||||
|     if (handle == Kernel::CurrentThread) { | ||||
|         handle = Kernel::GetCurrentThreadHandle(); | ||||
|     } | ||||
|     _assert_msg_(KERNEL, (handle != Kernel::CurrentProcess), | ||||
|         "(UNIMPLEMENTED) process handle duplication!"); | ||||
|      | ||||
|     // TODO(bunnei): FixMe - This is a hack to return the handle that we were asked to duplicate.
 | ||||
|     *out = handle; | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Signals an event
 | ||||
| Result SignalEvent(Handle evt) { | ||||
|     Result res = Kernel::SignalEvent(evt); | ||||
|     DEBUG_LOG(SVC, "called event=0x%08X", evt); | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| /// Clears an event
 | ||||
| Result ClearEvent(Handle evt) { | ||||
|     Result res = Kernel::ClearEvent(evt); | ||||
|     DEBUG_LOG(SVC, "called event=0x%08X", evt); | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| /// Sleep the current thread
 | ||||
| void SleepThread(s64 nanoseconds) { | ||||
|     DEBUG_LOG(SVC, "called nanoseconds=%d", nanoseconds); | ||||
| } | ||||
| 
 | ||||
| const HLE::FunctionDef SVC_Table[] = { | ||||
|     {0x00,  NULL,                                       "Unknown"}, | ||||
|     {0x01,  WrapI_VUUUUU<ControlMemory>,                "ControlMemory"}, | ||||
|     {0x02,  WrapI_VVU<QueryMemory>,                     "QueryMemory"}, | ||||
|     {0x03,  NULL,                                       "ExitProcess"}, | ||||
|     {0x04,  NULL,                                       "GetProcessAffinityMask"}, | ||||
|     {0x05,  NULL,                                       "SetProcessAffinityMask"}, | ||||
|     {0x06,  NULL,                                       "GetProcessIdealProcessor"}, | ||||
|     {0x07,  NULL,                                       "SetProcessIdealProcessor"}, | ||||
|     {0x08,  WrapI_UUUUU<CreateThread>,                  "CreateThread"}, | ||||
|     {0x09,  NULL,                                       "ExitThread"}, | ||||
|     {0x0A,  NULL,                                       "SleepThread"}, | ||||
|     {0x0B,  NULL,                                       "GetThreadPriority"}, | ||||
|     {0x0C,  NULL,                                       "SetThreadPriority"}, | ||||
|     {0x0D,  NULL,                                       "GetThreadAffinityMask"}, | ||||
|     {0x0E,  NULL,                                       "SetThreadAffinityMask"}, | ||||
|     {0x0F,  NULL,                                       "GetThreadIdealProcessor"}, | ||||
|     {0x10,  NULL,                                       "SetThreadIdealProcessor"}, | ||||
|     {0x11,  NULL,                                       "GetCurrentProcessorNumber"}, | ||||
|     {0x12,  NULL,                                       "Run"}, | ||||
|     {0x13,  WrapI_VU<CreateMutex>,                      "CreateMutex"}, | ||||
|     {0x14,  WrapI_U<ReleaseMutex>,                      "ReleaseMutex"}, | ||||
|     {0x15,  NULL,                                       "CreateSemaphore"}, | ||||
|     {0x16,  NULL,                                       "ReleaseSemaphore"}, | ||||
|     {0x17,  WrapI_VU<CreateEvent>,                      "CreateEvent"}, | ||||
|     {0x18,  NULL,                                       "SignalEvent"}, | ||||
|     {0x19,  NULL,                                       "ClearEvent"}, | ||||
|     {0x1A,  NULL,                                       "CreateTimer"}, | ||||
|     {0x1B,  NULL,                                       "SetTimer"}, | ||||
|     {0x1C,  NULL,                                       "CancelTimer"}, | ||||
|     {0x1D,  NULL,                                       "ClearTimer"}, | ||||
|     {0x1E,  NULL,                                       "CreateMemoryBlock"}, | ||||
|     {0x1F,  WrapI_UUUU<MapMemoryBlock>,                 "MapMemoryBlock"}, | ||||
|     {0x20,  NULL,                                       "UnmapMemoryBlock"}, | ||||
|     {0x21,  WrapI_V<CreateAddressArbiter>,              "CreateAddressArbiter"}, | ||||
|     {0x22,  NULL,                                       "ArbitrateAddress"}, | ||||
|     {0x23,  WrapI_U<CloseHandle>,                       "CloseHandle"}, | ||||
|     {0x24,  WrapI_US64<WaitSynchronization1>,           "WaitSynchronization1"}, | ||||
|     {0x25,  WrapI_VVUUS64<WaitSynchronizationN>,        "WaitSynchronizationN"}, | ||||
|     {0x26,  NULL,                                       "SignalAndWait"}, | ||||
|     {0x27,  NULL,                                       "DuplicateHandle"}, | ||||
|     {0x28,  NULL,                                       "GetSystemTick"}, | ||||
|     {0x29,  NULL,                                       "GetHandleInfo"}, | ||||
|     {0x2A,  NULL,                                       "GetSystemInfo"}, | ||||
|     {0x2B,  NULL,                                       "GetProcessInfo"}, | ||||
|     {0x2C,  NULL,                                       "GetThreadInfo"}, | ||||
|     {0x2D,  WrapI_VC<ConnectToPort>,                    "ConnectToPort"}, | ||||
|     {0x2E,  NULL,                                       "SendSyncRequest1"}, | ||||
|     {0x2F,  NULL,                                       "SendSyncRequest2"}, | ||||
|     {0x30,  NULL,                                       "SendSyncRequest3"}, | ||||
|     {0x31,  NULL,                                       "SendSyncRequest4"}, | ||||
|     {0x32,  WrapI_U<SendSyncRequest>,                   "SendSyncRequest"}, | ||||
|     {0x33,  NULL,                                       "OpenProcess"}, | ||||
|     {0x34,  NULL,                                       "OpenThread"}, | ||||
|     {0x35,  NULL,                                       "GetProcessId"}, | ||||
|     {0x36,  NULL,                                       "GetProcessIdOfThread"}, | ||||
|     {0x37,  WrapI_VU<GetThreadId>,                      "GetThreadId"}, | ||||
|     {0x38,  WrapI_VU<GetResourceLimit>,                 "GetResourceLimit"}, | ||||
|     {0x39,  NULL,                                       "GetResourceLimitLimitValues"}, | ||||
|     {0x3A,  WrapI_VUVI<GetResourceLimitCurrentValues>,  "GetResourceLimitCurrentValues"}, | ||||
|     {0x3B,  NULL,                                       "GetThreadContext"}, | ||||
|     {0x3C,  NULL,                                       "Break"}, | ||||
|     {0x3D,  WrapV_C<OutputDebugString>,                 "OutputDebugString"}, | ||||
|     {0x3E,  NULL,                                       "ControlPerformanceCounter"}, | ||||
|     {0x3F,  NULL,                                       "Unknown"}, | ||||
|     {0x40,  NULL,                                       "Unknown"}, | ||||
|     {0x41,  NULL,                                       "Unknown"}, | ||||
|     {0x42,  NULL,                                       "Unknown"}, | ||||
|     {0x43,  NULL,                                       "Unknown"}, | ||||
|     {0x44,  NULL,                                       "Unknown"}, | ||||
|     {0x45,  NULL,                                       "Unknown"}, | ||||
|     {0x46,  NULL,                                       "Unknown"}, | ||||
|     {0x47,  NULL,                                       "CreatePort"}, | ||||
|     {0x48,  NULL,                                       "CreateSessionToPort"}, | ||||
|     {0x49,  NULL,                                       "CreateSession"}, | ||||
|     {0x4A,  NULL,                                       "AcceptSession"}, | ||||
|     {0x4B,  NULL,                                       "ReplyAndReceive1"}, | ||||
|     {0x4C,  NULL,                                       "ReplyAndReceive2"}, | ||||
|     {0x4D,  NULL,                                       "ReplyAndReceive3"}, | ||||
|     {0x4E,  NULL,                                       "ReplyAndReceive4"}, | ||||
|     {0x4F,  NULL,                                       "ReplyAndReceive"}, | ||||
|     {0x50,  NULL,                                       "BindInterrupt"}, | ||||
|     {0x51,  NULL,                                       "UnbindInterrupt"}, | ||||
|     {0x52,  NULL,                                       "InvalidateProcessDataCache"}, | ||||
|     {0x53,  NULL,                                       "StoreProcessDataCache"}, | ||||
|     {0x54,  NULL,                                       "FlushProcessDataCache"}, | ||||
|     {0x55,  NULL,                                       "StartInterProcessDma"}, | ||||
|     {0x56,  NULL,                                       "StopDma"}, | ||||
|     {0x57,  NULL,                                       "GetDmaState"}, | ||||
|     {0x58,  NULL,                                       "RestartDma"}, | ||||
|     {0x59,  NULL,                                       "Unknown"}, | ||||
|     {0x5A,  NULL,                                       "Unknown"}, | ||||
|     {0x5B,  NULL,                                       "Unknown"}, | ||||
|     {0x5C,  NULL,                                       "Unknown"}, | ||||
|     {0x5D,  NULL,                                       "Unknown"}, | ||||
|     {0x5E,  NULL,                                       "Unknown"}, | ||||
|     {0x5F,  NULL,                                       "Unknown"}, | ||||
|     {0x60,  NULL,                                       "DebugActiveProcess"}, | ||||
|     {0x61,  NULL,                                       "BreakDebugProcess"}, | ||||
|     {0x62,  NULL,                                       "TerminateDebugProcess"}, | ||||
|     {0x63,  NULL,                                       "GetProcessDebugEvent"}, | ||||
|     {0x64,  NULL,                                       "ContinueDebugEvent"}, | ||||
|     {0x65,  NULL,                                       "GetProcessList"}, | ||||
|     {0x66,  NULL,                                       "GetThreadList"}, | ||||
|     {0x67,  NULL,                                       "GetDebugThreadContext"}, | ||||
|     {0x68,  NULL,                                       "SetDebugThreadContext"}, | ||||
|     {0x69,  NULL,                                       "QueryDebugProcessMemory"}, | ||||
|     {0x6A,  NULL,                                       "ReadProcessMemory"}, | ||||
|     {0x6B,  NULL,                                       "WriteProcessMemory"}, | ||||
|     {0x6C,  NULL,                                       "SetHardwareBreakPoint"}, | ||||
|     {0x6D,  NULL,                                       "GetDebugThreadParam"}, | ||||
|     {0x6E,  NULL,                                       "Unknown"}, | ||||
|     {0x6F,  NULL,                                       "Unknown"}, | ||||
|     {0x70,  NULL,                                       "ControlProcessMemory"}, | ||||
|     {0x71,  NULL,                                       "MapProcessMemory"}, | ||||
|     {0x72,  NULL,                                       "UnmapProcessMemory"}, | ||||
|     {0x73,  NULL,                                       "Unknown"}, | ||||
|     {0x74,  NULL,                                       "Unknown"}, | ||||
|     {0x75,  NULL,                                       "Unknown"}, | ||||
|     {0x76,  NULL,                                       "TerminateProcess"}, | ||||
|     {0x77,  NULL,                                       "Unknown"}, | ||||
|     {0x78,  NULL,                                       "CreateResourceLimit"}, | ||||
|     {0x79,  NULL,                                       "Unknown"}, | ||||
|     {0x7A,  NULL,                                       "Unknown"}, | ||||
|     {0x7B,  NULL,                                       "Unknown"}, | ||||
|     {0x7C,  NULL,                                       "KernelSetState"}, | ||||
|     {0x7D,  NULL,                                       "QueryProcessMemory"}, | ||||
|     {0x00, nullptr,                         "Unknown"}, | ||||
|     {0x01, HLE::Wrap<ControlMemory>,        "ControlMemory"}, | ||||
|     {0x02, HLE::Wrap<QueryMemory>,          "QueryMemory"}, | ||||
|     {0x03, nullptr,                         "ExitProcess"}, | ||||
|     {0x04, nullptr,                         "GetProcessAffinityMask"}, | ||||
|     {0x05, nullptr,                         "SetProcessAffinityMask"}, | ||||
|     {0x06, nullptr,                         "GetProcessIdealProcessor"}, | ||||
|     {0x07, nullptr,                         "SetProcessIdealProcessor"}, | ||||
|     {0x08, HLE::Wrap<CreateThread>,         "CreateThread"}, | ||||
|     {0x09, HLE::Wrap<ExitThread>,           "ExitThread"}, | ||||
|     {0x0A, HLE::Wrap<SleepThread>,          "SleepThread"}, | ||||
|     {0x0B, HLE::Wrap<GetThreadPriority>,    "GetThreadPriority"}, | ||||
|     {0x0C, HLE::Wrap<SetThreadPriority>,    "SetThreadPriority"}, | ||||
|     {0x0D, nullptr,                         "GetThreadAffinityMask"}, | ||||
|     {0x0E, nullptr,                         "SetThreadAffinityMask"}, | ||||
|     {0x0F, nullptr,                         "GetThreadIdealProcessor"}, | ||||
|     {0x10, nullptr,                         "SetThreadIdealProcessor"}, | ||||
|     {0x11, nullptr,                         "GetCurrentProcessorNumber"}, | ||||
|     {0x12, nullptr,                         "Run"}, | ||||
|     {0x13, HLE::Wrap<CreateMutex>,          "CreateMutex"}, | ||||
|     {0x14, HLE::Wrap<ReleaseMutex>,         "ReleaseMutex"}, | ||||
|     {0x15, nullptr,                         "CreateSemaphore"}, | ||||
|     {0x16, nullptr,                         "ReleaseSemaphore"}, | ||||
|     {0x17, HLE::Wrap<CreateEvent>,          "CreateEvent"}, | ||||
|     {0x18, HLE::Wrap<SignalEvent>,          "SignalEvent"}, | ||||
|     {0x19, HLE::Wrap<ClearEvent>,           "ClearEvent"}, | ||||
|     {0x1A, nullptr,                         "CreateTimer"}, | ||||
|     {0x1B, nullptr,                         "SetTimer"}, | ||||
|     {0x1C, nullptr,                         "CancelTimer"}, | ||||
|     {0x1D, nullptr,                         "ClearTimer"}, | ||||
|     {0x1E, nullptr,                         "CreateMemoryBlock"}, | ||||
|     {0x1F, HLE::Wrap<MapMemoryBlock>,       "MapMemoryBlock"}, | ||||
|     {0x20, nullptr,                         "UnmapMemoryBlock"}, | ||||
|     {0x21, HLE::Wrap<CreateAddressArbiter>, "CreateAddressArbiter"}, | ||||
|     {0x22, HLE::Wrap<ArbitrateAddress>,     "ArbitrateAddress"}, | ||||
|     {0x23, HLE::Wrap<CloseHandle>,          "CloseHandle"}, | ||||
|     {0x24, HLE::Wrap<WaitSynchronization1>, "WaitSynchronization1"}, | ||||
|     {0x25, HLE::Wrap<WaitSynchronizationN>, "WaitSynchronizationN"}, | ||||
|     {0x26, nullptr,                         "SignalAndWait"}, | ||||
|     {0x27, HLE::Wrap<DuplicateHandle>,      "DuplicateHandle"}, | ||||
|     {0x28, nullptr,                         "GetSystemTick"}, | ||||
|     {0x29, nullptr,                         "GetHandleInfo"}, | ||||
|     {0x2A, nullptr,                         "GetSystemInfo"}, | ||||
|     {0x2B, nullptr,                         "GetProcessInfo"}, | ||||
|     {0x2C, nullptr,                         "GetThreadInfo"}, | ||||
|     {0x2D, HLE::Wrap<ConnectToPort>,        "ConnectToPort"}, | ||||
|     {0x2E, nullptr,                         "SendSyncRequest1"}, | ||||
|     {0x2F, nullptr,                         "SendSyncRequest2"}, | ||||
|     {0x30, nullptr,                         "SendSyncRequest3"}, | ||||
|     {0x31, nullptr,                         "SendSyncRequest4"}, | ||||
|     {0x32, HLE::Wrap<SendSyncRequest>,      "SendSyncRequest"}, | ||||
|     {0x33, nullptr,                         "OpenProcess"}, | ||||
|     {0x34, nullptr,                         "OpenThread"}, | ||||
|     {0x35, nullptr,                         "GetProcessId"}, | ||||
|     {0x36, nullptr,                         "GetProcessIdOfThread"}, | ||||
|     {0x37, HLE::Wrap<GetThreadId>,          "GetThreadId"}, | ||||
|     {0x38, HLE::Wrap<GetResourceLimit>,     "GetResourceLimit"}, | ||||
|     {0x39, nullptr,                         "GetResourceLimitLimitValues"}, | ||||
|     {0x3A, HLE::Wrap<GetResourceLimitCurrentValues>, "GetResourceLimitCurrentValues"}, | ||||
|     {0x3B, nullptr,                         "GetThreadContext"}, | ||||
|     {0x3C, nullptr,                         "Break"}, | ||||
|     {0x3D, HLE::Wrap<OutputDebugString>,    "OutputDebugString"}, | ||||
|     {0x3E, nullptr,                         "ControlPerformanceCounter"}, | ||||
|     {0x3F, nullptr,                         "Unknown"}, | ||||
|     {0x40, nullptr,                         "Unknown"}, | ||||
|     {0x41, nullptr,                         "Unknown"}, | ||||
|     {0x42, nullptr,                         "Unknown"}, | ||||
|     {0x43, nullptr,                         "Unknown"}, | ||||
|     {0x44, nullptr,                         "Unknown"}, | ||||
|     {0x45, nullptr,                         "Unknown"}, | ||||
|     {0x46, nullptr,                         "Unknown"}, | ||||
|     {0x47, nullptr,                         "CreatePort"}, | ||||
|     {0x48, nullptr,                         "CreateSessionToPort"}, | ||||
|     {0x49, nullptr,                         "CreateSession"}, | ||||
|     {0x4A, nullptr,                         "AcceptSession"}, | ||||
|     {0x4B, nullptr,                         "ReplyAndReceive1"}, | ||||
|     {0x4C, nullptr,                         "ReplyAndReceive2"}, | ||||
|     {0x4D, nullptr,                         "ReplyAndReceive3"}, | ||||
|     {0x4E, nullptr,                         "ReplyAndReceive4"}, | ||||
|     {0x4F, nullptr,                         "ReplyAndReceive"}, | ||||
|     {0x50, nullptr,                         "BindInterrupt"}, | ||||
|     {0x51, nullptr,                         "UnbindInterrupt"}, | ||||
|     {0x52, nullptr,                         "InvalidateProcessDataCache"}, | ||||
|     {0x53, nullptr,                         "StoreProcessDataCache"}, | ||||
|     {0x54, nullptr,                         "FlushProcessDataCache"}, | ||||
|     {0x55, nullptr,                         "StartInterProcessDma"}, | ||||
|     {0x56, nullptr,                         "StopDma"}, | ||||
|     {0x57, nullptr,                         "GetDmaState"}, | ||||
|     {0x58, nullptr,                         "RestartDma"}, | ||||
|     {0x59, nullptr,                         "Unknown"}, | ||||
|     {0x5A, nullptr,                         "Unknown"}, | ||||
|     {0x5B, nullptr,                         "Unknown"}, | ||||
|     {0x5C, nullptr,                         "Unknown"}, | ||||
|     {0x5D, nullptr,                         "Unknown"}, | ||||
|     {0x5E, nullptr,                         "Unknown"}, | ||||
|     {0x5F, nullptr,                         "Unknown"}, | ||||
|     {0x60, nullptr,                         "DebugActiveProcess"}, | ||||
|     {0x61, nullptr,                         "BreakDebugProcess"}, | ||||
|     {0x62, nullptr,                         "TerminateDebugProcess"}, | ||||
|     {0x63, nullptr,                         "GetProcessDebugEvent"}, | ||||
|     {0x64, nullptr,                         "ContinueDebugEvent"}, | ||||
|     {0x65, nullptr,                         "GetProcessList"}, | ||||
|     {0x66, nullptr,                         "GetThreadList"}, | ||||
|     {0x67, nullptr,                         "GetDebugThreadContext"}, | ||||
|     {0x68, nullptr,                         "SetDebugThreadContext"}, | ||||
|     {0x69, nullptr,                         "QueryDebugProcessMemory"}, | ||||
|     {0x6A, nullptr,                         "ReadProcessMemory"}, | ||||
|     {0x6B, nullptr,                         "WriteProcessMemory"}, | ||||
|     {0x6C, nullptr,                         "SetHardwareBreakPoint"}, | ||||
|     {0x6D, nullptr,                         "GetDebugThreadParam"}, | ||||
|     {0x6E, nullptr,                         "Unknown"}, | ||||
|     {0x6F, nullptr,                         "Unknown"}, | ||||
|     {0x70, nullptr,                         "ControlProcessMemory"}, | ||||
|     {0x71, nullptr,                         "MapProcessMemory"}, | ||||
|     {0x72, nullptr,                         "UnmapProcessMemory"}, | ||||
|     {0x73, nullptr,                         "Unknown"}, | ||||
|     {0x74, nullptr,                         "Unknown"}, | ||||
|     {0x75, nullptr,                         "Unknown"}, | ||||
|     {0x76, nullptr,                         "TerminateProcess"}, | ||||
|     {0x77, nullptr,                         "Unknown"}, | ||||
|     {0x78, nullptr,                         "CreateResourceLimit"}, | ||||
|     {0x79, nullptr,                         "Unknown"}, | ||||
|     {0x7A, nullptr,                         "Unknown"}, | ||||
|     {0x7B, nullptr,                         "Unknown"}, | ||||
|     {0x7C, nullptr,                         "KernelSetState"}, | ||||
|     {0x7D, nullptr,                         "QueryProcessMemory"}, | ||||
| }; | ||||
| 
 | ||||
| void Register() { | ||||
|  |  | |||
|  | @ -29,6 +29,10 @@ struct ThreadContext { | |||
|     u32 fpu_registers[32]; | ||||
|     u32 fpscr; | ||||
|     u32 fpexc; | ||||
| 
 | ||||
|     // These are not part of native ThreadContext, but needed by emu
 | ||||
|     u32 reg_15; | ||||
|     u32 mode; | ||||
| }; | ||||
| 
 | ||||
| enum ResetType { | ||||
|  | @ -38,6 +42,15 @@ enum ResetType { | |||
|     RESETTYPE_MAX_BIT = (1u << 31), | ||||
| }; | ||||
| 
 | ||||
| enum ArbitrationType { | ||||
|     ARBITRATIONTYPE_SIGNAL, | ||||
|     ARBITRATIONTYPE_WAIT_IF_LESS_THAN, | ||||
|     ARBITRATIONTYPE_DECREMENT_AND_WAIT_IF_LESS_THAN, | ||||
|     ARBITRATIONTYPE_WAIT_IF_LESS_THAN_WITH_TIMEOUT, | ||||
|     ARBITRATIONTYPE_DECREMENT_AND_WAIT_IF_LESS_THAN_WITH_TIMEOUT, | ||||
|     ARBITRATIONTYPE_MAX_BIT = (1u << 31) | ||||
| }; | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Namespace SVC
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue