diff --git a/sysmodules/Makefile b/sysmodules/Makefile index 162ce69..bdd18a5 100644 --- a/sysmodules/Makefile +++ b/sysmodules/Makefile @@ -1,4 +1,4 @@ -SUBFOLDERS := loader sm pxi rosalina +SUBFOLDERS := loader sm pm pxi rosalina CXIS := $(foreach dir, $(SUBFOLDERS), $(dir)/$(dir).cxi) .PHONY: all clean $(SUBFOLDERS) diff --git a/sysmodules/pm/LICENSE b/sysmodules/pm/LICENSE new file mode 100644 index 0000000..72cba6d --- /dev/null +++ b/sysmodules/pm/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 TuxSH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sysmodules/pm/Makefile b/sysmodules/pm/Makefile new file mode 100644 index 0000000..b14d489 --- /dev/null +++ b/sysmodules/pm/Makefile @@ -0,0 +1,147 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft +DEFINES := -DARM11 -D_3DS + +CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -O2 -mword-relocations \ + -fomit-frame-pointer -ffunction-sections -fdata-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lctru + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).cxi $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).cxi + +$(OUTPUT).cxi : $(OUTPUT).elf $(OUTPUT).rsf + @makerom -f ncch -rsf $(word 2,$^) -nocodepadding -o $@ -elf $< + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) + +%.elf: $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/sysmodules/pm/README.md b/sysmodules/pm/README.md new file mode 100644 index 0000000..2f5f6f4 --- /dev/null +++ b/sysmodules/pm/README.md @@ -0,0 +1,10 @@ +# 3ds_pm +Open source replacement of the ARM11 PM system module. +This is licensed under the MIT license. + +# Usage +To run this system module, use a recent release or commit of [Luma3DS](https://github.com/AuroraWright/Luma3DS/), build this project and copy the generated CXI file to `/luma/sysmodules/pm.cxi`. + +# Credits +@fincs +@Stary diff --git a/sysmodules/pm/pm.rsf b/sysmodules/pm/pm.rsf new file mode 100644 index 0000000..389622b --- /dev/null +++ b/sysmodules/pm/pm.rsf @@ -0,0 +1,125 @@ +BasicInfo: + Title : pm + CompanyCode : "00" + ProductCode : lennybuilder + ContentType : Application + Logo : None + +TitleInfo: + UniqueId : 0x12 + Category : Base + Version : 2 + +Option: + UseOnSD : false + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : true # Compresses exefs code + +AccessControlInfo: + IdealProcessor : 1 + AffinityMask : 3 + + Priority : 20 + + DisableDebug : false + EnableForceDebug : false + CanWriteSharedPage : false + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : false + PermitMainFunctionArgument : false + CanShareDeviceMemory : false + RunnableOnSleep : true + SpecialMemoryArrange : false + ResourceLimitCategory : Other + + CoreVersion : 2 + DescVersion : 2 + + MemoryType : Base # Application / System / Base + HandleTableSize: 0x200 + IORegisterMapping: + SystemCallAccess: + ControlMemory: 1 + QueryMemory: 2 + ExitProcess: 3 + SetProcessAffinityMask: 5 + GetProcessIdealProcessor: 6 + SetProcessIdealProcessor: 7 + CreateThread: 8 + ExitThread: 9 + SleepThread: 10 + GetThreadPriority: 11 + SetThreadPriority: 12 + GetThreadIdealProcessor: 15 + GetCurrentProcessorNumber: 17 + Run: 18 + CreateMutex: 19 + ReleaseMutex: 20 + CreateSemaphore: 21 + ReleaseSemaphore: 22 + CreateEvent: 23 + SignalEvent: 24 + ClearEvent: 25 + CreateTimer: 26 + SetTimer: 27 + CancelTimer: 28 + ClearTimer: 29 + CreateMemoryBlock: 30 + MapMemoryBlock: 31 + UnmapMemoryBlock: 32 + CreateAddressArbiter: 33 + ArbitrateAddress: 34 + CloseHandle: 35 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + DuplicateHandle: 39 + GetSystemTick: 40 + GetHandleInfo: 41 + GetSystemInfo: 42 + GetProcessInfo: 43 + GetThreadInfo: 44 + ConnectToPort: 45 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + OpenProcess: 51 + OpenThread: 52 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetThreadId: 55 + GetResourceLimit: 56 + GetResourceLimitLimitValues: 57 + GetResourceLimitCurrentValues: 58 + GetThreadContext: 59 + Break: 60 + OutputDebugString: 61 + CreatePort: 71 + CreateSessionToPort: 72 + AcceptSession: 74 + ReplyAndReceive1: 75 + ReplyAndReceive2: 76 + ReplyAndReceive3: 77 + ReplyAndReceive4: 78 + ReplyAndReceive: 79 + DebugActiveProcess: 96 + TerminateDebugProcess: 98 + GetThreadList: 102 + TerminateProcess: 118 + SetProcessResourceLimits: 119 + CreateResourceLimit: 120 + SetResourceLimitValues: 121 + KernelSetState: 124 + InterruptNumbers: + ServiceAccessControl: + # Note: pm also uses srv:pm and Loader but doesn't list them here. + - fs:REG + FileSystemAccess: + +SystemControlInfo: + SaveDataSize: 0KB # It doesn't use any save data. + RemasterVersion: 0 + StackSize: 0x1000 diff --git a/sysmodules/pm/source/exheader_info_heap.c b/sysmodules/pm/source/exheader_info_heap.c new file mode 100644 index 0000000..c9bef70 --- /dev/null +++ b/sysmodules/pm/source/exheader_info_heap.c @@ -0,0 +1,51 @@ +#include <3ds.h> +#include +#include + +typedef union Node { + union Node *next; + ExHeader_Info info; +} Node; + +static Node *g_headNode = NULL; + +void ExHeaderInfoHeap_Init(void *buf, size_t num) +{ + Node *node = (Node *)buf; + g_headNode = node; + for (size_t i = 0; i < num; i++) { + node->next = &node[1]; + node = node->next; + } +} + +ExHeader_Info *ExHeaderInfoHeap_New(void) +{ + Node *newHead; + Node *oldHead = atomic_load(&g_headNode); + if (oldHead == NULL) { + return NULL; + } else { + do { + newHead = oldHead == NULL ? oldHead : oldHead->next; + } while(!atomic_compare_exchange_weak(&g_headNode, &oldHead, newHead)); + + if (oldHead == NULL) { + return NULL; + } + else { + memset(&oldHead->info, 0, sizeof(ExHeader_Info)); + return &oldHead->info; + } + } +} + +void ExHeaderInfoHeap_Delete(ExHeader_Info *data) +{ + Node *newHead = (Node *)data; + Node *oldHead = atomic_load(&g_headNode); + do { + newHead->next = oldHead; + } while(!atomic_compare_exchange_weak(&g_headNode, &oldHead, newHead)); +} + diff --git a/sysmodules/pm/source/exheader_info_heap.h b/sysmodules/pm/source/exheader_info_heap.h new file mode 100644 index 0000000..b546efd --- /dev/null +++ b/sysmodules/pm/source/exheader_info_heap.h @@ -0,0 +1,9 @@ +#pragma once + +#include <3ds/exheader.h> + +// Official PM uses an overly complicated allocator with semaphores + +void ExHeaderInfoHeap_Init(void *buf, size_t num); +ExHeader_Info *ExHeaderInfoHeap_New(void); +void ExHeaderInfoHeap_Delete(ExHeader_Info *data); diff --git a/sysmodules/pm/source/firmlaunch.c b/sysmodules/pm/source/firmlaunch.c new file mode 100644 index 0000000..d7dc001 --- /dev/null +++ b/sysmodules/pm/source/firmlaunch.c @@ -0,0 +1,56 @@ +#include <3ds.h> +#include +#include "firmlaunch.h" +#include "termination.h" +#include "task_runner.h" +#include "util.h" + +static void *const g_firmlaunchParameters = (void *)0x12000000; + +void mapFirmlaunchParameters(void) +{ + assertSuccess(svcKernelSetState(3, 0, g_firmlaunchParameters)); +} + +Result GetFirmlaunchParams(void *outParams, size_t size) +{ + size = size >= 0x1000 ? 0x1000 : size; + memcpy(outParams, g_firmlaunchParameters, size); + return 0; +} + +Result SetFirmlaunchParams(const void *params, size_t size) +{ + size = size >= 0x1000 ? 0x1000 : size; + memcpy(g_firmlaunchParameters, params, size); + if (size < 0x1000) { + memset(g_firmlaunchParameters + size, 0, 0x1000 - size); + } + return 0; +} + +static void LaunchFirmAsync(void *argdata) +{ + struct { + u32 firmTidLow; + } *args = argdata; + + terminateAllProcesses((u32)-1, 4 * 1000 * 1000 * 1000LL); + // pm has dead code there (notification 0x179, but there's no 'caller process'... (-1)) + + u64 firmTid = args->firmTidLow; // note: tidHigh doesn't matter + firmTid = IS_N3DS ? (firmTid & ~N3DS_TID_MASK) | N3DS_TID_BIT : firmTid; + assertSuccess(svcKernelSetState(0, firmTid)); +} + +Result LaunchFirm(u32 firmTidLow, const void *params, size_t size) +{ + struct { + u32 firmTidLow; + } args = { firmTidLow }; + + SetFirmlaunchParams(params, size); + TaskRunner_RunTask(LaunchFirmAsync, &args, sizeof(args)); + + return 0; +} diff --git a/sysmodules/pm/source/firmlaunch.h b/sysmodules/pm/source/firmlaunch.h new file mode 100644 index 0000000..34f077d --- /dev/null +++ b/sysmodules/pm/source/firmlaunch.h @@ -0,0 +1,8 @@ +#pragma once + +#include <3ds/types.h> + +void mapFirmlaunchParameters(void); +Result GetFirmlaunchParams(void *outParams, size_t size); +Result SetFirmlaunchParams(const void *params, size_t size); +Result LaunchFirm(u32 firmTidLow, const void *params, size_t size); diff --git a/sysmodules/pm/source/info.c b/sysmodules/pm/source/info.c new file mode 100644 index 0000000..f75a4f0 --- /dev/null +++ b/sysmodules/pm/source/info.c @@ -0,0 +1,127 @@ +#include <3ds.h> +#include +#include "exheader_info_heap.h" +#include "manager.h" +#include "info.h" +#include "util.h" + +Result registerProgram(u64 *programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate) +{ + FS_ProgramInfo pi = *programInfo, piu = *programInfoUpdate; + Result res = 0; + + if (IS_N3DS) { + pi.programId = (pi.programId & ~N3DS_TID_MASK) | N3DS_TID_BIT; + piu.programId = (piu.programId & ~N3DS_TID_MASK) | N3DS_TID_BIT; + res = LOADER_RegisterProgram(programHandle, &pi, &piu); + if (R_FAILED(res)) { + pi.programId &= ~N3DS_TID_MASK; + piu.programId &= ~N3DS_TID_MASK; + res = LOADER_RegisterProgram(programHandle, &pi, &piu); + } + } else { + res = LOADER_RegisterProgram(programHandle, &pi, &piu); + } + + return res; +} + +Result getAndListDependencies(u64 *dependencies, u32 *numDeps, ProcessData *process, ExHeader_Info *exheaderInfo) +{ + Result res = 0; + TRY(LOADER_GetProgramInfo(exheaderInfo, process->programHandle)); + return listDependencies(dependencies, numDeps, exheaderInfo); +} + +Result listDependencies(u64 *dependencies, u32 *numDeps, const ExHeader_Info *exheaderInfo) +{ + Result res = 0; + + u32 num = 0; + for (u32 i = 0; i < 48 && exheaderInfo->sci.dependencies[i] != 0; i++) { + u64 titleId = exheaderInfo->sci.dependencies[i]; + if (IS_N3DS || (titleId & N3DS_TID_MASK) == 0) { + // On O3DS, ignore N3DS titles. + // Then (on both) remove the N3DS titleId bits + dependencies[num++] = titleId & ~N3DS_TID_MASK; + } + } + + *numDeps = num; + return res; +} + +Result listMergeUniqueDependencies(ProcessData **procs, u64 *dependencies, u32 *remrefcounts, u32 *numDeps, const ExHeader_Info *exheaderInfo) +{ + Result res = 0; + u32 numDepsUnique = *numDeps; + u32 num2; + + u64 deps[48]; + u32 newrefcounts[48] = {0}; + + TRY(listDependencies(deps, &num2, exheaderInfo)); + + ProcessList_Lock(&g_manager.processList); + for (u32 i = 0; i < num2; i++) { + // Filter duplicate results + u32 j; + for (j = 0; j < numDepsUnique && deps[i] != dependencies[j]; j++); + if (j >= numDepsUnique) { + if (numDepsUnique >= 48) { + panic(2); + } + dependencies[numDepsUnique] = deps[i]; + newrefcounts[numDepsUnique] = 1; + procs[numDepsUnique] = ProcessList_FindProcessByTitleId(&g_manager.processList, deps[i]); + numDepsUnique++; + } else { + ++newrefcounts[j]; + } + } + + // Apply refcounts + for (u32 i = 0; i < numDepsUnique; i++) { + if (procs[i] != NULL) { + ProcessData_Incref(procs[i], newrefcounts[i]); + } else { + remrefcounts[i] += newrefcounts[i]; + } + } + ProcessList_Unlock(&g_manager.processList); + + *numDeps = numDepsUnique; + return res; +} + + +Result GetTitleExHeaderFlags(ExHeader_Arm11CoreInfo *outCoreInfo, ExHeader_SystemInfoFlags *outSiFlags, const FS_ProgramInfo *programInfo) +{ + Result res = 0; + u64 programHandle = 0; + + if (g_manager.preparingForReboot) { + return 0xC8A05801; + } + + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + if (exheaderInfo == NULL) { + panic(0); + } + + res = registerProgram(&programHandle, programInfo, programInfo); + + if (R_SUCCEEDED(res)) + { + res = LOADER_GetProgramInfo(exheaderInfo, programHandle); + if (R_SUCCEEDED(res)) { + *outCoreInfo = exheaderInfo->aci.local_caps.core_info; + *outSiFlags = exheaderInfo->sci.codeset_info.flags; + } + LOADER_UnregisterProgram(programHandle); + } + + ExHeaderInfoHeap_Delete(exheaderInfo); + + return res; +} diff --git a/sysmodules/pm/source/info.h b/sysmodules/pm/source/info.h new file mode 100644 index 0000000..1858fd9 --- /dev/null +++ b/sysmodules/pm/source/info.h @@ -0,0 +1,12 @@ +#pragma once + +#include <3ds/exheader.h> +#include <3ds/services/fs.h> +#include "process_data.h" + +Result registerProgram(u64 *programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate); +Result getAndListDependencies(u64 *dependencies, u32 *numDeps, ProcessData *process, ExHeader_Info *exheaderInfo); +Result listDependencies(u64 *dependencies, u32 *numDeps, const ExHeader_Info *exheaderInfo); +Result listMergeUniqueDependencies(ProcessData **procs, u64 *dependencies, u32 *remrefcounts, u32 *numDeps, const ExHeader_Info *exheaderInfo); + +Result GetTitleExHeaderFlags(ExHeader_Arm11CoreInfo *outCoreInfo, ExHeader_SystemInfoFlags *outSiFlags, const FS_ProgramInfo *programInfo); diff --git a/sysmodules/pm/source/intrusive_list.h b/sysmodules/pm/source/intrusive_list.h new file mode 100644 index 0000000..112ade1 --- /dev/null +++ b/sysmodules/pm/source/intrusive_list.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +/// Intrusive node structure definition. +typedef struct IntrusiveNode { + struct IntrusiveNode *prev; ///< Pointer to the previous node. + struct IntrusiveNode *next; ///< Pointer to the next node. +} IntrusiveNode; + +/// Intrusive list structure definition. +typedef union IntrusiveList { + IntrusiveNode node; + struct { + IntrusiveNode *last; ///< Pointer to the last element. + IntrusiveNode *first; ///< Pointer to the first element. + }; +} IntrusiveList; + +/** + * @brief Initializes the intrusive linked list. + * + * @param ll The intrusive list to initialize. + */ +static inline void IntrusiveList_Init(IntrusiveList *ll) +{ + ll->node.prev = &ll->node; + ll->node.next = &ll->node; +} + +/** + * @brief Tests if a node is past the end of the list containing it (if any). + * + * @param nd The node to test. + * @return bool true iff node is past the end of the list. + */ +static inline bool IntrusiveList_TestEnd(const IntrusiveList *ll, const IntrusiveNode *nd) +{ + return nd == &ll->node; +} + +/** + * @brief Inserts a node after another one list. + * + * @param pos The node to insert the new node after. + * @param nd The new node. + */ +static inline void IntrusiveList_InsertAfter(IntrusiveNode *pos, IntrusiveNode *nd) +{ + // if pos is last & list is empty, ->next writes to first, etc. + pos->next->prev = nd; + nd->prev = pos; + nd->next = pos->next; + pos->next = nd; +} + +/** + * @brief Erases a node. + * + * @param nd The node to erase. + */ +static inline void IntrusiveList_Erase(const IntrusiveNode *nd) +{ + nd->prev->next = nd->next; + nd->next->prev = nd->prev; +} + +/** + * @brief Makes a list storage from a buffer, using each element's intrusive node field at offset 0. + * + * @param ll The intrusive list to create. + * @param buf The buffer to use. + * @param elem_size The size of each element. + * @param total_size The total size of the buffer. + * + * @pre @ref ll has not been initialized yet. + * @pre Each element must contain a @ref IntrusiveNode instance at offset 0, which is then used. + */ +static inline void IntrusiveList_CreateFromBuffer(IntrusiveList *ll, void *buf, size_t elemSize, size_t totalSize) +{ + IntrusiveList_Init(ll); + for (size_t pos = 0; pos < totalSize; pos += elemSize) { + IntrusiveList_InsertAfter(ll->last, (IntrusiveNode *)((char *)buf + pos)); + } +} diff --git a/sysmodules/pm/source/launch.c b/sysmodules/pm/source/launch.c new file mode 100644 index 0000000..acb7abc --- /dev/null +++ b/sysmodules/pm/source/launch.c @@ -0,0 +1,483 @@ +#include <3ds.h> +#include +#include "launch.h" +#include "info.h" +#include "manager.h" +#include "reslimit.h" +#include "exheader_info_heap.h" +#include "task_runner.h" +#include "util.h" + +static inline void removeAccessToService(const char *service, char (*serviceAccessList)[8]) +{ + char ALIGN(8) name[8+1]; + strncpy(name, service, 8); + name[8] = 0; + + u32 j = 0; + for (u32 i = 0; i < 34 && *(u64 *)serviceAccessList[i] != 0; i++) { + if (*(u64 *)serviceAccessList[i] != *(u64 *)name) { + *(u64 *)serviceAccessList[j++] = *(u64 *)serviceAccessList[i]; + } + } + + if (j < 34) { + memset(&serviceAccessList[j], 0, 8 * (34 - j)); + } +} + +static void blacklistServices(u64 titleId, char (*serviceAccessList)[8]) +{ + if (osGetFirmVersion() < SYSTEM_VERSION(2, 51, 0) || (titleId >> 46) != 0x10) { + return; + } + + u32 titleUid = ((u32)titleId >> 8) & 0xFFFFF; + + switch (titleUid) { + // Cubic Ninja + case 0x343: + case 0x465: + case 0x4B3: + removeAccessToService("http:C", serviceAccessList); + removeAccessToService("soc:U", serviceAccessList); + break; + + default: + break; + } +} + +// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function. +static Result launchTitleImpl(Handle *debug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo, + const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo); + +// Note: official PM doesn't include svcDebugActiveProcess in this function, but rather in the caller handling dependencies +static Result loadWithoutDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo, + u32 launchFlags, const ExHeader_Info *exheaderInfo) +{ + Result res = 0; + Handle processHandle = 0; + u32 pid; + ProcessData *process; + const ExHeader_Arm11SystemLocalCapabilities *localcaps = &exheaderInfo->aci.local_caps; + + if (outProcessData != NULL) { + *outProcessData = NULL; + } + + if (programInfo->programId & (1ULL << 35)) { + // RequireBatchUpdate? + return 0xD8E05803; + } + + TRY(LOADER_LoadProcess(&processHandle, programHandle)); + TRY(svcGetProcessId(&pid, processHandle)); + + // Note: bug in official PM: it seems not to panic/cleanup properly if the function calls below fail, + // svcTerminateProcess won't be called, it's possible to trigger NULL derefs if you crash fs/sm/whatever, + // leaks dependencies, and so on... + // This can be solved by interesting the new process in the list earlier, etc. etc., allowing us to simplify the logic greatly. + + ProcessList_Lock(&g_manager.processList); + process = ProcessList_New(&g_manager.processList); + if (process == NULL) { + panic(1); + } + + process->handle = processHandle; + process->pid = pid; + process->titleId = exheaderInfo->aci.local_caps.title_id;; + process->programHandle = programHandle; + process->flags = 0; // will be filled later + process->terminatedNotificationVariation = (launchFlags & 0xF0) >> 4; + process->terminationStatus = TERMSTATUS_RUNNING; + process->refcount = 1; + + ProcessList_Unlock(&g_manager.processList); + svcSignalEvent(g_manager.newProcessEvent); + + if (outProcessData != NULL) { + *outProcessData = process; + } + + u32 serviceCount; + for(serviceCount = 0; serviceCount < 34 && *(u64 *)localcaps->service_access[serviceCount] != 0; serviceCount++); + + TRY(FSREG_Register(pid, programHandle, programInfo, &localcaps->storage_info)); + TRY(SRVPM_RegisterProcess(pid, serviceCount, localcaps->service_access)); + + if (localcaps->reslimit_category <= RESLIMIT_CATEGORY_OTHER) { + TRY(svcSetProcessResourceLimits(processHandle, g_manager.reslimits[localcaps->reslimit_category])); + } + + // Yes, even numberOfCores=2 on N3DS. On the 3DS, the affinity mask doesn't play the role of an access limiter, + // it's only useful for cpuId < 0. thread->affinityMask = process->affinityMask | (cpuId >= 0 ? 1 << cpuId : 0) + u8 affinityMask = localcaps->core_info.affinity_mask; + TRY(svcSetProcessAffinityMask(processHandle, &affinityMask, 2)); + TRY(svcSetProcessIdealProcessor(processHandle, localcaps->core_info.ideal_processor)); + + if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) { + setAppCpuTimeLimitAndSchedModeFromDescriptor(localcaps->title_id, localcaps->reslimits[0]); + (*outProcessData)->flags |= PROCESSFLAG_NORMAL_APPLICATION; // not in official PM + } + + if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) { + TRY(svcDebugActiveProcess(outDebug, pid)); + } + + return res; +} + +static Result loadWithDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo, + u32 launchFlags, const ExHeader_Info *exheaderInfo) +{ + Result res = 0; + + u64 dependencies[48]; + u32 remrefcounts[48] = {0}; + ProcessData *depProcs[48] = {NULL}; + u32 numUnique = 0; + + FS_ProgramInfo depProgramInfo; + + res = loadWithoutDependencies(outDebug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo); + ProcessData *process = *outProcessData; + + if (R_FAILED(res)) { + if (process != NULL) { + svcTerminateProcess(process->handle); + } + + return res; + } + + ExHeader_Info *depExheaderInfo = ExHeaderInfoHeap_New(); + if (depExheaderInfo == NULL) { + panic(0); + } + + listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, exheaderInfo); + + if (numUnique > 0) { + process->flags |= PROCESSFLAG_DEPENDENCIES_LOADED; + } + + /* + Official pm does this: + for each dependency: + if dep already loaded: if autoloaded increase refcount // note: not autoloaded = not autoterminated + else: load new sysmodule w/o its deps (then process its deps), set flag "autoloaded" return early from entire function if it fails + Naturally, it forgets to incref all subsequent dependencies here & also when it factors the duplicate entries in, + and has a few other bugs (actually I'm not entirely sure... I think it doesn't clear dependencies on termination if it fails) + It also has a buffer overflow bug if the flattened dep tree has more than 48 elements (but this can never happen in practice) + */ + + for (u32 i = 0; i < numUnique; i++) { + if (depProcs[i] != NULL) { + continue; + } + // Note: numUnique is changed within the loop + depProgramInfo.programId = dependencies[i]; + depProgramInfo.mediaType = MEDIATYPE_NAND; + + res = launchTitleImpl(NULL, &process, &depProgramInfo, NULL, 0, depExheaderInfo); + depProcs[i] = process; + if (R_SUCCEEDED(res)) { + process->flags |= PROCESSFLAG_AUTOLOADED | PROCESSFLAG_DEPENDENCIES_LOADED; + ProcessData_Incref(process, remrefcounts[i] - 1); + remrefcounts[i] = 0; + listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, depExheaderInfo); // does some incref too + } else if (process != NULL) { + svcTerminateProcess(process->handle); + ExHeaderInfoHeap_Delete(depExheaderInfo); + return res; + } + } + + + ExHeaderInfoHeap_Delete(depExheaderInfo); + return res; +} + +// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function. +static Result launchTitleImpl(Handle *debug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo, + const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo) +{ + if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) { + launchFlags |= PMLAUNCHFLAG_LOAD_DEPENDENCIES; + } else { + launchFlags &= ~(PMLAUNCHFLAG_USE_UPDATE_TITLE | PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION); + launchFlags &= ~(PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM | PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM); + } + + Result res = 0; + u64 programHandle; + StartupInfo si = {0}; + + programInfoUpdate = (launchFlags & PMLAUNCHFLAG_USE_UPDATE_TITLE) ? programInfoUpdate : programInfo; + TRY(registerProgram(&programHandle, programInfo, programInfoUpdate)); + + res = LOADER_GetProgramInfo(exheaderInfo, programHandle); + res = R_SUCCEEDED(res) && exheaderInfo->aci.local_caps.core_info.core_version != SYSCOREVER ? (Result)0xC8A05800 : res; + + if (R_FAILED(res)) { + LOADER_UnregisterProgram(programHandle); + return res; + } + + // Change APPMEMALLOC if needed + if (IS_N3DS && APPMEMTYPE == 6 && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) { + u32 limitMb; + SystemMode n3dsSystemMode = exheaderInfo->aci.local_caps.core_info.n3ds_system_mode; + if ((launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM) || n3dsSystemMode == SYSMODE_O3DS_PROD) { + if ((launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM) & PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM) { + limitMb = 96; + } else { + switch (exheaderInfo->aci.local_caps.core_info.o3ds_system_mode) { + case SYSMODE_O3DS_PROD: limitMb = 64; break; + case SYSMODE_DEV1: limitMb = 96; break; + case SYSMODE_DEV2: limitMb = 80; break; + default: limitMb = 0; break; + } + } + + setAppMemLimit(limitMb << 20); + } + } + + blacklistServices(exheaderInfo->aci.local_caps.title_id, exheaderInfo->aci.local_caps.service_access); + + if (launchFlags & PMLAUNCHFLAG_LOAD_DEPENDENCIES) { + TRYG(loadWithDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup); + // note: official pm doesn't terminate the process if this fails (dependency loading)... + // This may be intentional, but I believe this is a bug since the 0xD8A05805 and svcRun failure codepaths terminate the process... + // It also forgets to clear PROCESSFLAG_NOTIFY_TERMINATION in the process... + } else { + TRYG(loadWithoutDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup); + // note: official pm doesn't terminate the proc. if it fails here either, but will because of the svcCloseHandle and the svcRun codepath + } + + ProcessData *process = *outProcessData; + if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) { + // saved field is different in official pm + // this also means official pm can't launch a title with a debug flag and an application + if (g_manager.debugData == NULL) { + g_manager.debugData = process; + } else { + res = 0xD8A05805; + } + } else { + si.priority = exheaderInfo->aci.local_caps.core_info.priority; + si.stack_size = exheaderInfo->sci.codeset_info.stack_size; + res = svcRun(process->handle, &si); + if (R_SUCCEEDED(res) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) { + g_manager.runningApplicationData = process; + notifySubscribers(0x10C); + } + } + + cleanup: + process = *outProcessData; + if (process != NULL && R_FAILED(res)) { + svcTerminateProcess(process->handle); + } else if (process != NULL) { + // official PM sets it but forgets to clear it on failure... + process->flags |= (launchFlags & PMLAUNCHFLAG_NOTIFY_TERMINATION) ? PROCESSFLAG_NOTIFY_TERMINATION : 0; + } + + return res; +} + +static Result launchTitleImplWrapper(Handle *outDebug, u32 *outPid, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags) +{ + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + if (exheaderInfo == NULL) { + panic(0); + } + + ProcessData *process; + Result res = launchTitleImpl(outDebug, &process, programInfo, programInfoUpdate, launchFlags, exheaderInfo); + + if (outPid != NULL) { + *outPid = process->pid; + } + + ExHeaderInfoHeap_Delete(exheaderInfo); + + return res; +} + +static void LaunchTitleAsync(void *argdata) +{ + struct { + FS_ProgramInfo programInfo, programInfoUpdate; + u32 launchFlags; + } *args = argdata; + + launchTitleImplWrapper(NULL, NULL, &args->programInfo, &args->programInfoUpdate, args->launchFlags); +} + +Result LaunchTitle(u32 *outPid, const FS_ProgramInfo *programInfo, u32 launchFlags) +{ + ProcessData *process, *foundProcess = NULL; + + launchFlags &= ~PMLAUNCHFLAG_USE_UPDATE_TITLE; + + if (g_manager.preparingForReboot) { + return 0xC8A05801; + } + + u32 tidh = (u32)(programInfo->programId >> 32); + u32 tidl = (u32)programInfo->programId; + if ((tidh == 0x00040030 || tidh == 0x00040130) && (tidl & 0xFF) != SYSCOREVER) { + // Panic if launching SAFE_MODE sysmodules or applets (note: exheader syscorever check above only done for applications in official PM) + // Official PM also hardcodes SYSCOREVER = 2 here. + panic(4); + } + + if ((g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) { + return 0xC8A05BF0; + } + + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + if ((process->titleId & ~0xFFULL) == (programInfo->programId & ~0xFFULL)) { + foundProcess = process; + break; + } + } + ProcessList_Unlock(&g_manager.processList); + + if (foundProcess != NULL) { + foundProcess->flags &= ~PROCESSFLAG_AUTOLOADED; + if (outPid != NULL) { + *outPid = foundProcess->pid; + } + return 0; + } else { + if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION || !(launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION)) { + return launchTitleImplWrapper(NULL, outPid, programInfo, programInfo, launchFlags); + } else { + struct { + FS_ProgramInfo programInfo, programInfoUpdate; + u32 launchFlags; + } args = { *programInfo, *programInfo, launchFlags }; + + if (outPid != NULL) { + *outPid = (u32)-1; // PM doesn't do that lol + } + TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args)); + return 0; + } + } +} + +Result LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags) +{ + if (g_manager.preparingForReboot) { + return 0xC8A05801; + } + if (g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) { + return 0xC8A05BF0; + } + if (!(launchFlags & ~PMLAUNCHFLAG_NORMAL_APPLICATION)) { + return 0xD8E05802; + } + + launchFlags |= PMLAUNCHFLAG_USE_UPDATE_TITLE; + + if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) { + return launchTitleImplWrapper(NULL, NULL, programInfo, programInfoUpdate, launchFlags); + } else { + struct { + FS_ProgramInfo programInfo, programInfoUpdate; + u32 launchFlags; + } args = { *programInfo, *programInfoUpdate, launchFlags }; + + TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args)); + return 0; + } +} + +Result LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags) +{ + if (g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) { + return 0xC8A05BF0; + } + + assertSuccess(setAppCpuTimeLimit(0)); + return LaunchTitle(NULL, programInfo, launchFlags | PMLAUNCHFLAG_LOAD_DEPENDENCIES | PMLAUNCHFLAG_NORMAL_APPLICATION); +} + +Result RunQueuedProcess(Handle *outDebug) +{ + Result res = 0; + StartupInfo si = {0}; + + if (g_manager.debugData == NULL) { + return 0xD8A05804; + } else if ((g_manager.debugData->flags & PROCESSFLAG_NORMAL_APPLICATION) && g_manager.runningApplicationData != NULL) { + // Not in official PM + return 0xC8A05BF0; + } + + ProcessData *process = g_manager.debugData; + g_manager.debugData = NULL; + + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + if (exheaderInfo == NULL) { + panic(0); + } + + TRYG(LOADER_GetProgramInfo(exheaderInfo, process->programHandle), cleanup); + TRYG(svcDebugActiveProcess(outDebug, process->pid), cleanup); + + si.priority = exheaderInfo->aci.local_caps.core_info.priority; + si.stack_size = exheaderInfo->sci.codeset_info.stack_size; + res = svcRun(process->handle, &si); + if (R_SUCCEEDED(res) && process->flags & PROCESSFLAG_NORMAL_APPLICATION) { + // Second operand not in official PM + g_manager.runningApplicationData = process; + notifySubscribers(0x10C); + } + + cleanup: + if (R_FAILED(res)) { + process->flags &= ~PROCESSFLAG_NOTIFY_TERMINATION; + svcTerminateProcess(process->handle); + } + + ExHeaderInfoHeap_Delete(exheaderInfo); + + return res; +} + +Result LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags) +{ + if (g_manager.debugData != NULL) { + return RunQueuedProcess(outDebug); + } + + if (g_manager.runningApplicationData != NULL) { + return 0xC8A05BF0; + } + + assertSuccess(setAppCpuTimeLimit(0)); + return launchTitleImplWrapper(outDebug, NULL, programInfo, programInfo, + (launchFlags & ~PMLAUNCHFLAG_USE_UPDATE_TITLE) | PMLAUNCHFLAG_NORMAL_APPLICATION); +} + +Result autolaunchSysmodules(void) +{ + Result res = 0; + FS_ProgramInfo programInfo = { .mediaType = MEDIATYPE_NAND }; + + // Launch NS + if (NSTID != 0) { + programInfo.programId = NSTID; + TRY(launchTitleImplWrapper(NULL, NULL, &programInfo, &programInfo, PMLAUNCHFLAG_LOAD_DEPENDENCIES)); + } + + return res; +} diff --git a/sysmodules/pm/source/launch.h b/sysmodules/pm/source/launch.h new file mode 100644 index 0000000..bb4fdb8 --- /dev/null +++ b/sysmodules/pm/source/launch.h @@ -0,0 +1,13 @@ +#pragma once + +#include <3ds/exheader.h> +#include <3ds/services/fs.h> +#include "process_data.h" + +Result LaunchTitle(u32 *outPid, const FS_ProgramInfo *programInfo, u32 launchFlags); +Result LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags); +Result LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags); +Result RunQueuedProcess(Handle *outDebug); +Result LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags); + +Result autolaunchSysmodules(void); diff --git a/sysmodules/pm/source/main.c b/sysmodules/pm/source/main.c new file mode 100644 index 0000000..b5c2c48 --- /dev/null +++ b/sysmodules/pm/source/main.c @@ -0,0 +1,100 @@ +#include <3ds.h> + +#include "manager.h" +#include "reslimit.h" +#include "launch.h" +#include "firmlaunch.h" +#include "exheader_info_heap.h" +#include "task_runner.h" +#include "process_monitor.h" +#include "pmapp.h" +#include "pmdbg.h" +#include "util.h" +#include "my_thread.h" +#include "service_manager.h" + +static MyThread processMonitorThread, taskRunnerThread; + +// this is called before main +void __appInit() +{ + // Wait for sm + for(Result res = 0xD88007FA; res == (Result)0xD88007FA; svcSleepThread(500 * 1000LL)) { + res = srvPmInit(); + if(R_FAILED(res) && res != (Result)0xD88007FA) + panic(res); + } + + loaderInit(); + fsRegInit(); + + static u8 ALIGN(8) processDataBuffer[0x40 * sizeof(ProcessData)] = {0}; + static u8 ALIGN(8) exheaderInfoBuffer[6 * sizeof(ExHeader_Info)] = {0}; + static u8 ALIGN(8) threadStacks[2][THREAD_STACK_SIZE] = {0}; + + // Init objects + Manager_Init(processDataBuffer, 0x40); + ExHeaderInfoHeap_Init(exheaderInfoBuffer, 6); + TaskRunner_Init(); + + // Init the reslimits, register the KIPs and map the firmlaunch parameters + initializeReslimits(); + Manager_RegisterKips(); + mapFirmlaunchParameters(); + + // Create the threads + assertSuccess(MyThread_Create(&processMonitorThread, processMonitor, NULL, threadStacks[0], THREAD_STACK_SIZE, 0x17, -2)); + assertSuccess(MyThread_Create(&taskRunnerThread, TaskRunner_HandleTasks, NULL, threadStacks[1], THREAD_STACK_SIZE, 0x17, -2)); + + // Launch NS, etc. + autolaunchSysmodules(); +} + +// this is called after main exits +void __appExit() +{ + // We don't clean up g_manager's handles because it could hang the process monitor thread, etc. + fsRegExit(); + loaderExit(); + srvPmExit(); +} + + +Result __sync_init(void); +Result __sync_fini(void); +void __libc_init_array(void); +void __libc_fini_array(void); + +void __ctru_exit() +{ + __libc_fini_array(); + __appExit(); + __sync_fini(); + svcExitProcess(); +} + +void initSystem() +{ + __sync_init(); + __appInit(); + __libc_init_array(); +} + +static const ServiceManagerServiceEntry services[] = { + { "pm:app", 3, pmAppHandleCommands, false }, + { "pm:dbg", 1, pmDbgHandleCommands, false }, + { NULL }, +}; + +static const ServiceManagerNotificationEntry notifications[] = { + { 0x000, NULL }, +}; + +int main(void) +{ + Result res = 0; + if (R_FAILED(res = ServiceManager_Run(services, notifications, NULL))) { + panic(res); + } + return 0; +} diff --git a/sysmodules/pm/source/manager.c b/sysmodules/pm/source/manager.c new file mode 100644 index 0000000..f75efb8 --- /dev/null +++ b/sysmodules/pm/source/manager.c @@ -0,0 +1,67 @@ +#include <3ds.h> +#include +#include "manager.h" +#include "reslimit.h" +#include "util.h" + +Manager g_manager; + +void Manager_Init(void *procBuf, size_t numProc) +{ + memset(&g_manager, 0, sizeof(Manager)); + ProcessList_Init(&g_manager.processList, procBuf, numProc); + assertSuccess(svcCreateEvent(&g_manager.newProcessEvent, RESET_ONESHOT)); + assertSuccess(svcCreateEvent(&g_manager.allNotifiedTerminationEvent , RESET_ONESHOT)); +} + +void Manager_RegisterKips(void) +{ + s64 numKips = 0; + ProcessData *process; + Handle processHandle; + + svcGetSystemInfo(&numKips, 26, 0); + + ProcessList_Lock(&g_manager.processList); + for (u32 i = 0; i < (u32)numKips; i++) { + process = ProcessList_New(&g_manager.processList); + if (process == NULL) { + panic(1); + } + + assertSuccess(svcOpenProcess(&processHandle, i)); + process->handle = processHandle; + process->pid = i; + process->refcount = 1; + process->titleId = 0x0004000100001000ULL; // note: same TID for all builtins + process->flags = PROCESSFLAG_KIP; + process->terminationStatus = TERMSTATUS_RUNNING; + + assertSuccess(svcSetProcessResourceLimits(processHandle, g_manager.reslimits[RESLIMIT_CATEGORY_OTHER])); + } + + ProcessList_Unlock(&g_manager.processList); +} + +Result UnregisterProcess(u64 titleId) +{ + ProcessData *foundProcess = NULL; + + ProcessList_Lock(&g_manager.processList); + foundProcess = ProcessList_FindProcessByTitleId(&g_manager.processList, titleId); + if (foundProcess != NULL) { + if (foundProcess == g_manager.runningApplicationData) { + g_manager.runningApplicationData = NULL; + } + + if (foundProcess == g_manager.debugData) { + g_manager.debugData = NULL; + } + + svcCloseHandle(foundProcess->handle); + ProcessList_Delete(&g_manager.processList, foundProcess); + } + + ProcessList_Unlock(&g_manager.processList); + return 0; +} diff --git a/sysmodules/pm/source/manager.h b/sysmodules/pm/source/manager.h new file mode 100644 index 0000000..82346dd --- /dev/null +++ b/sysmodules/pm/source/manager.h @@ -0,0 +1,23 @@ +#pragma once + +#include <3ds/types.h> +#include "process_data.h" + +typedef struct Manager { + ProcessList processList; + ProcessData *runningApplicationData; + ProcessData *debugData; // note: official PM uses runningApplicationData for both, and has queuedApplicationProcessHandle + Handle reslimits[4]; + Handle newProcessEvent; + Handle allNotifiedTerminationEvent; + bool waitingForTermination; + bool preparingForReboot; + u8 maxAppCpuTime; + s8 cpuTimeBase; +} Manager; + +extern Manager g_manager; + +void Manager_Init(void *procBuf, size_t numProc); +void Manager_RegisterKips(void); +Result UnregisterProcess(u64 titleId); diff --git a/sysmodules/pm/source/my_thread.c b/sysmodules/pm/source/my_thread.c new file mode 100644 index 0000000..4f541ce --- /dev/null +++ b/sysmodules/pm/source/my_thread.c @@ -0,0 +1,35 @@ +#include <3ds.h> +#include "my_thread.h" + +static void _thread_begin(void* arg) +{ + MyThread *t = (MyThread *)arg; + t->ep(t->p); + MyThread_Exit(); +} + +Result MyThread_Create(MyThread *t, void (*entrypoint)(void *p), void *p, void *stack, u32 stackSize, int prio, int affinity) +{ + t->ep = entrypoint; + t->p = p; + t->stacktop = (u8 *)stack + stackSize; + + return svcCreateThread(&t->handle, _thread_begin, (u32)t, (u32*)t->stacktop, prio, affinity); +} + +Result MyThread_Join(MyThread *thread, s64 timeout_ns) +{ + if (thread == NULL) return 0; + Result res = svcWaitSynchronization(thread->handle, timeout_ns); + if(R_FAILED(res)) return res; + + svcCloseHandle(thread->handle); + thread->handle = (Handle)0; + + return res; +} + +void MyThread_Exit(void) +{ + svcExitThread(); +} diff --git a/sysmodules/pm/source/my_thread.h b/sysmodules/pm/source/my_thread.h new file mode 100644 index 0000000..90c7462 --- /dev/null +++ b/sysmodules/pm/source/my_thread.h @@ -0,0 +1,20 @@ +#pragma once + +#include <3ds/types.h> +#include <3ds/result.h> +#include <3ds/svc.h> +#include <3ds/synchronization.h> + +#define THREAD_STACK_SIZE 0x1000 + +typedef struct MyThread { + Handle handle; + void *p; + void (*ep)(void *p); + bool finished; + void* stacktop; +} MyThread; + +Result MyThread_Create(MyThread *t, void (*entrypoint)(void *p), void *p, void *stack, u32 stackSize, int prio, int affinity); +Result MyThread_Join(MyThread *thread, s64 timeout_ns); +void MyThread_Exit(void); diff --git a/sysmodules/pm/source/pmapp.c b/sysmodules/pm/source/pmapp.c new file mode 100644 index 0000000..fc0b012 --- /dev/null +++ b/sysmodules/pm/source/pmapp.c @@ -0,0 +1,129 @@ +#include <3ds.h> +#include +#include "launch.h" +#include "firmlaunch.h" +#include "termination.h" +#include "info.h" +#include "reslimit.h" +#include "manager.h" +#include "util.h" + +void pmAppHandleCommands(void *ctx) +{ + (void)ctx; + u32 *cmdbuf = getThreadCommandBuffer(); + u32 cmdhdr = cmdbuf[0]; + + FS_ProgramInfo programInfo, programInfoUpdate; + ExHeader_Arm11CoreInfo coreInfo; + ExHeader_SystemInfoFlags siFlags; + u32 pid; + u64 titleId, mbz; + s64 timeout, limit; + void *buf; + size_t size; + + switch (cmdhdr >> 16) { + case 1: + memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo)); + cmdbuf[1] = LaunchTitle(&pid, &programInfo, cmdbuf[5]); + cmdbuf[2] = pid; + cmdbuf[0] = IPC_MakeHeader(1, 2, 0); + break; + case 2: + if (cmdhdr != IPC_MakeHeader(2, 2, 2) || (cmdbuf[3] & 0xF) != 0xA) { + goto invalid_command; + } + size = cmdbuf[3] >> 4; + buf = (void *)cmdbuf[4]; + cmdbuf[1] = LaunchFirm(cmdbuf[1], buf, size); + cmdbuf[0] = IPC_MakeHeader(2, 1, 2); + cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_R); + cmdbuf[3] = (u32)buf; + break; + case 3: + memcpy(&timeout, cmdbuf + 1, 8); + cmdbuf[1] = TerminateApplication(timeout); + cmdbuf[0] = IPC_MakeHeader(3, 1, 0); + break; + case 4: + memcpy(&titleId, cmdbuf + 1, 8); + memcpy(&timeout, cmdbuf + 3, 8); + cmdbuf[1] = TerminateTitle(titleId, timeout); + cmdbuf[0] = IPC_MakeHeader(4, 1, 0); + break; + case 5: + memcpy(&timeout, cmdbuf + 2, 8); + cmdbuf[1] = TerminateProcess(cmdbuf[1], timeout); + cmdbuf[0] = IPC_MakeHeader(5, 1, 0); + break; + case 6: + if (cmdhdr != IPC_MakeHeader(6, 2, 2) || cmdbuf[3] != 0x20) { + goto invalid_command; + } + memcpy(&timeout, cmdbuf + 1, 8); + cmdbuf[1] = PrepareForReboot(cmdbuf[4], timeout); + cmdbuf[0] = IPC_MakeHeader(6, 1, 0); + break; + case 7: + if (cmdhdr != IPC_MakeHeader(7, 1, 2) || (cmdbuf[2] & 0xF) != 0xC) { + goto invalid_command; + } + size = cmdbuf[2] >> 4; + buf = (void *)cmdbuf[3]; + cmdbuf[1] = GetFirmlaunchParams(buf, size); + cmdbuf[0] = IPC_MakeHeader(7, 1, 2); + cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_W); + cmdbuf[3] = (u32)buf; + break; + case 8: + memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo)); + cmdbuf[1] = GetTitleExHeaderFlags(&coreInfo, &siFlags, &programInfo); + cmdbuf[0] = IPC_MakeHeader(8, 5, 0); + memcpy(cmdbuf + 2, &coreInfo, sizeof(ExHeader_Arm11CoreInfo)); + memcpy(cmdbuf + 4, &siFlags, sizeof(ExHeader_SystemInfoFlags)); + break; + case 9: + if (cmdhdr != IPC_MakeHeader(9, 1, 2) || (cmdbuf[2] & 0xF) != 0xA) { + goto invalid_command; + } + size = cmdbuf[2] >> 4; + buf = (void *)cmdbuf[3]; + cmdbuf[1] = SetFirmlaunchParams(buf, size); + cmdbuf[0] = IPC_MakeHeader(9, 1, 2); + cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_R); + cmdbuf[3] = (u32)buf; + break; + case 10: + memcpy(&mbz, cmdbuf + 4, 8); + cmdbuf[1] = SetAppResourceLimit(cmdbuf[1], (ResourceLimitType)cmdbuf[2], cmdbuf[3], mbz); + cmdbuf[0] = IPC_MakeHeader(10, 1, 0); + break; + case 11: + memcpy(&mbz, cmdbuf + 4, 8); + cmdbuf[1] = GetAppResourceLimit(&limit, cmdbuf[1], (ResourceLimitType)cmdbuf[2], cmdbuf[3], mbz); + cmdbuf[0] = IPC_MakeHeader(11, 3, 0); + memcpy(cmdbuf + 2, &limit, 8); + break; + case 12: + memcpy(&titleId, cmdbuf + 1, 8); + cmdbuf[1] = UnregisterProcess(titleId); + cmdbuf[0] = IPC_MakeHeader(12, 1, 0); + break; + case 13: + memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo)); + memcpy(&programInfoUpdate, cmdbuf + 5, sizeof(FS_ProgramInfo)); + cmdbuf[1] = LaunchTitleUpdate(&programInfo, &programInfoUpdate, cmdbuf[9]); + break; + default: + cmdbuf[0] = IPC_MakeHeader(0, 1, 0); + cmdbuf[1] = 0xD900182F; + break; + } + + return; + + invalid_command: + cmdbuf[0] = IPC_MakeHeader(0, 1, 0); + cmdbuf[1] = 0xD9001830; +} diff --git a/sysmodules/pm/source/pmapp.h b/sysmodules/pm/source/pmapp.h new file mode 100644 index 0000000..dabd996 --- /dev/null +++ b/sysmodules/pm/source/pmapp.h @@ -0,0 +1,6 @@ +#pragma once + +#include <3ds/types.h> + +void pmAppHandleCommands(void *ctx); + diff --git a/sysmodules/pm/source/pmdbg.c b/sysmodules/pm/source/pmdbg.c new file mode 100644 index 0000000..461f200 --- /dev/null +++ b/sysmodules/pm/source/pmdbg.c @@ -0,0 +1,41 @@ +#include <3ds.h> +#include +#include "launch.h" +#include "util.h" + +void pmDbgHandleCommands(void *ctx) +{ + (void)ctx; + u32 *cmdbuf = getThreadCommandBuffer(); + u32 cmdhdr = cmdbuf[0]; + + FS_ProgramInfo programInfo; + Handle debug; + + switch (cmdhdr >> 16) { + case 1: + debug = 0; + memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo)); + cmdbuf[1] = LaunchAppDebug(&debug, &programInfo, cmdbuf[5]); + cmdbuf[0] = IPC_MakeHeader(1, 1, 2); + cmdbuf[2] = IPC_Desc_MoveHandles(1); + cmdbuf[3] = debug; + break; + case 2: + memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo)); + cmdbuf[1] = LaunchApp(&programInfo, cmdbuf[5]); + cmdbuf[0] = IPC_MakeHeader(2, 1, 0); + break; + case 3: + debug = 0; + cmdbuf[1] = RunQueuedProcess(&debug); + cmdbuf[0] = IPC_MakeHeader(3, 1, 2); + cmdbuf[2] = IPC_Desc_MoveHandles(1); + cmdbuf[3] = debug; + break; + default: + cmdbuf[0] = IPC_MakeHeader(0, 1, 0); + cmdbuf[1] = 0xD900182F; + break; + } +} diff --git a/sysmodules/pm/source/pmdbg.h b/sysmodules/pm/source/pmdbg.h new file mode 100644 index 0000000..2ed8044 --- /dev/null +++ b/sysmodules/pm/source/pmdbg.h @@ -0,0 +1,5 @@ +#pragma once + +#include <3ds/types.h> + +void pmDbgHandleCommands(void *ctx); diff --git a/sysmodules/pm/source/process_data.c b/sysmodules/pm/source/process_data.c new file mode 100644 index 0000000..c7bdcf0 --- /dev/null +++ b/sysmodules/pm/source/process_data.c @@ -0,0 +1,90 @@ +#include <3ds.h> +#include +#include "process_data.h" +#include "util.h" + +ProcessData *ProcessList_FindProcessById(const ProcessList *list, u32 pid) +{ + ProcessData *process; + + FOREACH_PROCESS(list, process) { + if (process->pid == pid) { + return process; + } + } + + return NULL; +} + +ProcessData *ProcessList_FindProcessByHandle(const ProcessList *list, Handle handle) +{ + ProcessData *process; + + FOREACH_PROCESS(list, process) { + if (process->handle == handle) { + return process; + } + } + + return NULL; +} + +ProcessData *ProcessList_FindProcessByTitleId(const ProcessList *list, u64 titleId) +{ + ProcessData *process; + + FOREACH_PROCESS(list, process) { + if ((process->titleId & ~0xFFULL) == (titleId & ~0xFFULL)) { + return process; + } + } + + return NULL; +} + +Result ProcessData_Notify(const ProcessData *process, u32 notificationId) +{ + Result res = SRVPM_PublishToProcess(notificationId, process->handle); + if (res == (Result)0xD8606408) { + panic(res); + } + + return res; +} + +Result ProcessData_SendTerminationNotification(ProcessData *process) +{ + Result res = ProcessData_Notify(process, 0x100); + process->terminationStatus = R_SUCCEEDED(res) ? TERMSTATUS_NOTIFICATION_SENT : TERMSTATUS_NOTIFICATION_FAILED; + return res; +} + +void ProcessData_Incref(ProcessData *process, u32 amount) +{ + if (process->flags & PROCESSFLAG_AUTOLOADED) { + if (process->refcount + amount > 0xFF) { + panic(3); + } + + process->refcount += amount; + } +} + +ProcessData *ProcessList_New(ProcessList *list) +{ + if (IntrusiveList_TestEnd(&list->freeList, list->freeList.first)) { + return NULL; + } + + IntrusiveNode *nd = list->freeList.first; + IntrusiveList_Erase(nd); + memset(nd, 0, sizeof(ProcessData)); + IntrusiveList_InsertAfter(list->list.last, nd); + return (ProcessData *)nd; +} + +void ProcessList_Delete(ProcessList *list, ProcessData *process) +{ + IntrusiveList_Erase(&process->node); + IntrusiveList_InsertAfter(list->freeList.first, &process->node); +} diff --git a/sysmodules/pm/source/process_data.h b/sysmodules/pm/source/process_data.h new file mode 100644 index 0000000..e13dd4f --- /dev/null +++ b/sysmodules/pm/source/process_data.h @@ -0,0 +1,95 @@ +#pragma once + +#include <3ds/types.h> +#include <3ds/synchronization.h> +#include "intrusive_list.h" + +#define FOREACH_PROCESS(list, process) \ +for (process = ProcessList_GetFirst(list); !ProcessList_TestEnd(list, process); process = ProcessList_GetNext(process)) + +enum { + PROCESSFLAG_NOTIFY_TERMINATION = BIT(0), + PROCESSFLAG_KIP = BIT(1), + PROCESSFLAG_DEPENDENCIES_LOADED = BIT(2), + PROCESSFLAG_AUTOLOADED = BIT(3), + PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED = BIT(4), + PROCESSFLAG_NORMAL_APPLICATION = BIT(5), // Official PM doesn't have this +}; + +typedef enum TerminationStatus { + TERMSTATUS_RUNNING = 0, + TERMSTATUS_NOTIFICATION_SENT = 1, + TERMSTATUS_NOTIFICATION_FAILED = 2, + TERMSTATUS_TERMINATED = 3, +} TerminationStatus; + +typedef struct ProcessData { + IntrusiveNode node; + Handle handle; + u32 pid; + u64 titleId; + u64 programHandle; + u8 flags; + u8 terminatedNotificationVariation; + TerminationStatus terminationStatus; + u8 refcount; +} ProcessData; + +typedef struct ProcessList { + RecursiveLock lock; + IntrusiveList list; + IntrusiveList freeList; +} ProcessList; + +static inline void ProcessList_Init(ProcessList *list, void *buf, size_t num) +{ + IntrusiveList_Init(&list->list); + IntrusiveList_CreateFromBuffer(&list->freeList, buf, sizeof(ProcessData), sizeof(ProcessData) * num); + RecursiveLock_Init(&list->lock); +} + +static inline void ProcessList_Lock(ProcessList *list) +{ + RecursiveLock_Lock(&list->lock); +} + +static inline void ProcessList_Unlock(ProcessList *list) +{ + RecursiveLock_Unlock(&list->lock); +} + +static inline ProcessData *ProcessList_GetNext(const ProcessData *process) +{ + return (ProcessData *)process->node.next; +} + +static inline ProcessData *ProcessList_GetPrev(const ProcessData *process) +{ + return (ProcessData *)process->node.next; +} + +static inline ProcessData *ProcessList_GetFirst(const ProcessList *list) +{ + return (ProcessData *)list->list.first; +} + +static inline ProcessData *ProcessList_GetLast(const ProcessList *list) +{ + return (ProcessData *)list->list.last; +} + +static inline bool ProcessList_TestEnd(const ProcessList *list, const ProcessData *process) +{ + return IntrusiveList_TestEnd(&list->list, &process->node); +} + +ProcessData *ProcessList_New(ProcessList *list); +void ProcessList_Delete(ProcessList *list, ProcessData *process); + +ProcessData *ProcessList_FindProcessById(const ProcessList *list, u32 pid); +ProcessData *ProcessList_FindProcessByTitleId(const ProcessList *list, u64 titleId); +ProcessData *ProcessList_FindProcessByHandle(const ProcessList *list, Handle handle); + +void ProcessData_Incref(ProcessData *process, u32 amount); +Result ProcessData_Notify(const ProcessData *process, u32 notificationId); +Result ProcessData_SendTerminationNotification(ProcessData *process); diff --git a/sysmodules/pm/source/process_monitor.c b/sysmodules/pm/source/process_monitor.c new file mode 100644 index 0000000..dbc8968 --- /dev/null +++ b/sysmodules/pm/source/process_monitor.c @@ -0,0 +1,109 @@ +#include <3ds.h> +#include +#include "process_monitor.h" +#include "exheader_info_heap.h" +#include "termination.h" +#include "reslimit.h" +#include "manager.h" +#include "util.h" + +static void cleanupProcess(ProcessData *process) +{ + if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) { + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + + if (exheaderInfo == NULL) { + panic(0); + } + + listAndTerminateDependencies(process, exheaderInfo); + + ExHeaderInfoHeap_Delete(exheaderInfo); + } + + if (!(process->flags & PROCESSFLAG_KIP)) { + SRVPM_UnregisterProcess(process->pid); + FSREG_Unregister(process->pid); + LOADER_UnregisterProgram(process->programHandle); + } + + if (process == g_manager.runningApplicationData) { + if (IS_N3DS && APPMEMTYPE == 6) { + assertSuccess(resetAppMemLimit()); + } + g_manager.runningApplicationData = NULL; + } + + if (process == g_manager.debugData) { + g_manager.debugData = NULL; + } + + if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) { + notifySubscribers(0x110 + process->terminatedNotificationVariation); + } +} + +void processMonitor(void *p) +{ + (void)p; + + Handle handles[0x41] = { g_manager.newProcessEvent }; + + for (;;) { + u32 numProcesses = 0; + bool atLeastOneTerminating = false; + ProcessData *process; + ProcessData processBackup; + s32 id = -1; + + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + // Rebuild the handle array + if (process->terminationStatus != TERMSTATUS_TERMINATED) { + handles[1 + numProcesses++] = process->handle; + if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) { + atLeastOneTerminating = true; + } + } + } + ProcessList_Unlock(&g_manager.processList); + + // If no more processes are terminating, signal the event + if (g_manager.waitingForTermination && !atLeastOneTerminating) { + assertSuccess(svcSignalEvent(g_manager.allNotifiedTerminationEvent)); + } + + // Note: lack of assertSuccess is intentional. + svcWaitSynchronizationN(&id, handles, 1 + numProcesses, false, -1LL); + + if (id > 0) { + // Note: official PM conditionally erases the process from the list, cleans up, then conditionally frees the process data + // Bug in official PM (?): it unlocks the list before setting termstatus = TERMSTATUS_TERMINATED + ProcessList_Lock(&g_manager.processList); + process = ProcessList_FindProcessByHandle(&g_manager.processList, handles[id]); + if (process != NULL) { + process->terminationStatus = TERMSTATUS_TERMINATED; + if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) { + process->flags |= PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED; + } + + processBackup = *process; // <-- make sure no list access is done through this node + + // Note: PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED can be set by terminateProcessImpl + // APT is shit, why must an app call APT to ask to terminate itself? + + if (!(process->flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) { + ProcessList_Delete(&g_manager.processList, process); + } + } + ProcessList_Unlock(&g_manager.processList); + + if (process != NULL) { + cleanupProcess(&processBackup); + if (!(processBackup.flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) { + svcCloseHandle(processBackup.handle); + } + } + } + } +} diff --git a/sysmodules/pm/source/process_monitor.h b/sysmodules/pm/source/process_monitor.h new file mode 100644 index 0000000..e5e2192 --- /dev/null +++ b/sysmodules/pm/source/process_monitor.h @@ -0,0 +1,3 @@ +#pragma once + +void processMonitor(void *p); diff --git a/sysmodules/pm/source/reslimit.c b/sysmodules/pm/source/reslimit.c new file mode 100644 index 0000000..ff6249f --- /dev/null +++ b/sysmodules/pm/source/reslimit.c @@ -0,0 +1,373 @@ +#include <3ds.h> +#include "reslimit.h" +#include "util.h" +#include "manager.h" + +typedef s64 ReslimitValues[10]; + +static const ResourceLimitType g_reslimitInitOrder[10] = { + RESLIMIT_COMMIT, + RESLIMIT_PRIORITY, + RESLIMIT_THREAD, + RESLIMIT_EVENT, + RESLIMIT_MUTEX, + RESLIMIT_SEMAPHORE, + RESLIMIT_TIMER, + RESLIMIT_SHAREDMEMORY, + RESLIMIT_ADDRESSARBITER, + RESLIMIT_CPUTIME, +}; + +static u32 g_currentAppMemLimit = 0, g_defaultAppMemLimit; + +static ReslimitValues g_o3dsReslimitValues[4] = { + // APPLICATION + { + 0x4000000, // Allocatable memory (dynamically calculated) + 0x18, // Max. priority + 32, // Threads + 32, // Events + 32, // Mutexes + 8, // Semaphores + 8, // Timers + 16, // Shared memory objects + 2, // Address arbiters + 0, // Core1 CPU time + }, + + // SYS_APPLET + { + 0x2606000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 14, // Threads + 8, // Events + 8, // Mutexes + 4, // Semaphores + 4, // Timers + 8, // Shared memory objects + 3, // Address arbiters + 10000, // Core1 CPU time + }, + + // LIB_APPLET + { + 0x0602000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 14, // Threads + 8, // Events + 8, // Mutexes + 4, // Semaphores + 4, // Timers + 8, // Shared memory objects + 1, // Address arbiters + 10000, // Core1 CPU time + }, + + // OTHER (BASE sysmodules) + { + 0x1682000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 202, // Threads + 248, // Events + 35, // Mutexes + 64, // Semaphores + 43, // Timers + 30, // Shared memory objects + 43, // Address arbiters + 1000, // Core1 CPU time + }, +}; + +static ReslimitValues g_n3dsReslimitValues[4] = { + // APPLICATION + { + 0x7C00000, // Allocatable memory (dynamically calculated) + 0x18, // Max. priority + 32, // Threads + 32, // Events + 32, // Mutexes + 8, // Semaphores + 8, // Timers + 16, // Shared memory objects + 2, // Address arbiters + 0, // Core1 CPU time + }, + + // SYS_APPLET + { + 0x5E06000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 29, // Threads + 11, // Events + 8, // Mutexes + 4, // Semaphores + 4, // Timers + 8, // Shared memory objects + 3, // Address arbiters + 10000, // Core1 CPU time + }, + + // LIB_APPLET + { + 0x0602000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 14, // Threads + 8, // Events + 8, // Mutexes + 4, // Semaphores + 4, // Timers + 8, // Shared memory objects + 1, // Address arbiters + 10000, // Core1 CPU time + }, + + // OTHER (BASE sysmodules) + { + 0x2182000, // Allocatable memory (dynamically calculated) + 4, // Max priority + 225, // Threads + 264, // Events + 37, // Mutexes + 67, // Semaphores + 44, // Timers + 31, // Shared memory objects + 45, // Address arbiters + 1000, // Core1 CPU time + }, +}; + +/* + SCHEDULER AND PREEMPTION STUFF ON THE 3DS: + + Most of the basic scheduler stuff is the same between the Switch and the 3DS: + - Multi-level queue with 64 levels, 0 being highest priority. + - Interrupt handling code mostly the same, same thing for the entire irq bottom-half + object hierarchy. + - There is the same global critical section object, although not fully a spinlock, + and critsec Leave code has the same logic w/r/t the scheduler and scheduler interrupts. + + Some stuff is different: + - The thread selection is done core-per-core, and not all-at-once like on the Switch. + - No load balancing. SetThreadAffinityMask is not implemented either. + - When needed (thread creation), each scheduler has a thread queue; + other schedulers enqueue threads on these queues, triggering the scheduler + SGI when transferring a thread, to make sure threads run on the right core. + - Preemption stuff is different. Preemption is only on core1 when enabled. + - Only one time source, on core1. + - On 3DS, the process affinity mask (specified in exheader) IS NOT A LIMITER. + ThreadAffinityMask = processAffinityMask | (1 << coreId). processAffinityMask is only used + for cpuId < 0 in CreateThread. + + 3DS CPUTIME + + Seemingly implemented since 2.1 (see pm workaround and kernel workaround for 1.0 titles below), + on 1.0, there was not any restriction on core1 usage (?). + + There are 2 preemption modes, controlled by svcKernelSetState(6, 3, (u64)x) (switching fails + if any of those are still active). + + Both rely on segregating running thread/processes in 3 categories: "application", "sysmodules" + and "exempted" (applets). + + Cputime values have different meanings: + - 0: disables preemption. Prevent applications from spawning threads in core1. + Behavior is undefined if an 1..89 reslimit is still running. Intended to be ran before app creation only. + - 1..89: core1 cputime percentage. Enables preemption. Previous threads from the same + reslimit are not affected (bug?), but *all* threads with prio >= 16 from the "sysmodule" category + (reslimit value 1000, see below) are affected and possibly immediately setup for preemption, depending on their + current reslimit value. + - 1000: Sets the "sysmodule" preemption info to point to the current reslimit's, + for when something else (application cputime reslimit being set by PM) is set to 1..89. + - 10000 and above: exempted from preemption. + - other values: buggy/possibly undefined? + + For the following, time durations might be off by a factor of 2 (?), and nothing was tested. + Please check on hardware/emu code. Really really needs proofreading/prooftesting, especially mode0. + The timers are updated on scheduler thread reselection. + + Both modes pause threads they don't want to run in thread selection, and unpause them when needed. + If the threads that are intended to be paused is running an SVC, the pause will happen *after* SVC return. + + Mode0 (unsure) + + Starting by "sysmodule" threads, alternatively allow (if preemptible) only sysmodule threads, + and then only application threads to run. + The latter has an exception; if "sysmodule" threads have run for less than 2usec, they + are unpaused an allowed to run instead. + + This happens at a rate of 1ms * (cpuTime/100). + + Mode1 + + When this mode is enabled, only one application thread is allowed to be created on core1. + + This divides the core1 time into slices of 12.5ms. + + The "application" thread is given cpuTime% of the slice. + The "sysmodules" threads are given a total of (90 - cpuTime)% of the slice. + 10% remain for other threads. +*/ + +static const struct { + u32 titleUid; + u32 value; +} g_startCpuTimeOverrides[] = { + /* + Region-incoherent? CHN/KOR/TWN consistently missing. + This seems to be here to replace a 2.0+ kernel workaround for 1.0 titles (maybe?). + + It seems like 1.0 kernel didn't implement cputime reslimit & you could freely access core1. + 2.1+ kernel returns an error if you try to start an application thread on core1 with cputime=0, + except for 1.0 titles for which it automatically sets cputime to 25. + */ + { 0x205, 10000 }, // 3DS sound (JPN) + { 0x215, 10000 }, // 3DS sound (USA) + { 0x225, 10000 }, // 3DS sound (EUR) + { 0x304, 10000 }, // Star Fox 64 3D (JPN) + { 0x32E, 10000 }, // Super Monkey Ball 3D (JPN) + { 0x334, 30 }, // Zelda no Densetsu: Toki no Ocarina 3D (JPN) + { 0x335, 30 }, // The Legend of Zelda: Ocarina of Time 3D (USA) + { 0x336, 30 }, // The Legend of Zelda: Ocarina of Time 3D (EUR) + { 0x348, 10000 }, // Doctor Lautrec to Boukyaku no Kishidan (JPN) + { 0x349, 10000 }, // Pro Yakyuu Spirits 2011 (JPN) + { 0x368, 10000 }, // Doctor Lautrec and the Forgotten Knights (USA) + { 0x370, 10000 }, // Super Monkey Ball 3D (USA) + { 0x389, 10000 }, // Super Monkey Ball 3D (EUR) + { 0x490, 10000 }, // Star Fox 64 3D (USA) + { 0x491, 10000 }, // Star Fox 64 3D (EUR) + { 0x562, 10000 }, // Doctor Lautrec and the Forgotten Knights (EUR) +}; + +static ReslimitValues *fixupReslimitValues(void) +{ + // In order: APPLICATION, SYS_APPLET, LIB_APPLET, OTHER + // Fixup "commit" reslimit + u32 sysmemalloc = SYSMEMALLOC; + ReslimitValues *values = !IS_N3DS ? g_o3dsReslimitValues : g_n3dsReslimitValues; + + static const u32 minAppletMemAmount = 0x1200000; + u32 defaultMemAmount = !IS_N3DS ? 0x2C00000 : 0x6400000; + u32 otherMinOvercommitAmount = !IS_N3DS ? 0x280000 : 0x180000; + u32 baseRegionSize = !IS_N3DS ? 0x1400000 : 0x2000000; + + if (sysmemalloc < minAppletMemAmount) { + values[1][0] = SYSMEMALLOC - minAppletMemAmount / 3; + values[2][0] = 0; + values[3][0] = baseRegionSize + otherMinOvercommitAmount; + } else { + u32 excess = sysmemalloc < defaultMemAmount ? 0 : sysmemalloc - defaultMemAmount; + values[1][0] = 3 * excess / 4 + sysmemalloc - minAppletMemAmount / 3; + values[2][0] = 1 * excess / 4 + minAppletMemAmount / 3; + values[3][0] = baseRegionSize + (otherMinOvercommitAmount + excess / 4); + } + + values[0][0] = APPMEMALLOC; + g_defaultAppMemLimit = APPMEMALLOC; + + return values; +} + +Result initializeReslimits(void) +{ + Result res = 0; + ReslimitValues *values = fixupReslimitValues(); + for (u32 i = 0; i < 4; i++) { + TRY(svcCreateResourceLimit(&g_manager.reslimits[i])); + TRY(svcSetResourceLimitValues(g_manager.reslimits[i], g_reslimitInitOrder, values[i], 10)); + } + + return res; +} + +Result setAppMemLimit(u32 limit) +{ + if (g_currentAppMemLimit == g_defaultAppMemLimit) { + return 0; + } + + ResourceLimitType category = RESLIMIT_COMMIT; + s64 value = limit | ((u64)limit << 32); // high u32 is the value written to APPMEMALLOC + return svcSetResourceLimitValues(g_manager.reslimits[0], &category, &value, 1); +} + +Result resetAppMemLimit(void) +{ + return setAppMemLimit(g_defaultAppMemLimit); +} + +Result setAppCpuTimeLimit(s64 limit) +{ + ResourceLimitType category = RESLIMIT_CPUTIME; + return svcSetResourceLimitValues(g_manager.reslimits[0], &category, &limit, 1); +} + +static Result getAppCpuTimeLimit(s64 *limit) +{ + ResourceLimitType category = RESLIMIT_CPUTIME; + return svcGetResourceLimitLimitValues(limit, g_manager.reslimits[0], &category, 1); +} + +void setAppCpuTimeLimitAndSchedModeFromDescriptor(u64 titleId, u16 descriptor) +{ + /* + Two main cases here: + - app has a non-0 cputime descriptor in exhdr: current core1 cputime reslimit, + and maximum, and scheduling mode are set to it. SetAppResourceLimit is *not* needed + to use core1. + - app has a 0 cputime descriptor: maximum is set to 80. + Current reslimit is set to 0, and SetAppResourceLimit *is* needed + to use core1, **EXCEPT** for an hardcoded set of titles. + */ + u8 cpuTime = (u8)descriptor; + assertSuccess(setAppCpuTimeLimit(cpuTime)); + + g_manager.cpuTimeBase = 0; + + if (cpuTime != 0) { + // Set core1 scheduling mode + g_manager.maxAppCpuTime = cpuTime & 0x7F; + assertSuccess(svcKernelSetState(6, 3, (cpuTime & 0x80) ? 1LL : 0LL)); + } else { + u32 titleUid = ((u32)titleId >> 8) & 0xFFFFF; + g_manager.maxAppCpuTime = 80; + static const u32 numOverrides = sizeof(g_startCpuTimeOverrides) / sizeof(g_startCpuTimeOverrides[0]); + + if (titleUid >= g_startCpuTimeOverrides[0].titleUid && titleUid <= g_startCpuTimeOverrides[numOverrides - 1].titleUid) { + u32 i = 0; + for (u32 i = 0; i < numOverrides && titleUid < g_startCpuTimeOverrides[i].titleUid; i++); + if (i < numOverrides) { + if (g_startCpuTimeOverrides[i].value > 100 && g_startCpuTimeOverrides[i].value < 200) { + assertSuccess(svcKernelSetState(6, 3, 0LL)); + assertSuccess(setAppCpuTimeLimit(g_startCpuTimeOverrides[i].value - 100)); + } else { + assertSuccess(svcKernelSetState(6, 3, 1LL)); + assertSuccess(setAppCpuTimeLimit(g_startCpuTimeOverrides[i].value)); + } + } + } + } +} + +Result SetAppResourceLimit(u32 mbz, ResourceLimitType category, u32 value, u64 mbz2) +{ + if (mbz != 0 || mbz2 != 0 || category != RESLIMIT_CPUTIME || value > (u32)g_manager.maxAppCpuTime) { + return 0xD8E05BF4; + } + + value += value < 5 ? 0 : g_manager.cpuTimeBase; + return setAppCpuTimeLimit(value); +} + +Result GetAppResourceLimit(s64 *value, u32 mbz, ResourceLimitType category, u32 mbz2, u64 mbz3) +{ + if (mbz != 0 || mbz2 != 0 || mbz3 != 0 || category != RESLIMIT_CPUTIME) { + return 0xD8E05BF4; + } + + Result res = getAppCpuTimeLimit(value); + if (R_SUCCEEDED(res) && *value >= 5) { + *value = *value >= g_manager.cpuTimeBase ? *value - g_manager.cpuTimeBase : 0; + } + + return res; +} diff --git a/sysmodules/pm/source/reslimit.h b/sysmodules/pm/source/reslimit.h new file mode 100644 index 0000000..55f3513 --- /dev/null +++ b/sysmodules/pm/source/reslimit.h @@ -0,0 +1,12 @@ +#pragma once + +#include <3ds/svc.h> + +Result initializeReslimits(void); +Result setAppMemLimit(u32 limit); +Result resetAppMemLimit(void); +Result setAppCpuTimeLimit(s64 limit); +void setAppCpuTimeLimitAndSchedModeFromDescriptor(u64 titleId, u16 descriptor); + +Result SetAppResourceLimit(u32 mbz, ResourceLimitType category, u32 value, u64 mbz2); +Result GetAppResourceLimit(s64 *value, u32 mbz, ResourceLimitType category, u32 mbz2, u64 mbz3); diff --git a/sysmodules/pm/source/service_manager.c b/sysmodules/pm/source/service_manager.c new file mode 100644 index 0000000..33cc78e --- /dev/null +++ b/sysmodules/pm/source/service_manager.c @@ -0,0 +1,182 @@ +/* +* This file is part of Luma3DS +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* 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, either version 3 of the License, or +* (at your option) any later version. +* +* 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 for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include <3ds.h> +#include "service_manager.h" + +#define TRY(expr) if(R_FAILED(res = (expr))) goto cleanup; + +Result ServiceManager_Run(const ServiceManagerServiceEntry *services, const ServiceManagerNotificationEntry *notifications, const ServiceManagerContextAllocator *allocator) +{ + Result res = 0; + + u32 numServices = 0; + u32 maxSessionsTotal = 0; + u32 numActiveSessions = 0; + bool terminationRequested = false; + + for (u32 i = 0; services[i].name != NULL; i++) { + numServices++; + maxSessionsTotal += services[i].maxSessions; + } + + Handle waitHandles[1 + numServices + maxSessionsTotal]; + void *ctxs[maxSessionsTotal]; + u8 handlerIds[maxSessionsTotal]; + + Handle replyTarget = 0; + s32 id = -1; + u32 *cmdbuf = getThreadCommandBuffer(); + + TRY(srvEnableNotification(&waitHandles[0])); + + // Subscribe to notifications if needed. + for (u32 i = 0; notifications[i].handler != NULL; i++) { + // Termination & ready for reboot events send by PM using PublishToProcess don't require subscription. + if (notifications[i].id != 0x100 && notifications[i].id != 0x179) { + TRY(srvSubscribe(notifications[i].id)); + } + } + + for (u32 i = 0; i < numServices; i++) { + if (!services[i].isGlobalPort) { + TRY(srvRegisterService(&waitHandles[1 + i], services[i].name, (s32)services[i].maxSessions)); + } else { + Handle clientPort; + TRY(svcCreatePort(&waitHandles[1 + i], &clientPort, services[i].name, (s32)services[i].maxSessions)); + svcCloseHandle(clientPort); + } + } + + while (!terminationRequested) { + if (replyTarget == 0) { + cmdbuf[0] = 0xFFFF0000; + } + + id = -1; + res = svcReplyAndReceive(&id, waitHandles, 1 + numServices + numActiveSessions, replyTarget); + + if (res == (Result)0xC920181A) { + // Session has been closed + u32 off; + if (id == -1) { + for (off = 0; off < numActiveSessions && waitHandles[1 + numServices + off] != replyTarget; off++); + if (off >= numActiveSessions) { + return res; + } + + id = 1 + numServices + off; + } else if ((u32)id < 1 + numServices) { + return res; + } + + off = id - 1 - numServices; + + Handle h = waitHandles[id]; + void *ctx = ctxs[off]; + waitHandles[id] = waitHandles[1 + numServices + --numActiveSessions]; + handlerIds[off] = handlerIds[numActiveSessions]; + ctxs[off] = ctxs[numActiveSessions]; + + svcCloseHandle(h); + if (allocator != NULL) { + allocator->freeSessionContext(ctx); + } + + replyTarget = 0; + res = 0; + } else if (R_FAILED(res)) { + return res; + } + + else { + // Ok, no session closed and no error + replyTarget = 0; + if (id == 0) { + // Notification + u32 notificationId = 0; + TRY(srvReceiveNotification(¬ificationId)); + terminationRequested = notificationId == 0x100; + + for (u32 i = 0; notifications[i].handler != NULL; i++) { + if (notifications[i].id == notificationId) { + notifications[i].handler(notificationId); + break; + } + } + } else if ((u32)id < 1 + numServices) { + // New session + Handle session; + void *ctx = NULL; + TRY(svcAcceptSession(&session, waitHandles[id])); + + if (allocator) { + ctx = allocator->newSessionContext((u8)(id - 1)); + if (ctx == NULL) { + svcCloseHandle(session); + return 0xDEAD0000; + } + } + + waitHandles[1 + numServices + numActiveSessions] = session; + handlerIds[numActiveSessions] = (u8)(id - 1); + ctxs[numActiveSessions++] = ctx; + } else { + // Service command + u32 off = id - 1 - numServices; + services[handlerIds[off]].handler(ctxs[off]); + replyTarget = waitHandles[id]; + } + } + } + +cleanup: + for (u32 i = 0; i < 1 + numServices + numActiveSessions; i++) { + svcCloseHandle(waitHandles[i]); + } + + // Subscribe to notifications if needed. + for (u32 i = 0; notifications[i].handler != NULL; i++) { + // Termination & ready for reboot events send by PM using PublishToProcess don't require subscription. + if (notifications[i].id != 0x100 && notifications[i].id != 0x179) { + TRY(srvUnsubscribe(notifications[i].id)); + } + } + + for (u32 i = 0; i < numServices; i++) { + if (!services[i].isGlobalPort) { + srvUnregisterService(services[i].name); + } + } + + if (allocator) { + for (u32 i = 0; i < numActiveSessions; i++) { + allocator->freeSessionContext(ctxs[i]); + } + } + + return res; +} diff --git a/sysmodules/pm/source/service_manager.h b/sysmodules/pm/source/service_manager.h new file mode 100644 index 0000000..1c2c1fb --- /dev/null +++ b/sysmodules/pm/source/service_manager.h @@ -0,0 +1,22 @@ +#pragma once + +#include <3ds/types.h> + +typedef struct ServiceManagerServiceEntry { + const char *name; + u32 maxSessions; + void (*handler)(void *ctx); + bool isGlobalPort; +} ServiceManagerServiceEntry; + +typedef struct ServiceManagerNotificationEntry { + u32 id; + void (*handler)(u32 id); +} ServiceManagerNotificationEntry; + +typedef struct ServiceManagerContextAllocator { + void* (*newSessionContext)(u8 serviceId); + void (*freeSessionContext)(void *ctx); +} ServiceManagerContextAllocator; + +Result ServiceManager_Run(const ServiceManagerServiceEntry *services, const ServiceManagerNotificationEntry *notifications, const ServiceManagerContextAllocator *allocator); diff --git a/sysmodules/pm/source/task_runner.c b/sysmodules/pm/source/task_runner.c new file mode 100644 index 0000000..79cbf02 --- /dev/null +++ b/sysmodules/pm/source/task_runner.c @@ -0,0 +1,31 @@ +#include <3ds.h> +#include +#include "task_runner.h" + +TaskRunner g_taskRunner; + +void TaskRunner_Init(void) +{ + memset(&g_taskRunner, 0, sizeof(TaskRunner)); + LightEvent_Init(&g_taskRunner.readyEvent, RESET_ONESHOT); + LightEvent_Init(&g_taskRunner.parametersSetEvent, RESET_ONESHOT); +} + +void TaskRunner_RunTask(void (*task)(void *argdata), void *argdata, size_t argsize) +{ + argsize = argsize > sizeof(g_taskRunner.argStorage) ? sizeof(g_taskRunner.argStorage) : argsize; + LightEvent_Wait(&g_taskRunner.readyEvent); + g_taskRunner.task = task; + memcpy(g_taskRunner.argStorage, argdata, argsize); + LightEvent_Signal(&g_taskRunner.parametersSetEvent); +} + +void TaskRunner_HandleTasks(void *p) +{ + (void)p; + for (;;) { + LightEvent_Signal(&g_taskRunner.readyEvent); + LightEvent_Wait(&g_taskRunner.parametersSetEvent); + g_taskRunner.task(g_taskRunner.argStorage); + } +} diff --git a/sysmodules/pm/source/task_runner.h b/sysmodules/pm/source/task_runner.h new file mode 100644 index 0000000..de277cb --- /dev/null +++ b/sysmodules/pm/source/task_runner.h @@ -0,0 +1,18 @@ +#pragma once + +#include <3ds/types.h> +#include <3ds/synchronization.h> + +typedef struct TaskRunner { + LightEvent readyEvent; + LightEvent parametersSetEvent; + void (*task)(void *argdata); + u8 argStorage[0x40]; +} TaskRunner; + +extern TaskRunner g_taskRunner; + +void TaskRunner_Init(void); +void TaskRunner_RunTask(void (*task)(void *argdata), void *argdata, size_t argsize); +/// Thread function +void TaskRunner_HandleTasks(void *p); diff --git a/sysmodules/pm/source/termination.c b/sysmodules/pm/source/termination.c new file mode 100644 index 0000000..9b724f4 --- /dev/null +++ b/sysmodules/pm/source/termination.c @@ -0,0 +1,351 @@ +#include <3ds.h> +#include "termination.h" +#include "info.h" +#include "manager.h" +#include "util.h" +#include "exheader_info_heap.h" +#include "task_runner.h" + +static Result terminateUnusedDependencies(const u64 *dependencies, u32 numDeps) +{ + ProcessData *process; + Result res = 0; + + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + if (process->terminationStatus != TERMSTATUS_RUNNING || !(process->flags & PROCESSFLAG_AUTOLOADED)) { + continue; + } + + u32 i; + for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++); + + if (i >= numDeps || --process->refcount > 0) { + // Process not a listed dependency or still used + continue; + } + + res = ProcessData_SendTerminationNotification(process); + res = R_SUMMARY(res) == RS_NOTFOUND ? 0 : res; + + if (R_FAILED(res)) { + assertSuccess(svcTerminateProcess(process->handle)); + } + } + + ProcessList_Unlock(&g_manager.processList); + return res; +} + +Result listAndTerminateDependencies(ProcessData *process, ExHeader_Info *exheaderInfo) +{ + Result res = 0; + u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space + u32 numDeps = 0; + + TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo)); + return terminateUnusedDependencies(dependencies, numDeps); +} + +static Result terminateProcessImpl(ProcessData *process, ExHeader_Info *exheaderInfo) +{ + // NOTE: list dependencies BEFORE sending the notification -- race condition material + Result res = 0; + u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space + u32 numDeps = 0; + + if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) { + TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo)); + process->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED; + ProcessData_SendTerminationNotification(process); + return terminateUnusedDependencies(dependencies, numDeps); + } else { + ProcessData_SendTerminationNotification(process); + return 0; + } +} + +static Result commitPendingTerminations(s64 timeout) +{ + // Wait for all of the processes that have received notification 0x100 to terminate + // till the timeout, then actually terminate these processes, etc. + + Result res = 0; + bool atLeastOneListener = false; + ProcessList_Lock(&g_manager.processList); + + ProcessData *process; + FOREACH_PROCESS(&g_manager.processList, process) { + switch (process->terminationStatus) { + case TERMSTATUS_NOTIFICATION_SENT: + atLeastOneListener = true; + break; + case TERMSTATUS_NOTIFICATION_FAILED: + res = svcTerminateProcess(process->handle); // official pm does not panic on failure here + break; + default: + break; + } + } + + ProcessList_Unlock(&g_manager.processList); + + if (atLeastOneListener) { + Result res = assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, timeout)); + + if (R_DESCRIPTION(res) == RD_TIMEOUT) { + ProcessList_Lock(&g_manager.processList); + + ProcessData *process; + FOREACH_PROCESS(&g_manager.processList, process) { + if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) { + res = svcTerminateProcess(process->handle); + } + } + + ProcessList_Unlock(&g_manager.processList); + + assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, -1LL)); + } + } else { + res = 0; + } + + return res; +} + +static void TerminateProcessOrTitleAsync(void *argdata) +{ + struct { + u64 id; + s64 timeout; + bool useTitleId; + } *args = argdata; + + ProcessData *process; + bool notify = false; + u8 variation; + + if (args->timeout >= 0) { + assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent)); + g_manager.waitingForTermination = true; + } + + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + if (exheaderInfo == NULL) { + panic(0); + } + + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + // It's the only place where it uses the full titleId, and doesn't break after the first result. + // Maybe it's to allow killing all the builtins at once with their dummy titleIds? Otherwise, + // two processes can't have the same titleId. + if ((args->useTitleId && process->titleId == args->id) || process->pid == args->id) { + if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) { + notify = true; + variation = process->terminatedNotificationVariation; + process->flags = (process->flags & ~PROCESSFLAG_NOTIFY_TERMINATION) | PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED; + } + terminateProcessImpl(process, exheaderInfo); + if (!args->useTitleId) { + break; + } + } + } + ProcessList_Unlock(&g_manager.processList); + + ExHeaderInfoHeap_Delete(exheaderInfo); + + if (args->timeout >= 0) { + commitPendingTerminations(args->timeout); + g_manager.waitingForTermination = false; + if (notify) { + notifySubscribers(0x110 + variation); + } + } +} + +static Result TerminateProcessOrTitle(u64 id, s64 timeout, bool useTitleId) +{ + ProcessData *process; + + if (g_manager.preparingForReboot) { + if (timeout != 0) { + return 0xC8A05801; + } + + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + if ((useTitleId && process->titleId == id) || process->pid == id) { + assertSuccess(svcTerminateProcess(process->handle)); + if (!useTitleId) { + break; + } + } + } + ProcessList_Unlock(&g_manager.processList); + + return 0; + } else { + struct { + u64 id; + s64 timeout; + bool useTitleId; + } args = { id, timeout, useTitleId }; + + TaskRunner_RunTask(TerminateProcessOrTitleAsync, &args, sizeof(args)); + return 0; + } +} + +Result TerminateApplication(s64 timeout) +{ + Result res = 0; + + if (g_manager.preparingForReboot) { + return 0xC8A05801; + } + + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + if (exheaderInfo == NULL) { + panic(0); + } + + assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent)); + g_manager.waitingForTermination = true; + + if (g_manager.runningApplicationData != NULL) { + terminateProcessImpl(g_manager.runningApplicationData, exheaderInfo); + } + + res = commitPendingTerminations(timeout); + + ExHeaderInfoHeap_Delete(exheaderInfo); + g_manager.waitingForTermination = false; + + return res; +} + +Result TerminateTitle(u64 titleId, s64 timeout) +{ + return TerminateProcessOrTitle(titleId, timeout, true); +} + +Result TerminateProcess(u32 pid, s64 timeout) +{ + return TerminateProcessOrTitle(pid, timeout, false); +} + +ProcessData *terminateAllProcesses(u32 callerPid, s64 timeout) +{ + u64 dstTimePoint = svcGetSystemTick() + nsToTicks(timeout); + ProcessData *process; + ProcessData *callerProcess = NULL; // note: official pm returns the caller's handle instead + + u64 dependencies[48]; + u32 numDeps = 0; + + ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New(); + + if (exheaderInfo == NULL) { + panic(0); + } + + assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent)); + g_manager.waitingForTermination = true; + + // List the dependencies of the caller + if (callerPid != (u32)-1) { + ProcessList_Lock(&g_manager.processList); + process = ProcessList_FindProcessById(&g_manager.processList, callerPid); + if (process != NULL) { + callerProcess = process; + getAndListDependencies(dependencies, &numDeps, process, exheaderInfo); + } + ProcessList_Unlock(&g_manager.processList); + } + + // Send notification 0x100 to the currently running application + if (g_manager.runningApplicationData != NULL) { + g_manager.runningApplicationData->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED; + ProcessData_SendTerminationNotification(g_manager.runningApplicationData); + } + + // Send notification 0x100 to anything but the caller deps or the caller; and *increase* the refcount of the latter if autoloaded + // Ignore KIPs + ProcessList_Lock(&g_manager.processList); + FOREACH_PROCESS(&g_manager.processList, process) { + if (process->flags & PROCESSFLAG_KIP) { + continue; + } else if (process == callerProcess && (process->flags & PROCESSFLAG_AUTOLOADED) != 0) { + ProcessData_Incref(process, 1); + continue; + } + + u32 i; + for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++); + + if (i >= numDeps) { + // Process not a listed dependency: send notification 0x100 + ProcessData_SendTerminationNotification(process); + } else if (process->flags & PROCESSFLAG_AUTOLOADED){ + ProcessData_Incref(process, 1); + } + } + ProcessList_Unlock(&g_manager.processList); + ExHeaderInfoHeap_Delete(exheaderInfo); + + s64 timeoutTicks = dstTimePoint - svcGetSystemTick(); + commitPendingTerminations(timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL); + g_manager.waitingForTermination = false; + + // Now, send termination notification to PXI (PID 4) + assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent)); + g_manager.waitingForTermination = true; + + ProcessList_Lock(&g_manager.processList); + process = ProcessList_FindProcessById(&g_manager.processList, 4); + if (process != NULL) { + ProcessData_SendTerminationNotification(process); + } else { + panic(0LL); + } + ProcessList_Unlock(&g_manager.processList); + + // Allow 1.5 extra seconds for PXI (approx 402167783 ticks) + timeoutTicks = dstTimePoint - svcGetSystemTick(); + commitPendingTerminations(1500 * 1000 * 1000LL + (timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL)); + g_manager.waitingForTermination = false; + + return callerProcess; +} + +static void PrepareForRebootAsync(void *argdata) +{ + struct { + u32 pid; + s64 timeout; + } *args = argdata; + + ProcessData *caller = terminateAllProcesses(args->pid, args->timeout); + if (caller != NULL) { + ProcessData_Notify(caller, 0x179); + } +} + +Result PrepareForReboot(u32 pid, s64 timeout) +{ + struct { + u32 pid; + s64 timeout; + } args = { pid, timeout }; + + if (g_manager.preparingForReboot) { + return 0xC8A05801; + } + + g_manager.preparingForReboot = true; + TaskRunner_RunTask(PrepareForRebootAsync, &args, sizeof(args)); + return 0; +} diff --git a/sysmodules/pm/source/termination.h b/sysmodules/pm/source/termination.h new file mode 100644 index 0000000..e726b24 --- /dev/null +++ b/sysmodules/pm/source/termination.h @@ -0,0 +1,12 @@ +#pragma once + +#include "process_data.h" +#include <3ds/exheader.h> + +Result listAndTerminateDependencies(ProcessData *process, ExHeader_Info *exheaderInfo); +ProcessData *terminateAllProcesses(u32 callerPid, s64 timeout); // callerPid = -1 for firmlaunch + +Result TerminateApplication(s64 timeout); +Result TerminateTitle(u64 titleId, s64 timeout); +Result TerminateProcess(u32 pid, s64 timeout); +Result PrepareForReboot(u32 pid, s64 timeout); diff --git a/sysmodules/pm/source/util.h b/sysmodules/pm/source/util.h new file mode 100644 index 0000000..d84b2b7 --- /dev/null +++ b/sysmodules/pm/source/util.h @@ -0,0 +1,81 @@ +#pragma once + +#include <3ds/types.h> +#include <3ds/result.h> +#include <3ds/os.h> + +#define REG32(reg) (*(vu32 *)reg) +#define REG64(reg) (*(vu64 *)reg) + +#define NSTID REG64(0x1FF80008) +#define SYSCOREVER REG32(0x1FF80010) +#define APPMEMTYPE REG32(0x1FF80030) +#define APPMEMALLOC REG32(0x1FF80040) +#define SYSMEMALLOC REG32(0x1FF80044) + +#define IS_N3DS (*(vu32 *)0x1FF80030 >= 6) // APPMEMTYPE. Hacky but doesn't use APT +#define N3DS_TID_MASK 0xF0000000ULL +#define N3DS_TID_BIT 0x20000000ULL + +#define TRY(expr) if(R_FAILED(res = (expr))) return res; +#define TRYG(expr, label) if(R_FAILED(res = (expr))) goto label; + +#define XDS +#ifdef XDS +static void hexItoa(u64 number, char *out, u32 digits, bool uppercase) +{ + const char hexDigits[] = "0123456789ABCDEF"; + const char hexDigitsLowercase[] = "0123456789abcdef"; + u32 i = 0; + + while(number > 0) + { + out[digits - 1 - i++] = uppercase ? hexDigits[number & 0xF] : hexDigitsLowercase[number & 0xF]; + number >>= 4; + } + + while(i < digits) out[digits - 1 - i++] = '0'; +} +#endif + +static void __attribute__((noinline)) panic(Result res) +{ +#ifndef XDS + (void)res; + __builtin_trap(); +#else + char buf[32] = {0}; + hexItoa(res, buf, 8, false); + svcOutputDebugString(buf, 8); + svcBreak(USERBREAK_PANIC); +#endif +} + +static inline Result assertSuccess(Result res) +{ + if(R_FAILED(res)) { + panic(res); + } + + return res; +} + +static inline Result notifySubscribers(u32 notificationId) +{ + Result res = srvPublishToSubscriber(notificationId, 0); + if (res == (Result)0xD8606408) { + panic(res); + } + + return res; +} + +static inline s64 nsToTicks(s64 ns) +{ + return ns * SYSCLOCK_ARM11 / (1000 * 1000 * 1000LL); +} + +static inline s64 ticksToNs(s64 ticks) +{ + return 1000 * 1000 * 1000LL * ticks / SYSCLOCK_ARM11; +}