mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-04-07 18:01:26 -06:00
core: kernel: Implement thread cpu time limit for core1
This commit is contained in:
parent
9b045bf837
commit
af07b97f44
@ -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) {
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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{};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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{};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user