core: kernel: Implement thread cpu time limit for core1

This commit is contained in:
PabloMK7 2026-03-23 20:49:40 +01:00
parent 9b045bf837
commit af07b97f44
17 changed files with 617 additions and 75 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project / PPSSPP Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -7,6 +7,7 @@
#include <algorithm>
#include <array>
#include <deque>
#include <tuple>
#include <boost/serialization/deque.hpp>
#include <boost/serialization/split_member.hpp>
#include "common/common_types.h"
@ -52,33 +53,35 @@ struct ThreadQueueList {
return T();
}
T pop_first() {
std::pair<Priority, T> pop_first() {
Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
auto tmp = std::move(cur->data.front());
cur->data.pop_front();
return tmp;
Priority prio = static_cast<Priority>(ToIndex(cur));
return {prio, tmp};
}
cur = cur->next_nonempty;
}
return T();
return {Priority(), T()};
}
T pop_first_better(Priority priority) {
std::pair<Priority, T> pop_first_better(Priority priority) {
Queue* cur = first;
Queue* stop = &queues[priority];
while (cur < stop) {
if (!cur->data.empty()) {
auto tmp = std::move(cur->data.front());
cur->data.pop_front();
return tmp;
Priority prio = static_cast<Priority>(ToIndex(cur));
return {prio, tmp};
}
cur = cur->next_nonempty;
}
return T();
return {Priority(), T()};
}
void push_front(Priority priority, const T& thread_id) {

View File

@ -182,7 +182,14 @@ struct ExHeader_ARM11_SystemLocalCaps {
BitField<4, 4, u8> system_mode;
};
u8 priority;
u8 resource_limit_descriptor[0x10][2];
union {
u16 core1_schedule_flags;
BitField<0, 7, u16> max_cpu;
// Schedule mode flag, 0 -> "single", 1 -> "multi"
BitField<7, 1, u16> schedule_mode;
BitField<8, 8, u16> unknown;
};
u8 resource_limit_descriptor[0xF][2]; // Always 0 (unused?)
ExHeader_StorageInfo storage_info;
u8 service_access_control[0x20][8];
u8 ex_service_access_control[0x2][8];

View File

@ -202,6 +202,14 @@ void KernelSystem::RestoreMemoryState(u64 title_id) {
}
}
void KernelSystem::SetCore1ScheduleMode(Core1ScheduleMode mode) {
GetThreadManager(1).SetScheduleMode(mode);
}
void KernelSystem::UpdateCore1AppCpuLimit() {
GetThreadManager(1).UpdateAppCpuLimit();
}
template <class Archive>
void KernelSystem::serialize(Archive& ar, const unsigned int) {
ar & memory_regions;
@ -246,4 +254,10 @@ void New3dsHwCapabilities::serialize(Archive& ar, const unsigned int) {
}
SERIALIZE_IMPL(New3dsHwCapabilities)
template <class Archive>
void Core1CpuTime::serialize(Archive& ar, const unsigned int) {
ar & raw;
}
SERIALIZE_IMPL(Core1CpuTime)
} // namespace Kernel

View File

@ -136,6 +136,43 @@ private:
friend class boost::serialization::access;
};
enum class Core1ScheduleMode : u32 {
// https://github.com/LumaTeam/Luma3DS/blob/e35972ea/sysmodules/pm/source/reslimit.c#L144
// Starting by "sysmodule" threads, alternatively allow (if preemptible) only sysmodule
// threads, and then only application threads to run. This happens at a rate of 2ms *
// (cpuTime/100).
Multi,
// This divides the core1 time into slices of 25ms. Only one application thread is allowed to be
// created on core1. 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. This
// mode is half-broken due to a kernel bug.
Single,
};
class Core1CpuTime {
public:
Core1CpuTime() = default;
constexpr Core1CpuTime(s32 value) : raw(value) {}
static const Core1CpuTime PREEMPTION_DISABLED;
static const Core1CpuTime PREEMPTION_SYSMODULE;
static const Core1CpuTime PREEMPTION_EXCEMPTED;
operator s32() const {
return raw;
}
private:
s32 raw{};
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
inline constexpr Core1CpuTime Core1CpuTime::PREEMPTION_DISABLED{0};
inline constexpr Core1CpuTime Core1CpuTime::PREEMPTION_SYSMODULE{1000};
inline constexpr Core1CpuTime Core1CpuTime::PREEMPTION_EXCEMPTED{10000};
class KernelSystem {
public:
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
@ -378,6 +415,10 @@ public:
void RestoreMemoryState(u64 title_id);
void SetCore1ScheduleMode(Core1ScheduleMode mode);
void UpdateCore1AppCpuLimit();
private:
void MemoryInit(MemoryMode memory_mode, u64 override_init_time);
@ -447,3 +488,4 @@ private:
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::New3dsHwCapabilities)
BOOST_CLASS_EXPORT_KEY(Kernel::Core1CpuTime)

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,19 +9,24 @@
#include "common/archives.h"
#include "common/assert.h"
#include "common/settings.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimit)
SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimitList)
namespace Kernel {
ResourceLimit::ResourceLimit(KernelSystem& kernel) : Object(kernel) {}
ResourceLimit::ResourceLimit(KernelSystem& _kernel) : Object(_kernel), kernel(_kernel) {}
ResourceLimit::~ResourceLimit() = default;
std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelSystem& kernel, std::string name) {
std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelSystem& kernel,
ResourceLimitCategory category,
std::string name) {
auto resource_limit = std::make_shared<ResourceLimit>(kernel);
resource_limit->m_category = category;
resource_limit->m_name = std::move(name);
return resource_limit;
}
@ -36,6 +41,11 @@ s32 ResourceLimit::GetLimitValue(ResourceLimitType type) const {
return m_limit_values[index];
}
void ResourceLimit::SetCurrentValue(ResourceLimitType type, s32 value) {
const auto index = static_cast<std::size_t>(type);
m_current_values[index] = value;
}
void ResourceLimit::SetLimitValue(ResourceLimitType type, s32 value) {
const auto index = static_cast<std::size_t>(type);
m_limit_values[index] = value;
@ -66,9 +76,89 @@ bool ResourceLimit::Release(ResourceLimitType type, s32 amount) {
return true;
}
void ResourceLimit::ApplyAppMaxCPUSetting(std::shared_ptr<Kernel::Process>& process, u8 exh_mode,
u8 exh_cpu_limit) {
// List of 1.0 titles with max CPU time overrides. This is not a "hack" list,
// these values actually exist in the 3DS kernel.
static constexpr std::array<std::pair<u32, Core1CpuTime>, 16> cpu_time_overrides = {{
// 3DS sound (JPN, USA, EUR)
{0x205, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x215, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x225, Core1CpuTime::PREEMPTION_EXCEMPTED},
// Star Fox 64 3D (JPN)
{0x304, Core1CpuTime::PREEMPTION_EXCEMPTED},
// Super Monkey Ball 3D (JPN)
{0x32E, Core1CpuTime::PREEMPTION_EXCEMPTED},
// The Legend of Zelda: Ocarina of Time 3D (JPN, USA, EUR)
{0x334, 30},
{0x335, 30},
{0x336, 30},
// Doctor Lautrec to Boukyaku no Kishidan (JPN, USA, EUR)
{0x348, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x368, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x562, Core1CpuTime::PREEMPTION_EXCEMPTED},
// Pro Yakyuu Spirits 2011 (JPN)
{0x349, Core1CpuTime::PREEMPTION_EXCEMPTED},
// Super Monkey Ball 3D (USA, EUR)
{0x370, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x389, Core1CpuTime::PREEMPTION_EXCEMPTED},
// Star Fox 64 3D (USA, EUR)
{0x490, Core1CpuTime::PREEMPTION_EXCEMPTED},
{0x491, Core1CpuTime::PREEMPTION_EXCEMPTED},
}};
Core1ScheduleMode final_mode{};
Core1CpuTime limit_cpu_time{};
Core1CpuTime set_cpu_time = Core1CpuTime::PREEMPTION_DISABLED;
if (exh_mode == 0 && exh_cpu_limit == 0) {
// This is a 1.0 app with the CPU time value not set. Use default or hardcoded value.
final_mode = Core1ScheduleMode::Single;
limit_cpu_time = 80;
set_cpu_time = 25;
u32 uID = static_cast<u32>(((process->codeset->program_id >> 8) & 0xFFFFF));
auto it = std::find_if(cpu_time_overrides.begin(), cpu_time_overrides.end(),
[uID](auto& v) { return v.first == uID; });
if (it != cpu_time_overrides.end()) {
const Core1CpuTime& time = it->second;
if (static_cast<u32>(time) > 100 && static_cast<u32>(time) < 200) {
// This code path is actually unused
final_mode = Core1ScheduleMode::Multi;
set_cpu_time = limit_cpu_time = static_cast<u32>(time) - 100;
} else {
final_mode = Core1ScheduleMode::Single;
set_cpu_time = limit_cpu_time = time;
}
}
} else {
final_mode = (exh_mode == 0) ? Core1ScheduleMode::Single : Core1ScheduleMode::Multi;
limit_cpu_time = exh_cpu_limit;
}
// Normally done by PM through svcSetKernelState and svcSetResourceLimitValues.
SetLimitValue(ResourceLimitType::CpuTime, limit_cpu_time);
SetCurrentValue(ResourceLimitType::CpuTime, set_cpu_time);
kernel.SetCore1ScheduleMode(final_mode);
kernel.UpdateCore1AppCpuLimit();
}
template <class Archive>
void ResourceLimit::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar & m_category;
ar & m_name;
ar & m_limit_values;
ar & m_current_values;
@ -82,7 +172,8 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
const auto& appmemalloc = kernel.GetMemoryRegion(MemoryRegion::APPLICATION);
// Create the Application resource limit
auto resource_limit = ResourceLimit::Create(kernel, "Applications");
auto resource_limit =
ResourceLimit::Create(kernel, ResourceLimitCategory::Application, "Applications");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x18);
resource_limit->SetLimitValue(ResourceLimitType::Commit, appmemalloc->size);
resource_limit->SetLimitValue(ResourceLimitType::Thread, 0x20);
@ -92,11 +183,12 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x10);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x2);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x0);
resource_limits[static_cast<u8>(ResourceLimitCategory::Application)] = resource_limit;
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, Core1CpuTime::PREEMPTION_DISABLED);
resource_limits[static_cast<u8>(resource_limit->GetCategory())] = resource_limit;
// Create the SysApplet resource limit
resource_limit = ResourceLimit::Create(kernel, "System Applets");
resource_limit =
ResourceLimit::Create(kernel, ResourceLimitCategory::SysApplet, "System Applets");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x5E06000 : 0x2606000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0x1D : 0xE);
@ -106,11 +198,12 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x3);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x2710);
resource_limits[static_cast<u8>(ResourceLimitCategory::SysApplet)] = resource_limit;
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, Core1CpuTime::PREEMPTION_EXCEMPTED);
resource_limits[static_cast<u8>(resource_limit->GetCategory())] = resource_limit;
// Create the LibApplet resource limit
resource_limit = ResourceLimit::Create(kernel, "Library Applets");
resource_limit =
ResourceLimit::Create(kernel, ResourceLimitCategory::LibApplet, "Library Applets");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, 0x602000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, 0xE);
@ -120,11 +213,11 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x1);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x2710);
resource_limits[static_cast<u8>(ResourceLimitCategory::LibApplet)] = resource_limit;
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, Core1CpuTime::PREEMPTION_EXCEMPTED);
resource_limits[static_cast<u8>(resource_limit->GetCategory())] = resource_limit;
// Create the Other resource limit
resource_limit = ResourceLimit::Create(kernel, "Others");
resource_limit = ResourceLimit::Create(kernel, ResourceLimitCategory::Other, "Others");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x2182000 : 0x1682000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0xE1 : 0xCA);
@ -134,8 +227,8 @@ ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
resource_limit->SetLimitValue(ResourceLimitType::Timer, is_new_3ds ? 0x2C : 0x2B);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, is_new_3ds ? 0x1F : 0x1E);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, is_new_3ds ? 0x2D : 0x2B);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x3E8);
resource_limits[static_cast<u8>(ResourceLimitCategory::Other)] = resource_limit;
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, Core1CpuTime::PREEMPTION_SYSMODULE);
resource_limits[static_cast<u8>(resource_limit->GetCategory())] = resource_limit;
}
ResourceLimitList::~ResourceLimitList() = default;

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -42,6 +42,7 @@ public:
* Creates a resource limit object.
*/
static std::shared_ptr<ResourceLimit> Create(KernelSystem& kernel,
ResourceLimitCategory category,
std::string name = "Unknown");
std::string GetTypeName() const override {
@ -56,15 +57,25 @@ public:
return HANDLE_TYPE;
}
ResourceLimitCategory GetCategory() const {
return m_category;
}
s32 GetCurrentValue(ResourceLimitType type) const;
s32 GetLimitValue(ResourceLimitType type) const;
void SetCurrentValue(ResourceLimitType name, s32 value);
void SetLimitValue(ResourceLimitType name, s32 value);
bool Reserve(ResourceLimitType type, s32 amount);
bool Release(ResourceLimitType type, s32 amount);
void ApplyAppMaxCPUSetting(std::shared_ptr<Kernel::Process>& process, u8 exh_mode,
u8 exh_cpu_limit);
private:
Kernel::KernelSystem& kernel;
ResourceLimitCategory m_category;
using ResourceArray = std::array<s32, static_cast<std::size_t>(ResourceLimitType::Max)>;
ResourceArray m_limit_values{};
ResourceArray m_current_values{};

View File

@ -37,6 +37,9 @@ void ThreadManager::serialize(Archive& ar, const unsigned int) {
ar & ready_queue;
ar & wakeup_callback_table;
ar & thread_list;
ar & current_schedule_mode;
ar & single_time_limiter;
ar & multi_time_limiter;
}
SERIALIZE_IMPL(ThreadManager)
@ -56,6 +59,7 @@ void Thread::serialize(Archive& ar, const unsigned int file_version) {
ar & held_mutexes;
ar & pending_mutexes;
ar & owner_process;
ar & resource_limit_category;
ar & wait_objects;
ar & wait_address;
ar & name;
@ -179,33 +183,49 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
}
Thread* ThreadManager::PopNextReadyThread() {
Thread* next = nullptr;
Thread* next;
Thread* thread = GetCurrentThread();
if (thread && thread->status == ThreadStatus::Running) {
do {
// We have to do better than the current thread.
// This call returns null when that's not possible.
next = ready_queue.pop_first_better(thread->current_priority);
if (!next) {
// Otherwise just keep going with the current thread
next = thread;
break;
} else if (!next->can_schedule)
unscheduled_ready_queue.push_back(next);
} while (!next->can_schedule);
} else {
do {
next = ready_queue.pop_first();
if (next && !next->can_schedule)
unscheduled_ready_queue.push_back(next);
} while (next && !next->can_schedule);
}
while (true) {
std::vector<std::pair<u32, Thread*>> skipped;
u32 next_priority{};
next = nullptr;
while (!unscheduled_ready_queue.empty()) {
auto t = std::move(unscheduled_ready_queue.back());
ready_queue.push_back(t->current_priority, t);
unscheduled_ready_queue.pop_back();
if (thread && thread->status == ThreadStatus::Running) {
do {
// We have to do better than the current thread.
// This call returns null when that's not possible.
std::tie(next_priority, next) =
ready_queue.pop_first_better(thread->current_priority);
if (!next) {
// Otherwise just keep going with the current thread
next = thread;
break;
} else if (!next->can_schedule) {
skipped.push_back({next_priority, next});
}
} while (!next->can_schedule);
} else {
do {
std::tie(next_priority, next) = ready_queue.pop_first();
if (next && !next->can_schedule) {
skipped.push_back({next_priority, next});
}
} while (next && !next->can_schedule);
}
for (auto it = skipped.rbegin(); it != skipped.rend(); it++) {
ready_queue.push_front(it->first, it->second);
}
// Try to time limit the selected thread on core 1
if (core_id == 1 && next && GetCpuLimiter()->DoTimeLimit(next)) {
// If the thread is time limited, select the next one
continue;
}
break;
}
return next;
@ -392,6 +412,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
thread->name = std::move(name);
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
thread->owner_process = owner_process;
thread->resource_limit_category = owner_process->resource_limit->GetCategory();
CASCADE_RESULT(thread->tls_address, owner_process->AllocateThreadLocalStorage());
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
@ -516,10 +537,158 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + command_header_offset;
}
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) {
CpuLimiter::~CpuLimiter() {}
CpuLimiterMulti::CpuLimiterMulti(Kernel::KernelSystem& _kernel) : kernel(_kernel) {}
void CpuLimiterMulti::Initialize(bool is_single) {
// TODO(PabloMK7): The is_single variable is needed to prevent
// registering an event twice with the same name. Once CpuLimiterSingle
// is implemented we can remove it.
tick_event = kernel.timing.RegisterEvent(
fmt::format("Kernel::{}::tick_event", is_single ? "CpuLimiterSingle" : "CpuLimiterMulti"),
[this](u64, s64 cycles_late) { this->OnTick(cycles_late); });
}
void CpuLimiterMulti::Start() {
if (ready) {
return;
}
ready = true;
active = false;
curr_state = SchedState::APP; // So that ChangeState starts with SYS
app_cpu_time = Core1CpuTime::PREEMPTION_DISABLED;
}
void CpuLimiterMulti::End() {
if (!ready) {
return;
}
ready = false;
active = false;
kernel.timing.UnscheduleEvent(tick_event, 0);
WakeupSleepingThreads();
}
void CpuLimiterMulti::UpdateAppCpuLimit() {
if (!ready) {
return;
}
app_cpu_time = static_cast<u32>(kernel.ResourceLimit()
.GetForCategory(Kernel::ResourceLimitCategory::Application)
->GetCurrentValue(Kernel::ResourceLimitType::CpuTime));
if (app_cpu_time == Core1CpuTime::PREEMPTION_DISABLED) {
// No preemption, disable event
active = false;
kernel.timing.UnscheduleEvent(tick_event, 0);
WakeupSleepingThreads();
} else {
// Start preempting, enable event
if (active) {
// If we were active already, unschedule first
// so that the event is not scheduled twice.
// We could just not call ChangeState instead,
// but this allows adjusting the timing of the
// event sooner.
kernel.timing.UnscheduleEvent(tick_event, 0);
}
active = true;
ChangeState(0);
}
}
bool CpuLimiterMulti::DoTimeLimit(Thread* thread) {
if (!ready || !active) {
// Preemption is not active, don't do anything.
return false;
}
if (kernel.ResourceLimit()
.GetForCategory(thread->resource_limit_category)
->GetLimitValue(ResourceLimitType::CpuTime) == Core1CpuTime::PREEMPTION_EXCEMPTED) {
// The thread is excempted from preemption
return false;
}
// On real hardware, the kernel uses a KPreemptionTimer to determine if a
// thread needs to be time limited. This properly uses the resource limit
// value to check if it is a sysmodule or not. We can do this instead and
// it should be good enough. TODO(PabloMK7): fix?
if (thread->resource_limit_category == ResourceLimitCategory::Application &&
curr_state == SchedState::SYS ||
thread->resource_limit_category == ResourceLimitCategory::Other &&
curr_state == SchedState::APP) {
// Block thread as not in the current mode
thread->status = ThreadStatus::WaitSleep;
sleeping_thread_ids.push(thread->thread_id);
return true;
}
return false;
}
void CpuLimiterMulti::OnTick(s64 cycles_late) {
WakeupSleepingThreads();
ChangeState(cycles_late);
}
void CpuLimiterMulti::ChangeState(s64 cycles_late) {
curr_state = (curr_state == SchedState::SYS) ? SchedState::APP : SchedState::SYS;
s64 next_timer = base_tick_interval * (app_cpu_time / 100.f);
if (curr_state == SchedState::SYS) {
next_timer = base_tick_interval - next_timer;
}
if (next_timer > cycles_late) {
next_timer -= cycles_late;
}
kernel.timing.ScheduleEvent(next_timer, tick_event, 0, 1);
}
void CpuLimiterMulti::WakeupSleepingThreads() {
while (!sleeping_thread_ids.empty()) {
u32 curr_id = sleeping_thread_ids.front();
auto thread = kernel.GetThreadManager(1).GetThreadByID(curr_id);
if (thread && thread->status == ThreadStatus::WaitSleep) {
thread->ResumeFromWait();
}
sleeping_thread_ids.pop();
}
}
template <class Archive>
void CpuLimiterMulti::serialize(Archive& ar, const unsigned int) {
ar & ready;
ar & active;
ar & app_cpu_time;
ar & curr_state;
std::vector<u32> v;
if (Archive::is_loading::value) {
ar & v;
for (auto it : v) {
sleeping_thread_ids.push(it);
}
} else {
std::queue<u32> temp = sleeping_thread_ids;
while (!temp.empty()) {
v.push_back(temp.front());
temp.pop();
}
ar & v;
}
}
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id)
: kernel(kernel), core_id(core_id), current_schedule_mode(Core1ScheduleMode::Multi),
single_time_limiter(kernel), multi_time_limiter(kernel) {
ThreadWakeupEventType = kernel.timing.RegisterEvent(
"ThreadWakeupCallback_" + std::to_string(core_id),
[this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); });
if (core_id == 1) {
single_time_limiter.Initialize(true);
multi_time_limiter.Initialize(false);
}
}
ThreadManager::~ThreadManager() {
@ -532,13 +701,33 @@ std::span<const std::shared_ptr<Thread>> ThreadManager::GetThreadList() const {
return thread_list;
}
std::shared_ptr<Thread> ThreadManager::GetThreadByID(u32 thread_id) const {
for (auto& thread : thread_list) {
if (thread->thread_id == thread_id) {
return thread;
}
}
return nullptr;
}
void ThreadManager::SetScheduleMode(Core1ScheduleMode mode) {
GetCpuLimiter()->End();
current_schedule_mode = mode;
if (mode == Core1ScheduleMode::Single) {
LOG_WARNING(Kernel, "Unimplemented \"Single\" schedule mode.");
}
GetCpuLimiter()->Start();
}
void ThreadManager::UpdateAppCpuLimit() {
GetCpuLimiter()->UpdateAppCpuLimit();
}
std::shared_ptr<Thread> KernelSystem::GetThreadByID(u32 thread_id) const {
for (u32 core_id = 0; core_id < Core::System::GetInstance().GetNumCores(); core_id++) {
const auto thread_list = GetThreadManager(core_id).GetThreadList();
for (auto& thread : thread_list) {
if (thread->thread_id == thread_id) {
return thread;
}
auto ret = GetThreadManager(core_id).GetThreadByID(thread_id);
if (ret) {
return ret;
}
}
return nullptr;

View File

@ -15,11 +15,13 @@
#include <vector>
#include <boost/container/flat_set.hpp>
#include <boost/serialization/export.hpp>
#include <queue>
#include "common/common_types.h"
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core_timing.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
@ -49,7 +51,7 @@ enum class ThreadStatus {
Running, ///< Currently running
Ready, ///< Ready to run
WaitArb, ///< Waiting on an address arbiter
WaitSleep, ///< Waiting due to a SleepThread SVC
WaitSleep, ///< Waiting due to a SleepThread SVC or time limited
WaitIPC, ///< Waiting for the reply from an IPC request
WaitSynchAny, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
WaitSynchAll, ///< Waiting due to WaitSynchronizationN with wait_all = true
@ -81,6 +83,64 @@ private:
friend class boost::serialization::access;
};
class CpuLimiter {
public:
CpuLimiter() = default;
virtual ~CpuLimiter() = 0;
virtual void Start() = 0;
virtual void End() = 0;
virtual void UpdateAppCpuLimit() = 0;
virtual bool DoTimeLimit(Thread* thread) = 0;
};
class CpuLimiterMulti : public CpuLimiter {
public:
CpuLimiterMulti(Kernel::KernelSystem& kernel);
~CpuLimiterMulti() override = default;
void Initialize(bool is_single);
void Start() override;
void End() override;
void UpdateAppCpuLimit() override;
bool DoTimeLimit(Thread* thread) override;
private:
enum class SchedState : u32 {
APP,
SYS,
};
void OnTick(s64 cycles_late);
void ChangeState(s64 cycles_late);
void WakeupSleepingThreads();
static constexpr u64 base_tick_interval = nsToCycles(2'000'000); // 2ms
Kernel::KernelSystem& kernel;
Core::TimingEventType* tick_event{};
bool ready = false;
bool active = false;
Core1CpuTime app_cpu_time = Core1CpuTime::PREEMPTION_DISABLED;
SchedState curr_state{};
std::queue<u32> sleeping_thread_ids;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
// TODO(PabloMK7): Replace with proper implementation
using CpuLimiterSingle = CpuLimiterMulti;
class ThreadManager {
public:
explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id);
@ -126,10 +186,21 @@ public:
*/
std::span<const std::shared_ptr<Thread>> GetThreadList() const;
std::shared_ptr<Thread> GetThreadByID(u32 thread_id) const;
void SetCPU(Core::ARM_Interface& cpu_) {
cpu = &cpu_;
}
void SetScheduleMode(Core1ScheduleMode mode);
void UpdateAppCpuLimit();
CpuLimiter* GetCpuLimiter() {
return (current_schedule_mode == Core1ScheduleMode::Single) ? &single_time_limiter
: &multi_time_limiter;
}
private:
/**
* Switches the CPU's active thread context to that of the specified thread
@ -151,11 +222,11 @@ private:
void ThreadWakeupCallback(u64 thread_id, s64 cycles_late);
Kernel::KernelSystem& kernel;
Core::ARM_Interface* cpu;
u32 core_id;
Core::ARM_Interface* cpu{};
std::shared_ptr<Thread> current_thread;
Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue;
std::deque<Thread*> unscheduled_ready_queue;
std::unordered_map<u64, Thread*> wakeup_callback_table;
/// Event type for the thread wake up event
@ -164,6 +235,10 @@ private:
// Lists all threadsthat aren't deleted.
std::vector<std::shared_ptr<Thread>> thread_list;
Core1ScheduleMode current_schedule_mode{};
CpuLimiterSingle single_time_limiter;
CpuLimiterMulti multi_time_limiter;
friend class Thread;
friend class KernelSystem;
@ -316,6 +391,8 @@ public:
std::weak_ptr<Process> owner_process{}; ///< Process that owns this thread
ResourceLimitCategory resource_limit_category{};
/// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization1/N.
std::vector<std::shared_ptr<WaitObject>> wait_objects{};

View File

@ -29,6 +29,7 @@
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/pm/pm_app.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/service.h"
#include "core/hw/aes/ccm.h"
@ -47,7 +48,6 @@ void Module::serialize(Archive& ar, const unsigned int file_version) {
ar & shared_font_mem;
ar & shared_font_loaded;
ar & shared_font_relocated;
ar & cpu_percent;
ar & screen_capture_post_permission;
ar & applet_manager;
ar & wireless_reboot_info;
@ -731,29 +731,39 @@ void Module::APTInterface::SetAppCpuTimeLimit(Kernel::HLERequestContext& ctx) {
const auto must_be_one = rp.Pop<u32>();
const auto value = rp.Pop<u32>();
LOG_WARNING(Service_APT, "(STUBBED) called, must_be_one={}, value={}", must_be_one, value);
if (must_be_one != 1) {
LOG_ERROR(Service_APT, "This value should be one, but is actually {}!", must_be_one);
}
apt->cpu_percent = value;
auto pm_app = Service::PM::GetServiceAPP(apt->system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); // No error
if (pm_app) {
rb.Push(pm_app->UpdateResourceLimit(Kernel::ResourceLimitType::CpuTime, value));
} else {
LOG_ERROR(Service_APT, "Failed to get PM:APP module");
rb.Push(ResultUnknown);
}
}
void Module::APTInterface::GetAppCpuTimeLimit(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto must_be_one = rp.Pop<u32>();
LOG_WARNING(Service_APT, "(STUBBED) called, must_be_one={}", must_be_one);
if (must_be_one != 1) {
LOG_ERROR(Service_APT, "This value should be one, but is actually {}!", must_be_one);
}
auto pm_app = Service::PM::GetServiceAPP(apt->system);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); // No error
rb.Push(apt->cpu_percent);
if (pm_app) {
auto res = pm_app->GetResourceLimit(Kernel::ResourceLimitType::CpuTime);
rb.Push(res.Code());
rb.Push(res.ValueOr(u32{}));
} else {
LOG_ERROR(Service_APT, "Failed to get PM:APP module");
rb.Push(ResultUnknown);
rb.Push(u32{});
}
}
void Module::APTInterface::PrepareToStartLibraryApplet(Kernel::HLERequestContext& ctx) {

View File

@ -1089,8 +1089,6 @@ private:
bool shared_font_loaded = false;
bool shared_font_relocated = false;
u32 cpu_percent = 0; ///< CPU time available to the running application
ScreencapPostPermission screen_capture_post_permission =
ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -11,7 +11,7 @@ namespace Service::PM {
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
std::make_shared<PM_APP>()->InstallAsService(service_manager);
std::make_shared<PM_APP>(system)->InstallAsService(service_manager);
std::make_shared<PM_DBG>()->InstallAsService(service_manager);
}

View File

@ -1,16 +1,19 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/pm/pm_app.h"
SERVICE_CONSTRUCT_IMPL(Service::PM::PM_APP)
SERIALIZE_EXPORT_IMPL(Service::PM::PM_APP)
namespace Service::PM {
PM_APP::PM_APP() : ServiceFramework("pm:app", 3) {
PM_APP::PM_APP(Core::System& _system) : ServiceFramework("pm:app", 3), system(_system) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "LaunchTitle"},
@ -22,8 +25,8 @@ PM_APP::PM_APP() : ServiceFramework("pm:app", 3) {
{0x0007, nullptr, "GetFIRMLaunchParams"},
{0x0008, nullptr, "GetTitleExheaderFlags"},
{0x0009, nullptr, "SetFIRMLaunchParams"},
{0x000A, nullptr, "SetAppResourceLimit"},
{0x000B, nullptr, "GetAppResourceLimit"},
{0x000A, &PM_APP::SetAppResourceLimit, "SetAppResourceLimit"},
{0x000B, &PM_APP::GetAppResourceLimit, "GetAppResourceLimit"},
{0x000C, nullptr, "UnregisterProcess"},
{0x000D, nullptr, "LaunchTitleUpdate"},
// clang-format on
@ -32,4 +35,65 @@ PM_APP::PM_APP() : ServiceFramework("pm:app", 3) {
RegisterHandlers(functions);
}
Result PM_APP::UpdateResourceLimit(Kernel::ResourceLimitType type, u32 value) {
auto res_limit =
system.Kernel().ResourceLimit().GetForCategory(Kernel::ResourceLimitCategory::Application);
if (type != Kernel::ResourceLimitType::CpuTime) {
return Result{ErrorDescription::NotImplemented, ErrorModule::PM,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent};
}
if (value <= res_limit->GetLimitValue(Kernel::ResourceLimitType::CpuTime)) {
res_limit->SetCurrentValue(Kernel::ResourceLimitType::CpuTime, value);
system.Kernel().UpdateCore1AppCpuLimit();
}
return ResultSuccess;
}
ResultVal<u32> PM_APP::GetResourceLimit(Kernel::ResourceLimitType type) {
auto res_limit =
system.Kernel().ResourceLimit().GetForCategory(Kernel::ResourceLimitCategory::Application);
if (type != Kernel::ResourceLimitType::CpuTime) {
return Result{ErrorDescription::NotImplemented, ErrorModule::PM,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent};
}
return static_cast<u32>(res_limit->GetCurrentValue(Kernel::ResourceLimitType::CpuTime));
}
void PM_APP::SetAppResourceLimit(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(1, false);
auto type = static_cast<Kernel::ResourceLimitType>(rp.Pop<u32>());
u32 value = rp.Pop<s32>();
rp.Skip(2, false);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(UpdateResourceLimit(type, value));
}
void PM_APP::GetAppResourceLimit(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(1, false);
auto type = static_cast<Kernel::ResourceLimitType>(rp.Pop<u32>());
rp.Skip(3, false);
u64 res_value = 0;
auto res = GetResourceLimit(type);
if (res.Succeeded()) {
res_value = static_cast<u64>(res.Unwrap());
}
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
rb.Push(res.Code());
rb.Push(res_value);
}
std::shared_ptr<PM_APP> GetServiceAPP(Core::System& system) {
return system.ServiceManager().GetService<PM_APP>("pm:app");
}
} // namespace Service::PM

View File

@ -1,22 +1,36 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/service.h"
namespace Service::PM {
class PM_APP final : public ServiceFramework<PM_APP> {
public:
PM_APP();
explicit PM_APP(Core::System& system);
~PM_APP() = default;
Result UpdateResourceLimit(Kernel::ResourceLimitType type, u32 value);
ResultVal<u32> GetResourceLimit(Kernel::ResourceLimitType type);
private:
Core::System& system;
void SetAppResourceLimit(Kernel::HLERequestContext& ctx);
void GetAppResourceLimit(Kernel::HLERequestContext& ctx);
SERVICE_SERIALIZATION_SIMPLE
};
std::shared_ptr<PM_APP> GetServiceAPP(Core::System& system);
} // namespace Service::PM
SERVICE_CONSTRUCT(Service::PM::PM_APP)
BOOST_CLASS_EXPORT_KEY(Service::PM::PM_APP)

View File

@ -293,6 +293,8 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr<Kernel::Process>& process)
process->resource_limit =
system.Kernel().ResourceLimit().GetForCategory(Kernel::ResourceLimitCategory::Application);
process->resource_limit->ApplyAppMaxCPUSetting(process, 1, 89);
// On real HW this is done with FS:Reg, but we can be lazy
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
fs_user->RegisterProgramInfo(process->GetObjectId(), process->codeset->program_id, filepath);

View File

@ -203,6 +203,14 @@ ResultStatus Apploader_Artic::LoadExecImpl(std::shared_ptr<Kernel::Process>& pro
exheader.arm11_system_local_caps.resource_limit_category);
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
// Update application max cpu setting. PM module uses the launch flags to determine
// this, but using the resource limit category is close enough.
if (category == Kernel::ResourceLimitCategory::Application) {
process->resource_limit->ApplyAppMaxCPUSetting(
process, exheader.arm11_system_local_caps.schedule_mode,
exheader.arm11_system_local_caps.max_cpu);
}
// When running N3DS-unaware titles pm will lie about the amount of memory available.
// This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
// APPLICATION. See:

View File

@ -393,6 +393,8 @@ ResultStatus AppLoader_ELF::Load(std::shared_ptr<Kernel::Process>& process) {
process->resource_limit =
system.Kernel().ResourceLimit().GetForCategory(Kernel::ResourceLimitCategory::Application);
process->resource_limit->ApplyAppMaxCPUSetting(process, 1, 89);
process->Run(48, Kernel::DEFAULT_STACK_SIZE);
is_loaded = true;

View File

@ -202,6 +202,14 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process)
overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category);
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
// Update application max cpu setting. PM module uses the launch flags to determine
// this, but using the resource limit category is close enough.
if (category == Kernel::ResourceLimitCategory::Application) {
process->resource_limit->ApplyAppMaxCPUSetting(
process, overlay_ncch->exheader_header.arm11_system_local_caps.schedule_mode,
overlay_ncch->exheader_header.arm11_system_local_caps.max_cpu);
}
// When running N3DS-unaware titles pm will lie about the amount of memory available.
// This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
// APPLICATION. See: