Add custom pm sysmodule
This commit is contained in:
parent
e57abad52f
commit
1041d7c438
@ -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)
|
||||
|
21
sysmodules/pm/LICENSE
Normal file
21
sysmodules/pm/LICENSE
Normal file
@ -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.
|
147
sysmodules/pm/Makefile
Normal file
147
sysmodules/pm/Makefile
Normal file
@ -0,0 +1,147 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITARM)),)
|
||||
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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
|
||||
#---------------------------------------------------------------------------------------
|
10
sysmodules/pm/README.md
Normal file
10
sysmodules/pm/README.md
Normal file
@ -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
|
125
sysmodules/pm/pm.rsf
Normal file
125
sysmodules/pm/pm.rsf
Normal file
@ -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
|
51
sysmodules/pm/source/exheader_info_heap.c
Normal file
51
sysmodules/pm/source/exheader_info_heap.c
Normal file
@ -0,0 +1,51 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
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));
|
||||
}
|
||||
|
9
sysmodules/pm/source/exheader_info_heap.h
Normal file
9
sysmodules/pm/source/exheader_info_heap.h
Normal file
@ -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);
|
56
sysmodules/pm/source/firmlaunch.c
Normal file
56
sysmodules/pm/source/firmlaunch.c
Normal file
@ -0,0 +1,56 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
8
sysmodules/pm/source/firmlaunch.h
Normal file
8
sysmodules/pm/source/firmlaunch.h
Normal file
@ -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);
|
127
sysmodules/pm/source/info.c
Normal file
127
sysmodules/pm/source/info.c
Normal file
@ -0,0 +1,127 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
12
sysmodules/pm/source/info.h
Normal file
12
sysmodules/pm/source/info.h
Normal file
@ -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);
|
86
sysmodules/pm/source/intrusive_list.h
Normal file
86
sysmodules/pm/source/intrusive_list.h
Normal file
@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
483
sysmodules/pm/source/launch.c
Normal file
483
sysmodules/pm/source/launch.c
Normal file
@ -0,0 +1,483 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
13
sysmodules/pm/source/launch.h
Normal file
13
sysmodules/pm/source/launch.h
Normal file
@ -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);
|
100
sysmodules/pm/source/main.c
Normal file
100
sysmodules/pm/source/main.c
Normal file
@ -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;
|
||||
}
|
67
sysmodules/pm/source/manager.c
Normal file
67
sysmodules/pm/source/manager.c
Normal file
@ -0,0 +1,67 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
23
sysmodules/pm/source/manager.h
Normal file
23
sysmodules/pm/source/manager.h
Normal file
@ -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);
|
35
sysmodules/pm/source/my_thread.c
Normal file
35
sysmodules/pm/source/my_thread.c
Normal file
@ -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();
|
||||
}
|
20
sysmodules/pm/source/my_thread.h
Normal file
20
sysmodules/pm/source/my_thread.h
Normal file
@ -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);
|
129
sysmodules/pm/source/pmapp.c
Normal file
129
sysmodules/pm/source/pmapp.c
Normal file
@ -0,0 +1,129 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
6
sysmodules/pm/source/pmapp.h
Normal file
6
sysmodules/pm/source/pmapp.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <3ds/types.h>
|
||||
|
||||
void pmAppHandleCommands(void *ctx);
|
||||
|
41
sysmodules/pm/source/pmdbg.c
Normal file
41
sysmodules/pm/source/pmdbg.c
Normal file
@ -0,0 +1,41 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
}
|
5
sysmodules/pm/source/pmdbg.h
Normal file
5
sysmodules/pm/source/pmdbg.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <3ds/types.h>
|
||||
|
||||
void pmDbgHandleCommands(void *ctx);
|
90
sysmodules/pm/source/process_data.c
Normal file
90
sysmodules/pm/source/process_data.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
95
sysmodules/pm/source/process_data.h
Normal file
95
sysmodules/pm/source/process_data.h
Normal file
@ -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);
|
109
sysmodules/pm/source/process_monitor.c
Normal file
109
sysmodules/pm/source/process_monitor.c
Normal file
@ -0,0 +1,109 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
sysmodules/pm/source/process_monitor.h
Normal file
3
sysmodules/pm/source/process_monitor.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void processMonitor(void *p);
|
373
sysmodules/pm/source/reslimit.c
Normal file
373
sysmodules/pm/source/reslimit.c
Normal file
@ -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;
|
||||
}
|
12
sysmodules/pm/source/reslimit.h
Normal file
12
sysmodules/pm/source/reslimit.h
Normal file
@ -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);
|
182
sysmodules/pm/source/service_manager.c
Normal file
182
sysmodules/pm/source/service_manager.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
}
|
22
sysmodules/pm/source/service_manager.h
Normal file
22
sysmodules/pm/source/service_manager.h
Normal file
@ -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);
|
31
sysmodules/pm/source/task_runner.c
Normal file
31
sysmodules/pm/source/task_runner.c
Normal file
@ -0,0 +1,31 @@
|
||||
#include <3ds.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
}
|
18
sysmodules/pm/source/task_runner.h
Normal file
18
sysmodules/pm/source/task_runner.h
Normal file
@ -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);
|
351
sysmodules/pm/source/termination.c
Normal file
351
sysmodules/pm/source/termination.c
Normal file
@ -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;
|
||||
}
|
12
sysmodules/pm/source/termination.h
Normal file
12
sysmodules/pm/source/termination.h
Normal file
@ -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);
|
81
sysmodules/pm/source/util.h
Normal file
81
sysmodules/pm/source/util.h
Normal file
@ -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;
|
||||
}
|
Reference in New Issue
Block a user