ams-mtc: add ams-mtc
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm {
|
||||
|
||||
void KInterruptController::SetupInterruptLines(s32 core_id) const {
|
||||
const size_t ITLines = (core_id == 0) ? 32 * ((m_gicd->typer & 0x1F) + 1) : NumLocalInterrupts;
|
||||
|
||||
for (size_t i = 0; i < ITLines / 32; i++) {
|
||||
m_gicd->icenabler[i] = 0xFFFFFFFF;
|
||||
m_gicd->icpendr[i] = 0xFFFFFFFF;
|
||||
m_gicd->icactiver[i] = 0xFFFFFFFF;
|
||||
m_gicd->igroupr[i] = 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ITLines; i++) {
|
||||
m_gicd->ipriorityr.bytes[i] = 0xFF;
|
||||
m_gicd->itargetsr.bytes[i] = 0x00;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ITLines / 16; i++) {
|
||||
m_gicd->icfgr[i] = 0x00000000;
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptController::Initialize(s32 core_id) {
|
||||
/* Setup pointers to ARM mmio. */
|
||||
m_gicd = GetPointer<volatile GicDistributor >(KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_InterruptDistributor));
|
||||
m_gicc = GetPointer<volatile GicCpuInterface>(KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_InterruptCpuInterface));
|
||||
|
||||
/* Clear CTLRs. */
|
||||
m_gicc->ctlr = 0;
|
||||
if (core_id == 0) {
|
||||
m_gicd->ctlr = 0;
|
||||
}
|
||||
|
||||
m_gicc->pmr = 0;
|
||||
m_gicc->bpr = 7;
|
||||
|
||||
/* Setup all interrupt lines. */
|
||||
SetupInterruptLines(core_id);
|
||||
|
||||
/* Set CTLRs. */
|
||||
if (core_id == 0) {
|
||||
m_gicd->ctlr = 1;
|
||||
}
|
||||
m_gicc->ctlr = 1;
|
||||
|
||||
/* Set the mask for this core. */
|
||||
SetGicMask(core_id);
|
||||
|
||||
/* Set the priority level. */
|
||||
SetPriorityLevel(PriorityLevel_Low);
|
||||
}
|
||||
|
||||
void KInterruptController::Finalize(s32 core_id) {
|
||||
/* Clear CTLRs. */
|
||||
if (core_id == 0) {
|
||||
m_gicd->ctlr = 0;
|
||||
}
|
||||
m_gicc->ctlr = 0;
|
||||
|
||||
/* Set the priority level. */
|
||||
SetPriorityLevel(PriorityLevel_High);
|
||||
|
||||
/* Setup all interrupt lines. */
|
||||
SetupInterruptLines(core_id);
|
||||
|
||||
/* Clear pointers, if needed. */
|
||||
if (core_id == 0) {
|
||||
m_gicd = nullptr;
|
||||
m_gicc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptController::SaveCoreLocal(LocalState *state) const {
|
||||
/* Save isenabler. */
|
||||
for (size_t i = 0; i < util::size(state->isenabler); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
state->isenabler[i] = m_gicd->isenabler[i + Offset];
|
||||
m_gicd->isenabler[i + Offset] = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* Save ipriorityr. */
|
||||
for (size_t i = 0; i < util::size(state->ipriorityr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
state->ipriorityr[i] = m_gicd->ipriorityr.words[i + Offset];
|
||||
m_gicd->ipriorityr.words[i + Offset] = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* Save itargetsr. */
|
||||
for (size_t i = 0; i < util::size(state->itargetsr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
state->itargetsr[i] = m_gicd->itargetsr.words[i + Offset];
|
||||
}
|
||||
|
||||
/* Save icfgr. */
|
||||
for (size_t i = 0; i < util::size(state->icfgr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
state->icfgr[i] = m_gicd->icfgr[i + Offset];
|
||||
}
|
||||
|
||||
/* Save spendsgir. */
|
||||
for (size_t i = 0; i < util::size(state->spendsgir); ++i) {
|
||||
state->spendsgir[i] = m_gicd->spendsgir[i];
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptController::SaveGlobal(GlobalState *state) const {
|
||||
/* Save isenabler. */
|
||||
for (size_t i = 0; i < util::size(state->isenabler); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.isenabler);
|
||||
state->isenabler[i] = m_gicd->isenabler[i + Offset];
|
||||
m_gicd->isenabler[i + Offset] = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* Save ipriorityr. */
|
||||
for (size_t i = 0; i < util::size(state->ipriorityr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.ipriorityr);
|
||||
state->ipriorityr[i] = m_gicd->ipriorityr.words[i + Offset];
|
||||
m_gicd->ipriorityr.words[i + Offset] = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* Save itargetsr. */
|
||||
for (size_t i = 0; i < util::size(state->itargetsr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.itargetsr);
|
||||
state->itargetsr[i] = m_gicd->itargetsr.words[i + Offset];
|
||||
}
|
||||
|
||||
/* Save icfgr. */
|
||||
for (size_t i = 0; i < util::size(state->icfgr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.icfgr);
|
||||
state->icfgr[i] = m_gicd->icfgr[i + Offset];
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptController::RestoreCoreLocal(const LocalState *state) const {
|
||||
/* Restore ipriorityr. */
|
||||
for (size_t i = 0; i < util::size(state->ipriorityr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
m_gicd->ipriorityr.words[i + Offset] = state->ipriorityr[i];
|
||||
}
|
||||
|
||||
/* Restore itargetsr. */
|
||||
for (size_t i = 0; i < util::size(state->itargetsr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
m_gicd->itargetsr.words[i + Offset] = state->itargetsr[i];
|
||||
}
|
||||
|
||||
/* Restore icfgr. */
|
||||
for (size_t i = 0; i < util::size(state->icfgr); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
m_gicd->icfgr[i + Offset] = state->icfgr[i];
|
||||
}
|
||||
|
||||
/* Restore isenabler. */
|
||||
for (size_t i = 0; i < util::size(state->isenabler); ++i) {
|
||||
constexpr size_t Offset = 0;
|
||||
m_gicd->icenabler[i + Offset] = 0xFFFFFFFF;
|
||||
m_gicd->isenabler[i + Offset] = state->isenabler[i];
|
||||
}
|
||||
|
||||
/* Restore spendsgir. */
|
||||
for (size_t i = 0; i < util::size(state->spendsgir); ++i) {
|
||||
m_gicd->spendsgir[i] = state->spendsgir[i];
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptController::RestoreGlobal(const GlobalState *state) const {
|
||||
/* Restore ipriorityr. */
|
||||
for (size_t i = 0; i < util::size(state->ipriorityr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.ipriorityr);
|
||||
m_gicd->ipriorityr.words[i + Offset] = state->ipriorityr[i];
|
||||
}
|
||||
|
||||
/* Restore itargetsr. */
|
||||
for (size_t i = 0; i < util::size(state->itargetsr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.itargetsr);
|
||||
m_gicd->itargetsr.words[i + Offset] = state->itargetsr[i];
|
||||
}
|
||||
|
||||
/* Restore icfgr. */
|
||||
for (size_t i = 0; i < util::size(state->icfgr); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.icfgr);
|
||||
m_gicd->icfgr[i + Offset] = state->icfgr[i];
|
||||
}
|
||||
|
||||
/* Restore isenabler. */
|
||||
for (size_t i = 0; i < util::size(state->isenabler); ++i) {
|
||||
constexpr size_t Offset = util::size(LocalState{}.isenabler);
|
||||
m_gicd->icenabler[i + Offset] = 0xFFFFFFFF;
|
||||
m_gicd->isenabler[i + Offset] = state->isenabler[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
/* Include the common implementation. */
|
||||
#include "../arm/kern_generic_interrupt_controller.inc"
|
||||
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64::cpu {
|
||||
|
||||
/* Declare prototype to be implemented in asm. */
|
||||
void SynchronizeAllCoresImpl(s32 *sync_var, s32 num_cores);
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE void SetEventLocally() {
|
||||
__asm__ __volatile__("sevl" ::: "memory");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void WaitForEvent() {
|
||||
__asm__ __volatile__("wfe" ::: "memory");
|
||||
}
|
||||
|
||||
class KScopedCoreMigrationDisable {
|
||||
public:
|
||||
ALWAYS_INLINE KScopedCoreMigrationDisable() { GetCurrentThread().DisableCoreMigration(); }
|
||||
|
||||
ALWAYS_INLINE ~KScopedCoreMigrationDisable() { GetCurrentThread().EnableCoreMigration(); }
|
||||
};
|
||||
|
||||
class KScopedCacheMaintenance {
|
||||
private:
|
||||
bool m_active;
|
||||
public:
|
||||
ALWAYS_INLINE KScopedCacheMaintenance() {
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
if (m_active = !GetCurrentThread().IsInCacheMaintenanceOperation(); m_active) {
|
||||
GetCurrentThread().SetInCacheMaintenanceOperation();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ~KScopedCacheMaintenance() {
|
||||
if (m_active) {
|
||||
GetCurrentThread().ClearInCacheMaintenanceOperation();
|
||||
}
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
}
|
||||
};
|
||||
|
||||
/* Nintendo registers a handler for a SGI on thread termination, but does not handle anything. */
|
||||
/* This is sufficient, because post-interrupt scheduling is all they really intend to occur. */
|
||||
class KThreadTerminationInterruptHandler : public KInterruptHandler {
|
||||
public:
|
||||
constexpr KThreadTerminationInterruptHandler() : KInterruptHandler() { /* ... */ }
|
||||
|
||||
virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override {
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class KPerformanceCounterInterruptHandler : public KInterruptHandler {
|
||||
private:
|
||||
static constinit inline KLightLock s_lock;
|
||||
private:
|
||||
u64 m_counter;
|
||||
s32 m_which;
|
||||
bool m_done;
|
||||
public:
|
||||
constexpr KPerformanceCounterInterruptHandler() : KInterruptHandler(), m_counter(), m_which(), m_done() { /* ... */ }
|
||||
|
||||
static KLightLock &GetLock() { return s_lock; }
|
||||
|
||||
void Setup(s32 w) {
|
||||
m_done = false;
|
||||
m_which = w;
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
while (!m_done) {
|
||||
cpu::Yield();
|
||||
}
|
||||
}
|
||||
|
||||
u64 GetCounter() const { return m_counter; }
|
||||
|
||||
/* Nintendo misuses this per their own API, but it's functional. */
|
||||
virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override {
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
|
||||
if (m_which < 0) {
|
||||
m_counter = cpu::GetCycleCounter();
|
||||
} else {
|
||||
m_counter = cpu::GetPerformanceCounter(m_which);
|
||||
}
|
||||
DataMemoryBarrierInnerShareable();
|
||||
m_done = true;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class KCoreBarrierInterruptHandler : public KInterruptHandler {
|
||||
private:
|
||||
util::Atomic<u64> m_target_cores;
|
||||
KLightLock m_lock;
|
||||
public:
|
||||
constexpr KCoreBarrierInterruptHandler() : KInterruptHandler(), m_target_cores(0), m_lock() { /* ... */ }
|
||||
|
||||
virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override {
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
m_target_cores &= ~(1ul << GetCurrentCoreId());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SynchronizeCores(u64 core_mask) {
|
||||
/* Acquire exclusive access to ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* If necessary, force synchronization with other cores. */
|
||||
if (const u64 other_cores_mask = core_mask & ~(1ul << GetCurrentCoreId()); other_cores_mask != 0) {
|
||||
/* Send an interrupt to the other cores. */
|
||||
m_target_cores = other_cores_mask;
|
||||
cpu::DataSynchronizationBarrierInnerShareable();
|
||||
Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_CoreBarrier, other_cores_mask);
|
||||
|
||||
/* Wait for all cores to acknowledge. */
|
||||
{
|
||||
u64 v;
|
||||
__asm__ __volatile__("ldaxr %[v], %[p]\n"
|
||||
"cbz %[v], 1f\n"
|
||||
"0:\n"
|
||||
"wfe\n"
|
||||
"ldaxr %[v], %[p]\n"
|
||||
"cbnz %[v], 0b\n"
|
||||
"1:\n"
|
||||
: [v]"=&r"(v)
|
||||
: [p]"Q"(*reinterpret_cast<u64 *>(std::addressof(m_target_cores)))
|
||||
: "memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class KCacheHelperInterruptHandler : public KInterruptHandler {
|
||||
private:
|
||||
static constexpr s32 ThreadPriority = 8;
|
||||
public:
|
||||
enum class Operation {
|
||||
Idle,
|
||||
InstructionMemoryBarrier,
|
||||
StoreDataCache,
|
||||
FlushDataCache,
|
||||
};
|
||||
private:
|
||||
KLightLock m_lock;
|
||||
KLightLock m_cv_lock;
|
||||
KLightConditionVariable m_cv;
|
||||
util::Atomic<u64> m_target_cores;
|
||||
volatile Operation m_operation;
|
||||
private:
|
||||
static void ThreadFunction(uintptr_t _this) {
|
||||
reinterpret_cast<KCacheHelperInterruptHandler *>(_this)->ThreadFunctionImpl();
|
||||
}
|
||||
|
||||
void ThreadFunctionImpl() {
|
||||
const u64 core_mask = (1ul << GetCurrentCoreId());
|
||||
while (true) {
|
||||
/* Wait for a request to come in. */
|
||||
{
|
||||
KScopedLightLock lk(m_cv_lock);
|
||||
while ((m_target_cores.Load() & core_mask) == 0) {
|
||||
m_cv.Wait(std::addressof(m_cv_lock));
|
||||
}
|
||||
}
|
||||
|
||||
/* Process the request. */
|
||||
this->ProcessOperation();
|
||||
|
||||
/* Broadcast, if there's nothing pending. */
|
||||
{
|
||||
KScopedLightLock lk(m_cv_lock);
|
||||
|
||||
m_target_cores &= ~core_mask;
|
||||
if (m_target_cores.Load() == 0) {
|
||||
m_cv.Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessOperation();
|
||||
public:
|
||||
constexpr KCacheHelperInterruptHandler() : KInterruptHandler(), m_lock(), m_cv_lock(), m_cv(util::ConstantInitialize), m_target_cores(0), m_operation(Operation::Idle) { /* ... */ }
|
||||
|
||||
void Initialize(s32 core_id) {
|
||||
/* Reserve a thread from the system limit. */
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1));
|
||||
|
||||
/* Create a new thread. */
|
||||
KThread *new_thread = KThread::Create();
|
||||
MESOSPHERE_ABORT_UNLESS(new_thread != nullptr);
|
||||
MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, ThreadFunction, reinterpret_cast<uintptr_t>(this), ThreadPriority, core_id));
|
||||
|
||||
/* Register the new thread. */
|
||||
KThread::Register(new_thread);
|
||||
|
||||
/* Run the thread. */
|
||||
new_thread->Run();
|
||||
}
|
||||
|
||||
virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override {
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
this->ProcessOperation();
|
||||
m_target_cores &= ~(1ul << GetCurrentCoreId());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RequestOperation(Operation op) {
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Create core masks for us to use. */
|
||||
constexpr u64 AllCoresMask = (1ul << cpu::NumCores) - 1ul;
|
||||
const u64 other_cores_mask = AllCoresMask & ~(1ul << GetCurrentCoreId());
|
||||
|
||||
if ((op == Operation::InstructionMemoryBarrier) || (Kernel::GetState() == Kernel::State::Initializing)) {
|
||||
/* Check that there's no on-going operation. */
|
||||
MESOSPHERE_ABORT_UNLESS(m_operation == Operation::Idle);
|
||||
MESOSPHERE_ABORT_UNLESS(m_target_cores.Load() == 0);
|
||||
|
||||
/* Set operation. */
|
||||
m_operation = op;
|
||||
|
||||
/* For certain operations, we want to send an interrupt. */
|
||||
m_target_cores = other_cores_mask;
|
||||
|
||||
const u64 target_mask = m_target_cores.Load();
|
||||
|
||||
DataSynchronizationBarrierInnerShareable();
|
||||
Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_CacheOperation, target_mask);
|
||||
|
||||
this->ProcessOperation();
|
||||
while (m_target_cores.Load() != 0) {
|
||||
cpu::Yield();
|
||||
}
|
||||
|
||||
/* Go idle again. */
|
||||
m_operation = Operation::Idle;
|
||||
} else {
|
||||
/* Lock condvar so that we can send and wait for acknowledgement of request. */
|
||||
KScopedLightLock cv_lk(m_cv_lock);
|
||||
|
||||
/* Check that there's no on-going operation. */
|
||||
MESOSPHERE_ABORT_UNLESS(m_operation == Operation::Idle);
|
||||
MESOSPHERE_ABORT_UNLESS(m_target_cores.Load() == 0);
|
||||
|
||||
/* Set operation. */
|
||||
m_operation = op;
|
||||
|
||||
/* Request all cores. */
|
||||
m_target_cores = AllCoresMask;
|
||||
|
||||
/* Use the condvar. */
|
||||
m_cv.Broadcast();
|
||||
while (m_target_cores.Load() != 0) {
|
||||
m_cv.Wait(std::addressof(m_cv_lock));
|
||||
}
|
||||
|
||||
/* Go idle again. */
|
||||
m_operation = Operation::Idle;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Instances of the interrupt handlers. */
|
||||
constinit KThreadTerminationInterruptHandler g_thread_termination_handler;
|
||||
constinit KCacheHelperInterruptHandler g_cache_operation_handler;
|
||||
constinit KCoreBarrierInterruptHandler g_core_barrier_handler;
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_PERFORMANCE_COUNTER)
|
||||
constinit KPerformanceCounterInterruptHandler g_performance_counter_handler[cpu::NumCores];
|
||||
#endif
|
||||
|
||||
/* Expose this as a global, for asm to use. */
|
||||
constinit s32 g_all_core_sync_count;
|
||||
|
||||
template<typename F>
|
||||
ALWAYS_INLINE void PerformCacheOperationBySetWayImpl(int level, F f) {
|
||||
/* Used in multiple locations. */
|
||||
const u64 level_sel_value = static_cast<u64>(level << 1);
|
||||
|
||||
/* Get the cache size id register value with interrupts disabled. */
|
||||
u64 ccsidr_value;
|
||||
{
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Configure the cache select register for our level. */
|
||||
cpu::SetCsselrEl1(level_sel_value);
|
||||
|
||||
/* Ensure our configuration takes before reading the cache size id register. */
|
||||
cpu::InstructionMemoryBarrier();
|
||||
|
||||
/* Get the cache size id register. */
|
||||
ccsidr_value = cpu::GetCcsidrEl1();
|
||||
}
|
||||
|
||||
/* Ensure that no memory inconsistencies occur between cache management invocations. */
|
||||
cpu::DataSynchronizationBarrier();
|
||||
|
||||
/* Get cache size id info. */
|
||||
CacheSizeIdRegisterAccessor ccsidr_el1(ccsidr_value);
|
||||
const int num_sets = ccsidr_el1.GetNumberOfSets();
|
||||
const int num_ways = ccsidr_el1.GetAssociativity();
|
||||
const int line_size = ccsidr_el1.GetLineSize();
|
||||
|
||||
const u64 way_shift = static_cast<u64>(__builtin_clz(num_ways));
|
||||
const u64 set_shift = static_cast<u64>(line_size + 4);
|
||||
|
||||
for (int way = 0; way <= num_ways; way++) {
|
||||
for (int set = 0; set <= num_sets; set++) {
|
||||
const u64 way_value = static_cast<u64>(way) << way_shift;
|
||||
const u64 set_value = static_cast<u64>(set) << set_shift;
|
||||
f(way_value | set_value | level_sel_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void FlushDataCacheLineBySetWayImpl(const u64 sw_value) {
|
||||
__asm__ __volatile__("dc cisw, %[v]" :: [v]"r"(sw_value) : "memory");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void StoreDataCacheLineBySetWayImpl(const u64 sw_value) {
|
||||
__asm__ __volatile__("dc csw, %[v]" :: [v]"r"(sw_value) : "memory");
|
||||
}
|
||||
|
||||
void StoreDataCacheBySetWay(int level) {
|
||||
PerformCacheOperationBySetWayImpl(level, StoreDataCacheLineBySetWayImpl);
|
||||
}
|
||||
|
||||
void FlushDataCacheBySetWay(int level) {
|
||||
PerformCacheOperationBySetWayImpl(level, FlushDataCacheLineBySetWayImpl);
|
||||
}
|
||||
|
||||
void KCacheHelperInterruptHandler::ProcessOperation() {
|
||||
switch (m_operation) {
|
||||
case Operation::Idle:
|
||||
break;
|
||||
case Operation::InstructionMemoryBarrier:
|
||||
InstructionMemoryBarrier();
|
||||
break;
|
||||
case Operation::StoreDataCache:
|
||||
StoreDataCacheBySetWay(0);
|
||||
cpu::DataSynchronizationBarrier();
|
||||
break;
|
||||
case Operation::FlushDataCache:
|
||||
FlushDataCacheBySetWay(0);
|
||||
cpu::DataSynchronizationBarrier();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result InvalidateDataCacheRange(uintptr_t start, uintptr_t end) {
|
||||
MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize));
|
||||
MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize));
|
||||
R_UNLESS(UserspaceAccess::InvalidateDataCache(start, end), svc::ResultInvalidCurrentMemory());
|
||||
DataSynchronizationBarrier();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result StoreDataCacheRange(uintptr_t start, uintptr_t end) {
|
||||
MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize));
|
||||
MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize));
|
||||
R_UNLESS(UserspaceAccess::StoreDataCache(start, end), svc::ResultInvalidCurrentMemory());
|
||||
DataSynchronizationBarrier();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result FlushDataCacheRange(uintptr_t start, uintptr_t end) {
|
||||
MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize));
|
||||
MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize));
|
||||
R_UNLESS(UserspaceAccess::FlushDataCache(start, end), svc::ResultInvalidCurrentMemory());
|
||||
DataSynchronizationBarrier();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void InvalidateEntireInstructionCacheLocalImpl() {
|
||||
__asm__ __volatile__("ic iallu" ::: "memory");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void InvalidateEntireInstructionCacheGlobalImpl() {
|
||||
__asm__ __volatile__("ic ialluis" ::: "memory");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SynchronizeCores(u64 core_mask) {
|
||||
/* Request a core barrier interrupt. */
|
||||
g_core_barrier_handler.SynchronizeCores(core_mask);
|
||||
}
|
||||
|
||||
void StoreCacheForInit(void *addr, size_t size) {
|
||||
/* Store the data cache for the specified range. */
|
||||
const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize);
|
||||
const uintptr_t end = start + size;
|
||||
for (uintptr_t cur = start; cur < end; cur += DataCacheLineSize) {
|
||||
__asm__ __volatile__("dc cvac, %[cur]" :: [cur]"r"(cur) : "memory");
|
||||
}
|
||||
|
||||
/* Data synchronization barrier. */
|
||||
DataSynchronizationBarrierInnerShareable();
|
||||
|
||||
/* Invalidate instruction cache. */
|
||||
InvalidateEntireInstructionCacheLocalImpl();
|
||||
|
||||
/* Ensure local instruction consistency. */
|
||||
EnsureInstructionConsistency();
|
||||
}
|
||||
|
||||
void FlushEntireDataCache() {
|
||||
KScopedCoreMigrationDisable dm;
|
||||
|
||||
CacheLineIdRegisterAccessor clidr_el1;
|
||||
const int levels_of_coherency = clidr_el1.GetLevelsOfCoherency();
|
||||
|
||||
/* Store cache from L2 up to the level of coherence (if there's an L3 cache or greater). */
|
||||
for (int level = 2; level < levels_of_coherency; ++level) {
|
||||
StoreDataCacheBySetWay(level - 1);
|
||||
}
|
||||
|
||||
/* Flush cache from the level of coherence down to L2. */
|
||||
for (int level = levels_of_coherency; level > 1; --level) {
|
||||
FlushDataCacheBySetWay(level - 1);
|
||||
}
|
||||
|
||||
/* Data synchronization barrier for full system. */
|
||||
DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
Result InvalidateDataCache(void *addr, size_t size) {
|
||||
/* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */
|
||||
KScopedCacheMaintenance cm;
|
||||
|
||||
const uintptr_t start = reinterpret_cast<uintptr_t>(addr);
|
||||
const uintptr_t end = start + size;
|
||||
uintptr_t aligned_start = util::AlignDown(start, DataCacheLineSize);
|
||||
uintptr_t aligned_end = util::AlignUp(end, DataCacheLineSize);
|
||||
|
||||
if (aligned_start != start) {
|
||||
R_TRY(FlushDataCacheRange(aligned_start, aligned_start + DataCacheLineSize));
|
||||
aligned_start += DataCacheLineSize;
|
||||
}
|
||||
|
||||
if (aligned_start < aligned_end && (aligned_end != end)) {
|
||||
aligned_end -= DataCacheLineSize;
|
||||
R_TRY(FlushDataCacheRange(aligned_end, aligned_end + DataCacheLineSize));
|
||||
}
|
||||
|
||||
if (aligned_start < aligned_end) {
|
||||
R_TRY(InvalidateDataCacheRange(aligned_start, aligned_end));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result StoreDataCache(const void *addr, size_t size) {
|
||||
/* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */
|
||||
KScopedCacheMaintenance cm;
|
||||
|
||||
const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize);
|
||||
const uintptr_t end = util::AlignUp( reinterpret_cast<uintptr_t>(addr) + size, DataCacheLineSize);
|
||||
|
||||
R_RETURN(StoreDataCacheRange(start, end));
|
||||
}
|
||||
|
||||
Result FlushDataCache(const void *addr, size_t size) {
|
||||
/* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */
|
||||
KScopedCacheMaintenance cm;
|
||||
|
||||
const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize);
|
||||
const uintptr_t end = util::AlignUp( reinterpret_cast<uintptr_t>(addr) + size, DataCacheLineSize);
|
||||
|
||||
R_RETURN(FlushDataCacheRange(start, end));
|
||||
}
|
||||
|
||||
void InvalidateEntireInstructionCache() {
|
||||
KScopedCoreMigrationDisable dm;
|
||||
|
||||
/* Invalidate the instruction cache on all cores. */
|
||||
InvalidateEntireInstructionCacheGlobalImpl();
|
||||
EnsureInstructionConsistency();
|
||||
|
||||
/* Request the interrupt helper to perform an instruction memory barrier. */
|
||||
g_cache_operation_handler.RequestOperation(KCacheHelperInterruptHandler::Operation::InstructionMemoryBarrier);
|
||||
}
|
||||
|
||||
void InitializeInterruptThreads(s32 core_id) {
|
||||
/* Initialize the cache operation handler. */
|
||||
g_cache_operation_handler.Initialize(core_id);
|
||||
|
||||
/* Bind all handlers to the relevant interrupts. */
|
||||
Kernel::GetInterruptManager().BindHandler(std::addressof(g_cache_operation_handler), KInterruptName_CacheOperation, core_id, KInterruptController::PriorityLevel_High, false, false);
|
||||
Kernel::GetInterruptManager().BindHandler(std::addressof(g_thread_termination_handler), KInterruptName_ThreadTerminate, core_id, KInterruptController::PriorityLevel_Scheduler, false, false);
|
||||
Kernel::GetInterruptManager().BindHandler(std::addressof(g_core_barrier_handler), KInterruptName_CoreBarrier, core_id, KInterruptController::PriorityLevel_Scheduler, false, false);
|
||||
|
||||
/* If we should, enable user access to the performance counter registers. */
|
||||
if (KTargetSystem::IsUserPmuAccessEnabled()) { SetPmUserEnrEl0(1ul); }
|
||||
|
||||
/* If we should, enable the kernel performance counter interrupt handler. */
|
||||
#if defined(MESOSPHERE_ENABLE_PERFORMANCE_COUNTER)
|
||||
Kernel::GetInterruptManager().BindHandler(std::addressof(g_performance_counter_handler[core_id]), KInterruptName_PerformanceCounter, core_id, KInterruptController::PriorityLevel_Timer, false, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SynchronizeAllCores() {
|
||||
SynchronizeAllCoresImpl(&g_all_core_sync_count, static_cast<s32>(cpu::NumCores));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::arch::arm64::cpu::SynchronizeAllCoresImpl(int *sync_var, int num_cores) */
|
||||
.section .text._ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii
|
||||
.type _ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii, %function
|
||||
_ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii:
|
||||
/* Loop until the sync var is less than num cores. */
|
||||
sevl
|
||||
1:
|
||||
wfe
|
||||
ldaxr w2, [x0]
|
||||
cmp w2, w1
|
||||
b.gt 1b
|
||||
|
||||
/* Increment the sync var. */
|
||||
2:
|
||||
ldaxr w2, [x0]
|
||||
add w3, w2, #1
|
||||
stlxr w4, w3, [x0]
|
||||
cbnz w4, 2b
|
||||
|
||||
/* Loop until the sync var matches our ticket. */
|
||||
add w3, w2, w1
|
||||
sevl
|
||||
3:
|
||||
wfe
|
||||
ldaxr w2, [x0]
|
||||
cmp w2, w3
|
||||
b.ne 3b
|
||||
|
||||
/* Check if the ticket is the last. */
|
||||
sub w2, w1, #1
|
||||
add w2, w2, w1
|
||||
cmp w3, w2
|
||||
b.eq 5f
|
||||
|
||||
/* Our ticket is not the last one. Increment. */
|
||||
4:
|
||||
ldaxr w2, [x0]
|
||||
add w3, w2, #1
|
||||
stlxr w4, w3, [x0]
|
||||
cbnz w4, 4b
|
||||
ret
|
||||
|
||||
/* Our ticket is the last one. */
|
||||
5:
|
||||
stlr wzr, [x0]
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::cpu::ClearPageToZeroImpl(void *) */
|
||||
.section .text._ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv
|
||||
.type _ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv, %function
|
||||
_ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv:
|
||||
/* Efficiently clear the page using dc zva. */
|
||||
dc zva, x0
|
||||
add x8, x0, #0x040
|
||||
dc zva, x8
|
||||
add x8, x0, #0x080
|
||||
dc zva, x8
|
||||
add x8, x0, #0x0c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x100
|
||||
dc zva, x8
|
||||
add x8, x0, #0x140
|
||||
dc zva, x8
|
||||
add x8, x0, #0x180
|
||||
dc zva, x8
|
||||
add x8, x0, #0x1c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x200
|
||||
dc zva, x8
|
||||
add x8, x0, #0x240
|
||||
dc zva, x8
|
||||
add x8, x0, #0x280
|
||||
dc zva, x8
|
||||
add x8, x0, #0x2c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x300
|
||||
dc zva, x8
|
||||
add x8, x0, #0x340
|
||||
dc zva, x8
|
||||
add x8, x0, #0x380
|
||||
dc zva, x8
|
||||
add x8, x0, #0x3c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x400
|
||||
dc zva, x8
|
||||
add x8, x0, #0x440
|
||||
dc zva, x8
|
||||
add x8, x0, #0x480
|
||||
dc zva, x8
|
||||
add x8, x0, #0x4c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x500
|
||||
dc zva, x8
|
||||
add x8, x0, #0x540
|
||||
dc zva, x8
|
||||
add x8, x0, #0x580
|
||||
dc zva, x8
|
||||
add x8, x0, #0x5c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x600
|
||||
dc zva, x8
|
||||
add x8, x0, #0x640
|
||||
dc zva, x8
|
||||
add x8, x0, #0x680
|
||||
dc zva, x8
|
||||
add x8, x0, #0x6c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x700
|
||||
dc zva, x8
|
||||
add x8, x0, #0x740
|
||||
dc zva, x8
|
||||
add x8, x0, #0x780
|
||||
dc zva, x8
|
||||
add x8, x0, #0x7c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x800
|
||||
dc zva, x8
|
||||
add x8, x0, #0x840
|
||||
dc zva, x8
|
||||
add x8, x0, #0x880
|
||||
dc zva, x8
|
||||
add x8, x0, #0x8c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0x900
|
||||
dc zva, x8
|
||||
add x8, x0, #0x940
|
||||
dc zva, x8
|
||||
add x8, x0, #0x980
|
||||
dc zva, x8
|
||||
add x8, x0, #0x9c0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xa00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xa40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xa80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xac0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xb00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xb40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xb80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xbc0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xc00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xc40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xc80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xcc0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xd00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xd40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xd80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xdc0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xe00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xe40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xe80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xec0
|
||||
dc zva, x8
|
||||
add x8, x0, #0xf00
|
||||
dc zva, x8
|
||||
add x8, x0, #0xf40
|
||||
dc zva, x8
|
||||
add x8, x0, #0xf80
|
||||
dc zva, x8
|
||||
add x8, x0, #0xfc0
|
||||
dc zva, x8
|
||||
ret
|
||||
@@ -0,0 +1,656 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::svc {
|
||||
|
||||
void RestoreContext(uintptr_t sp);
|
||||
|
||||
}
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
namespace {
|
||||
|
||||
enum EsrEc : u32 {
|
||||
EsrEc_Unknown = 0b000000,
|
||||
EsrEc_WaitForInterruptOrEvent = 0b000001,
|
||||
EsrEc_Cp15McrMrc = 0b000011,
|
||||
EsrEc_Cp15McrrMrrc = 0b000100,
|
||||
EsrEc_Cp14McrMrc = 0b000101,
|
||||
EsrEc_FpAccess = 0b000111,
|
||||
EsrEc_Cp14Mrrc = 0b001100,
|
||||
EsrEc_BranchTarget = 0b001101,
|
||||
EsrEc_IllegalExecution = 0b001110,
|
||||
EsrEc_Svc32 = 0b010001,
|
||||
EsrEc_Svc64 = 0b010101,
|
||||
EsrEc_SystemInstruction64 = 0b011000,
|
||||
EsrEc_SveZen = 0b011001,
|
||||
EsrEc_PointerAuthInstruction = 0b011100,
|
||||
EsrEc_InstructionAbortEl0 = 0b100000,
|
||||
EsrEc_InstructionAbortEl1 = 0b100001,
|
||||
EsrEc_PcAlignmentFault = 0b100010,
|
||||
EsrEc_DataAbortEl0 = 0b100100,
|
||||
EsrEc_DataAbortEl1 = 0b100101,
|
||||
EsrEc_SpAlignmentFault = 0b100110,
|
||||
EsrEc_FpException32 = 0b101000,
|
||||
EsrEc_FpException64 = 0b101100,
|
||||
EsrEc_SErrorInterrupt = 0b101111,
|
||||
EsrEc_BreakPointEl0 = 0b110000,
|
||||
EsrEc_BreakPointEl1 = 0b110001,
|
||||
EsrEc_SoftwareStepEl0 = 0b110010,
|
||||
EsrEc_SoftwareStepEl1 = 0b110011,
|
||||
EsrEc_WatchPointEl0 = 0b110100,
|
||||
EsrEc_WatchPointEl1 = 0b110101,
|
||||
EsrEc_BkptInstruction = 0b111000,
|
||||
EsrEc_BrkInstruction = 0b111100,
|
||||
};
|
||||
|
||||
|
||||
|
||||
u32 GetInstructionDataSupervisorMode(const KExceptionContext *context, u64 esr) {
|
||||
/* Check for THUMB usermode */
|
||||
if ((context->psr & 0x3F) == 0x30) {
|
||||
u32 insn = *reinterpret_cast<u16 *>(context->pc & ~0x1);
|
||||
/* Check if the instruction was 32-bit. */
|
||||
if ((esr >> 25) & 1) {
|
||||
insn = (insn << 16) | *reinterpret_cast<u16 *>((context->pc & ~0x1) + sizeof(u16));
|
||||
}
|
||||
return insn;
|
||||
} else {
|
||||
/* Not thumb, so just get the instruction. */
|
||||
return *reinterpret_cast<u32 *>(context->pc);
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetInstructionDataUserMode(const KExceptionContext *context) {
|
||||
/* Check for THUMB usermode */
|
||||
u32 insn = 0;
|
||||
if ((context->psr & 0x3F) == 0x30) {
|
||||
u16 insn_high = 0;
|
||||
if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_high), reinterpret_cast<u16 *>(context->pc & ~0x1), sizeof(insn_high))) {
|
||||
insn = insn_high;
|
||||
|
||||
/* Check if the instruction was a THUMB mode branch prefix. */
|
||||
if (((insn >> 11) & 0b11110) == 0b11110) {
|
||||
u16 insn_low = 0;
|
||||
if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_low), reinterpret_cast<u16 *>((context->pc & ~0x1) + sizeof(u16)), sizeof(insn_low))) {
|
||||
insn = (static_cast<u32>(insn_high) << 16) | (static_cast<u32>(insn_low) << 0);
|
||||
} else {
|
||||
insn = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
insn = 0;
|
||||
}
|
||||
} else {
|
||||
u32 insn_value = 0;
|
||||
if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_value), reinterpret_cast<u32 *>(context->pc), sizeof(insn_value))) {
|
||||
insn = insn_value;
|
||||
} else if (KTargetSystem::IsDebugMode() && (context->pc & 3) == 0 && UserspaceAccess::CopyMemoryFromUserSize32BitWithSupervisorAccess(std::addressof(insn_value), reinterpret_cast<u32 *>(context->pc))) {
|
||||
insn = insn_value;
|
||||
} else {
|
||||
insn = 0;
|
||||
}
|
||||
}
|
||||
return insn;
|
||||
}
|
||||
|
||||
void HandleUserException(KExceptionContext *context, u64 raw_esr, u64 raw_far, u64 afsr0, u64 afsr1, u32 data) {
|
||||
/* Pre-process exception registers as needed. */
|
||||
u64 esr = raw_esr;
|
||||
u64 far = raw_far;
|
||||
const u64 ec = (esr >> 26) & 0x3F;
|
||||
if (ec == EsrEc_InstructionAbortEl0 || ec == EsrEc_DataAbortEl0) {
|
||||
/* Adjust registers if a synchronous external abort has occurred with far not valid. */
|
||||
/* Mask 0x03F = Low 6 bits IFSC == 0x10: "Synchronous External abort, */
|
||||
/* not on translation table walk or hardware update of translation table. */
|
||||
/* Mask 0x400 = FnV = "FAR Not Valid" */
|
||||
/* TODO: How would we perform this check using named register accesses? */
|
||||
if ((esr & 0x43F) == 0x410) {
|
||||
/* Clear the faulting register on memory tagging exception. */
|
||||
far = 0;
|
||||
} else {
|
||||
/* If the faulting address is a kernel address, set ISFC = 4. */
|
||||
if (far >= ams::svc::AddressMemoryRegion39Size) {
|
||||
esr = (esr & 0xFFFFFFC0) | 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KProcess &cur_process = GetCurrentProcess();
|
||||
bool should_process_user_exception = KTargetSystem::IsUserExceptionHandlersEnabled();
|
||||
|
||||
/* In the event that we return from this exception, we want SPSR.SS set so that we advance an instruction if single-stepping. */
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
context->psr |= (1ul << 21);
|
||||
#endif
|
||||
|
||||
/* If we should process the user exception (and it's not a breakpoint), try to enter. */
|
||||
const bool is_software_break = (ec == EsrEc_Unknown || ec == EsrEc_IllegalExecution || ec == EsrEc_BkptInstruction || ec == EsrEc_BrkInstruction);
|
||||
const bool is_breakpoint = (ec == EsrEc_BreakPointEl0 || ec == EsrEc_SoftwareStepEl0 || ec == EsrEc_WatchPointEl0);
|
||||
if ((should_process_user_exception) &&
|
||||
!(is_software_break && cur_process.IsAttachedToDebugger() && KDebug::IsBreakInstruction(data, context->psr)) &&
|
||||
!(is_breakpoint))
|
||||
{
|
||||
if (cur_process.EnterUserException()) {
|
||||
/* Fill out the exception info. */
|
||||
const bool is_aarch64 = (context->psr & 0x10) == 0;
|
||||
if (is_aarch64) {
|
||||
/* 64-bit. */
|
||||
ams::svc::aarch64::ExceptionInfo *info = std::addressof(static_cast<ams::svc::aarch64::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info);
|
||||
|
||||
for (size_t i = 0; i < util::size(info->r); ++i) {
|
||||
info->r[i] = context->x[i];
|
||||
}
|
||||
info->sp = context->sp;
|
||||
info->lr = context->x[30];
|
||||
info->pc = context->pc;
|
||||
info->pstate = (context->psr & cpu::El0Aarch64PsrMask);
|
||||
info->afsr0 = afsr0;
|
||||
info->afsr1 = afsr1;
|
||||
info->esr = esr;
|
||||
info->far = far;
|
||||
} else {
|
||||
/* 32-bit. */
|
||||
ams::svc::aarch32::ExceptionInfo *info = std::addressof(static_cast<ams::svc::aarch32::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info);
|
||||
|
||||
for (size_t i = 0; i < util::size(info->r); ++i) {
|
||||
info->r[i] = context->x[i];
|
||||
}
|
||||
info->sp = context->x[13];
|
||||
info->lr = context->x[14];
|
||||
info->pc = context->pc;
|
||||
info->flags = 1;
|
||||
|
||||
info->status_64.pstate = (context->psr & cpu::El0Aarch32PsrMask);
|
||||
info->status_64.afsr0 = afsr0;
|
||||
info->status_64.afsr1 = afsr1;
|
||||
info->status_64.esr = esr;
|
||||
info->status_64.far = far;
|
||||
}
|
||||
|
||||
/* Save the debug parameters to the current thread. */
|
||||
GetCurrentThread().SaveDebugParams(raw_far, raw_esr, data);
|
||||
|
||||
/* Get the exception type. */
|
||||
u32 type;
|
||||
switch (ec) {
|
||||
case EsrEc_Unknown:
|
||||
case EsrEc_IllegalExecution:
|
||||
case EsrEc_Cp15McrMrc:
|
||||
case EsrEc_Cp15McrrMrrc:
|
||||
case EsrEc_Cp14McrMrc:
|
||||
case EsrEc_Cp14Mrrc:
|
||||
case EsrEc_SystemInstruction64:
|
||||
case EsrEc_BkptInstruction:
|
||||
case EsrEc_BrkInstruction:
|
||||
type = ams::svc::ExceptionType_InstructionAbort;
|
||||
break;
|
||||
case EsrEc_PcAlignmentFault:
|
||||
type = ams::svc::ExceptionType_UnalignedInstruction;
|
||||
break;
|
||||
case EsrEc_SpAlignmentFault:
|
||||
type = ams::svc::ExceptionType_UnalignedData;
|
||||
break;
|
||||
case EsrEc_Svc32:
|
||||
case EsrEc_Svc64:
|
||||
type = ams::svc::ExceptionType_InvalidSystemCall;
|
||||
break;
|
||||
case EsrEc_SErrorInterrupt:
|
||||
type = ams::svc::ExceptionType_MemorySystemError;
|
||||
break;
|
||||
case EsrEc_InstructionAbortEl0:
|
||||
type = ams::svc::ExceptionType_InstructionAbort;
|
||||
break;
|
||||
case EsrEc_DataAbortEl0:
|
||||
/* If esr.IFSC is "Alignment Fault", return UnalignedData instead of DataAbort. */
|
||||
if ((esr & 0x3F) == 0b100001) {
|
||||
type = ams::svc::ExceptionType_UnalignedData;
|
||||
} else {
|
||||
type = ams::svc::ExceptionType_DataAbort;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
type = ams::svc::ExceptionType_DataAbort;
|
||||
break;
|
||||
}
|
||||
|
||||
/* We want to enter at the process entrypoint, with x0 = type. */
|
||||
context->pc = GetInteger(cur_process.GetEntryPoint());
|
||||
context->x[0] = type;
|
||||
if (is_aarch64) {
|
||||
context->x[1] = GetInteger(cur_process.GetProcessLocalRegionAddress() + AMS_OFFSETOF(ams::svc::aarch64::ProcessLocalRegion, exception_info));
|
||||
|
||||
const auto *plr = GetPointer<ams::svc::aarch64::ProcessLocalRegion>(cur_process.GetProcessLocalRegionAddress());
|
||||
context->sp = util::AlignDown(reinterpret_cast<uintptr_t>(plr->data) + sizeof(plr->data), 0x10);
|
||||
context->psr = 0;
|
||||
} else {
|
||||
context->x[1] = GetInteger(cur_process.GetProcessLocalRegionAddress() + AMS_OFFSETOF(ams::svc::aarch32::ProcessLocalRegion, exception_info));
|
||||
|
||||
const auto *plr = GetPointer<ams::svc::aarch32::ProcessLocalRegion>(cur_process.GetProcessLocalRegionAddress());
|
||||
context->x[13] = util::AlignDown(reinterpret_cast<uintptr_t>(plr->data) + sizeof(plr->data), 0x08);
|
||||
context->psr = 0x10;
|
||||
}
|
||||
|
||||
/* Process that we're entering a usermode exception on the current thread. */
|
||||
GetCurrentThread().OnEnterUsermodeException();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we should, clear the thread's state as single-step. */
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
if (AMS_UNLIKELY(GetCurrentThread().IsHardwareSingleStep())) {
|
||||
GetCurrentThread().ClearHardwareSingleStep();
|
||||
cpu::MonitorDebugSystemControlRegisterAccessor().SetSoftwareStep(false).Store();
|
||||
cpu::InstructionMemoryBarrier();
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
/* Collect additional information based on the ec. */
|
||||
uintptr_t params[3] = {};
|
||||
switch (ec) {
|
||||
case EsrEc_Unknown:
|
||||
case EsrEc_IllegalExecution:
|
||||
case EsrEc_BkptInstruction:
|
||||
case EsrEc_BrkInstruction:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_UndefinedInstruction;
|
||||
params[1] = far;
|
||||
params[2] = data;
|
||||
}
|
||||
break;
|
||||
case EsrEc_PcAlignmentFault:
|
||||
case EsrEc_SpAlignmentFault:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_AlignmentFault;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_Svc32:
|
||||
case EsrEc_Svc64:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_UndefinedSystemCall;
|
||||
params[1] = far;
|
||||
params[2] = (esr & 0xFF);
|
||||
}
|
||||
break;
|
||||
case EsrEc_BreakPointEl0:
|
||||
case EsrEc_SoftwareStepEl0:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_BreakPoint;
|
||||
params[1] = far;
|
||||
params[2] = ams::svc::BreakPointType_HardwareInstruction;
|
||||
}
|
||||
break;
|
||||
case EsrEc_WatchPointEl0:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_BreakPoint;
|
||||
params[1] = far;
|
||||
params[2] = ams::svc::BreakPointType_HardwareData;
|
||||
}
|
||||
break;
|
||||
case EsrEc_SErrorInterrupt:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_MemorySystemError;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_InstructionAbortEl0:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_InstructionAbort;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_DataAbortEl0:
|
||||
default:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_DataAbort;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Process the debug event. */
|
||||
Result result = KDebug::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params));
|
||||
|
||||
/* If we should stop processing the exception, do so. */
|
||||
if (svc::ResultStopProcessingException::Includes(result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
{
|
||||
if (ec != EsrEc_SoftwareStepEl0) {
|
||||
/* If the exception wasn't single-step, print details. */
|
||||
MESOSPHERE_EXCEPTION_LOG("Exception occurred. ");
|
||||
|
||||
{
|
||||
/* Print the current thread's registers. */
|
||||
KDebug::PrintRegister();
|
||||
|
||||
/* Print a backtrace. */
|
||||
KDebug::PrintBacktrace();
|
||||
}
|
||||
} else {
|
||||
/* If the exception was single-step and we have no debug object, we should just return. */
|
||||
if (AMS_UNLIKELY(!cur_process.IsAttachedToDebugger())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* Print that an exception occurred. */
|
||||
MESOSPHERE_EXCEPTION_LOG("Exception occurred. ");
|
||||
|
||||
{
|
||||
/* Print the current thread's registers. */
|
||||
KDebug::PrintRegister();
|
||||
|
||||
/* Print a backtrace. */
|
||||
KDebug::PrintBacktrace();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If the SVC is handled, handle it. */
|
||||
if (!svc::ResultNotHandled::Includes(result)) {
|
||||
/* If we successfully enter jit debug, stop processing the exception. */
|
||||
if (cur_process.EnterJitDebug(ams::svc::DebugEvent_Exception, static_cast<ams::svc::DebugException>(params[0]), params[1], params[2])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Exit the current process. */
|
||||
cur_process.Exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* NOTE: This function is called from ASM. */
|
||||
void FpuContextSwitchHandler() {
|
||||
KThreadContext::FpuContextSwitchHandler(GetCurrentThreadPointer());
|
||||
}
|
||||
|
||||
/* NOTE: This function is called from ASM. */
|
||||
void ReturnFromException(Result user_result) {
|
||||
/* Get the current thread. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
|
||||
/* Get the current exception context. */
|
||||
KExceptionContext *e_ctx = GetExceptionContext(cur_thread);
|
||||
|
||||
/* Get the current process. */
|
||||
KProcess &cur_process = GetCurrentProcess();
|
||||
|
||||
/* Read the exception info that userland put in tls. */
|
||||
union {
|
||||
ams::svc::aarch64::ExceptionInfo info64;
|
||||
ams::svc::aarch32::ExceptionInfo info32;
|
||||
} info = {};
|
||||
|
||||
const bool is_aarch64 = (e_ctx->psr & 0x10) == 0;
|
||||
if (is_aarch64) {
|
||||
/* We're 64-bit. */
|
||||
info.info64 = static_cast<const ams::svc::aarch64::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info;
|
||||
} else {
|
||||
/* We're 32-bit. */
|
||||
info.info32 = static_cast<const ams::svc::aarch32::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info;
|
||||
}
|
||||
|
||||
/* Try to leave the user exception. */
|
||||
if (cur_process.LeaveUserException()) {
|
||||
/* Process that we're leaving a usermode exception on the current thread. */
|
||||
GetCurrentThread().OnLeaveUsermodeException();
|
||||
|
||||
/* Copy the user context to the thread context. */
|
||||
if (is_aarch64) {
|
||||
for (size_t i = 0; i < util::size(info.info64.r); ++i) {
|
||||
e_ctx->x[i] = info.info64.r[i];
|
||||
}
|
||||
e_ctx->x[30] = info.info64.lr;
|
||||
e_ctx->sp = info.info64.sp;
|
||||
e_ctx->pc = info.info64.pc;
|
||||
e_ctx->psr = (info.info64.pstate & cpu::El0Aarch64PsrMask) | (e_ctx->psr & ~cpu::El0Aarch64PsrMask);
|
||||
} else {
|
||||
for (size_t i = 0; i < util::size(info.info32.r); ++i) {
|
||||
e_ctx->x[i] = info.info32.r[i];
|
||||
}
|
||||
e_ctx->x[14] = info.info32.lr;
|
||||
e_ctx->x[13] = info.info32.sp;
|
||||
e_ctx->pc = info.info32.pc;
|
||||
e_ctx->psr = (info.info32.status_64.pstate & cpu::El0Aarch32PsrMask) | (e_ctx->psr & ~cpu::El0Aarch32PsrMask);
|
||||
}
|
||||
|
||||
/* Note that PC was adjusted. */
|
||||
e_ctx->write = 1;
|
||||
|
||||
if (R_SUCCEEDED(user_result)) {
|
||||
/* If result handling succeeded, just restore the context. */
|
||||
svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx));
|
||||
} else {
|
||||
/* Restore the debug params for the exception. */
|
||||
uintptr_t far, esr, data;
|
||||
GetCurrentThread().RestoreDebugParams(std::addressof(far), std::addressof(esr), std::addressof(data));
|
||||
|
||||
/* Pre-process exception registers as needed. */
|
||||
const u64 ec = (esr >> 26) & 0x3F;
|
||||
if (ec == EsrEc_InstructionAbortEl0 || ec == EsrEc_DataAbortEl0) {
|
||||
/* Adjust registers if a synchronous external abort has occurred with far not valid. */
|
||||
/* Mask 0x03F = Low 6 bits IFSC == 0x10: "Synchronous External abort, */
|
||||
/* not on translation table walk or hardware update of translation table. */
|
||||
/* Mask 0x400 = FnV = "FAR Not Valid" */
|
||||
/* TODO: How would we perform this check using named register accesses? */
|
||||
if ((esr & 0x43F) == 0x410) {
|
||||
/* Clear the faulting register on memory tagging exception. */
|
||||
far = 0;
|
||||
} else {
|
||||
/* If the faulting address is a kernel address, set ISFC = 4. */
|
||||
if (far >= ams::svc::AddressMemoryRegion39Size) {
|
||||
esr = (esr & 0xFFFFFFC0) | 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Collect additional information based on the ec. */
|
||||
uintptr_t params[3] = {};
|
||||
switch (ec) {
|
||||
case EsrEc_Unknown:
|
||||
case EsrEc_IllegalExecution:
|
||||
case EsrEc_BkptInstruction:
|
||||
case EsrEc_BrkInstruction:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_UndefinedInstruction;
|
||||
params[1] = far;
|
||||
params[2] = data;
|
||||
}
|
||||
break;
|
||||
case EsrEc_PcAlignmentFault:
|
||||
case EsrEc_SpAlignmentFault:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_AlignmentFault;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_Svc32:
|
||||
case EsrEc_Svc64:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_UndefinedSystemCall;
|
||||
params[1] = far;
|
||||
params[2] = (esr & 0xFF);
|
||||
}
|
||||
break;
|
||||
case EsrEc_SErrorInterrupt:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_MemorySystemError;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_InstructionAbortEl0:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_InstructionAbort;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
case EsrEc_DataAbortEl0:
|
||||
default:
|
||||
{
|
||||
params[0] = ams::svc::DebugException_DataAbort;
|
||||
params[1] = far;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Process the debug event. */
|
||||
Result result = KDebug::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params));
|
||||
|
||||
/* If the SVC is handled, handle it. */
|
||||
if (!svc::ResultNotHandled::Includes(result)) {
|
||||
/* If we should stop processing the exception, restore. */
|
||||
if (svc::ResultStopProcessingException::Includes(result)) {
|
||||
svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx));
|
||||
}
|
||||
|
||||
/* If we successfully enter jit debug, restore. */
|
||||
if (cur_process.EnterJitDebug(ams::svc::DebugEvent_Exception, static_cast<ams::svc::DebugException>(params[0]), params[1], params[2])) {
|
||||
svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx));
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, if result debug was returned, restore. */
|
||||
if (svc::ResultDebug::Includes(result)) {
|
||||
svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print that an exception occurred. */
|
||||
MESOSPHERE_EXCEPTION_LOG("Exception occurred. ");
|
||||
|
||||
/* Exit the current process. */
|
||||
GetCurrentProcess().Exit();
|
||||
}
|
||||
|
||||
/* NOTE: This function is called from ASM. */
|
||||
void HandleException(KExceptionContext *context) {
|
||||
MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled());
|
||||
|
||||
/* Retrieve information about the exception. */
|
||||
const bool is_user_mode = (context->psr & 0xF) == 0;
|
||||
const u64 esr = cpu::GetEsrEl1();
|
||||
const u64 afsr0 = cpu::GetAfsr0El1();
|
||||
const u64 afsr1 = cpu::GetAfsr1El1();
|
||||
u64 far = 0;
|
||||
u32 data = 0;
|
||||
|
||||
/* Collect far and data based on the ec. */
|
||||
switch ((esr >> 26) & 0x3F) {
|
||||
case EsrEc_Unknown:
|
||||
case EsrEc_IllegalExecution:
|
||||
case EsrEc_BkptInstruction:
|
||||
case EsrEc_BrkInstruction:
|
||||
far = context->pc;
|
||||
/* NOTE: Nintendo always calls GetInstructionDataUserMode. */
|
||||
if (is_user_mode) {
|
||||
data = GetInstructionDataUserMode(context);
|
||||
} else {
|
||||
data = GetInstructionDataSupervisorMode(context, esr);
|
||||
}
|
||||
break;
|
||||
case EsrEc_Svc32:
|
||||
if (context->psr & 0x20) {
|
||||
/* Thumb mode. */
|
||||
context->pc -= 2;
|
||||
} else {
|
||||
/* ARM mode. */
|
||||
context->pc -= 4;
|
||||
}
|
||||
far = context->pc;
|
||||
break;
|
||||
case EsrEc_Svc64:
|
||||
context->pc -= 4;
|
||||
far = context->pc;
|
||||
break;
|
||||
case EsrEc_BreakPointEl0:
|
||||
far = context->pc;
|
||||
break;
|
||||
default:
|
||||
far = cpu::GetFarEl1();
|
||||
break;
|
||||
}
|
||||
|
||||
/* Note that we're in an exception handler. */
|
||||
GetCurrentThread().SetInExceptionHandler();
|
||||
|
||||
/* Verify that spsr's M is allowable (EL0t). */
|
||||
{
|
||||
if (is_user_mode) {
|
||||
/* If the user disable count is set, we may need to pin the current thread. */
|
||||
if (GetCurrentThread().GetUserDisableCount() != 0 && GetCurrentProcess().GetPinnedThread(GetCurrentCoreId()) == nullptr) {
|
||||
KScopedSchedulerLock lk;
|
||||
|
||||
/* Pin the current thread. */
|
||||
GetCurrentProcess().PinCurrentThread();
|
||||
|
||||
/* Set the interrupt flag for the thread. */
|
||||
GetCurrentThread().SetInterruptFlag();
|
||||
}
|
||||
|
||||
/* Enable interrupts while we process the usermode exception. */
|
||||
{
|
||||
KScopedInterruptEnable ei;
|
||||
|
||||
/* Terminate the thread, if we should. */
|
||||
if (GetCurrentThread().IsTerminationRequested()) {
|
||||
GetCurrentThread().Exit();
|
||||
}
|
||||
|
||||
HandleUserException(context, esr, far, afsr0, afsr1, data);
|
||||
}
|
||||
} else {
|
||||
const s32 core_id = GetCurrentCoreId();
|
||||
|
||||
MESOSPHERE_LOG("%d: Unhandled Exception in Supervisor Mode\n", core_id);
|
||||
if (GetCurrentProcessPointer() != nullptr) {
|
||||
MESOSPHERE_LOG("%d: Current Process = %s\n", core_id, GetCurrentProcess().GetName());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 31; i++) {
|
||||
MESOSPHERE_LOG("%d: X[%02zu] = %016lx\n", core_id, i, context->x[i]);
|
||||
}
|
||||
MESOSPHERE_LOG("%d: PC = %016lx\n", core_id, context->pc);
|
||||
MESOSPHERE_LOG("%d: SP = %016lx\n", core_id, context->sp);
|
||||
|
||||
MESOSPHERE_PANIC("Unhandled Exception in Supervisor Mode\n");
|
||||
}
|
||||
|
||||
MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled());
|
||||
|
||||
/* Handle any DPC requests. */
|
||||
while (GetCurrentThread().HasDpc()) {
|
||||
KDpcManager::HandleDpc();
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that we're no longer in an exception handler. */
|
||||
GetCurrentThread().ClearInExceptionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,949 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
/* <stratosphere/rocrt/rocrt.hpp> */
|
||||
namespace ams::rocrt {
|
||||
|
||||
constexpr inline const u32 ModuleHeaderVersion = util::FourCC<'M','O','D','0'>::Code;
|
||||
|
||||
struct ModuleHeader {
|
||||
u32 signature;
|
||||
u32 dynamic_offset;
|
||||
u32 bss_start_offset;
|
||||
u32 bss_end_offset;
|
||||
u32 exception_info_start_offset;
|
||||
u32 exception_info_end_offset;
|
||||
u32 module_offset;
|
||||
};
|
||||
|
||||
struct ModuleHeaderLocation {
|
||||
u32 pad;
|
||||
u32 header_offset;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline u64 ForbiddenBreakPointFlagsMask = (((1ul << 40) - 1) << 24) | /* Reserved upper bits. */
|
||||
(((1ul << 1) - 1) << 23) | /* Match VMID BreakPoint Type. */
|
||||
(((1ul << 2) - 1) << 14) | /* Security State Control. */
|
||||
(((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */
|
||||
(((1ul << 4) - 1) << 9) | /* Reserved middle bits. */
|
||||
(((1ul << 2) - 1) << 3) | /* Reserved lower bits. */
|
||||
(((1ul << 2) - 1) << 1); /* Privileged Mode Control. */
|
||||
|
||||
static_assert(ForbiddenBreakPointFlagsMask == 0xFFFFFFFFFF80FE1Eul);
|
||||
|
||||
constexpr inline u64 ForbiddenWatchPointFlagsMask = (((1ul << 32) - 1) << 32) | /* Reserved upper bits. */
|
||||
(((1ul << 4) - 1) << 20) | /* WatchPoint Type. */
|
||||
(((1ul << 2) - 1) << 14) | /* Security State Control. */
|
||||
(((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */
|
||||
(((1ul << 2) - 1) << 1); /* Privileged Access Control. */
|
||||
|
||||
static_assert(ForbiddenWatchPointFlagsMask == 0xFFFFFFFF00F0E006ul);
|
||||
|
||||
}
|
||||
|
||||
uintptr_t KDebug::GetProgramCounter(const KThread &thread) {
|
||||
return GetExceptionContext(std::addressof(thread))->pc;
|
||||
}
|
||||
|
||||
void KDebug::SetPreviousProgramCounter() {
|
||||
/* Get the current thread. */
|
||||
KThread *thread = GetCurrentThreadPointer();
|
||||
MESOSPHERE_ASSERT(thread->IsCallingSvc());
|
||||
|
||||
/* Get the exception context. */
|
||||
KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
|
||||
/* Set the previous pc. */
|
||||
if (e_ctx->write == 0) {
|
||||
/* Subtract from the program counter. */
|
||||
if (thread->GetOwnerProcess()->Is64Bit()) {
|
||||
e_ctx->pc -= sizeof(u32);
|
||||
} else {
|
||||
e_ctx->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32);
|
||||
}
|
||||
|
||||
/* Mark that we've set. */
|
||||
e_ctx->write = 1;
|
||||
}
|
||||
}
|
||||
|
||||
Result KDebug::GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) {
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
|
||||
|
||||
/* Get the exception context. */
|
||||
const KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
|
||||
/* Get whether we're 64-bit. */
|
||||
const bool is_64_bit = this->Is64Bit();
|
||||
|
||||
/* If general registers are requested, get them. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) {
|
||||
/* We can always get X0-X7/R0-R7. */
|
||||
auto register_count = 8;
|
||||
if (!thread->IsCallingSvc() || thread->GetSvcId() == svc::SvcId_ReturnFromException) {
|
||||
if (is_64_bit) {
|
||||
/* We're not in an SVC, so we can get X0-X29. */
|
||||
register_count = 29;
|
||||
} else {
|
||||
/* We're 32-bit, so we should get R0-R12. */
|
||||
register_count = 13;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the registers. */
|
||||
for (auto i = 0; i < register_count; ++i) {
|
||||
out->r[i] = is_64_bit ? e_ctx->x[i] : static_cast<u32>(e_ctx->x[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* If control flags are requested, get them. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) {
|
||||
if (is_64_bit) {
|
||||
out->fp = e_ctx->x[29];
|
||||
out->lr = e_ctx->x[30];
|
||||
out->sp = e_ctx->sp;
|
||||
out->pc = e_ctx->pc;
|
||||
out->pstate = (e_ctx->psr & cpu::El0Aarch64PsrMask);
|
||||
|
||||
/* Adjust PC if we should. */
|
||||
if (e_ctx->write == 0 && thread->IsCallingSvc()) {
|
||||
out->pc -= sizeof(u32);
|
||||
}
|
||||
|
||||
out->tpidr = e_ctx->tpidr;
|
||||
} else {
|
||||
out->r[11] = static_cast<u32>(e_ctx->x[11]);
|
||||
out->r[13] = static_cast<u32>(e_ctx->x[13]);
|
||||
out->r[14] = static_cast<u32>(e_ctx->x[14]);
|
||||
out->lr = 0;
|
||||
out->sp = 0;
|
||||
out->pc = e_ctx->pc;
|
||||
out->pstate = (e_ctx->psr & cpu::El0Aarch32PsrMask);
|
||||
|
||||
/* Adjust PC if we should. */
|
||||
if (e_ctx->write == 0 && thread->IsCallingSvc()) {
|
||||
out->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32);
|
||||
}
|
||||
|
||||
out->tpidr = static_cast<u32>(e_ctx->tpidr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the FPU context. */
|
||||
R_RETURN(this->GetFpuContext(out, thread, context_flags));
|
||||
}
|
||||
|
||||
Result KDebug::SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) {
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
|
||||
|
||||
/* Get the exception context. */
|
||||
KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
|
||||
/* If general registers are requested, set them. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) {
|
||||
if (this->Is64Bit()) {
|
||||
/* Set X0-X28. */
|
||||
for (auto i = 0; i <= 28; ++i) {
|
||||
e_ctx->x[i] = ctx.r[i];
|
||||
}
|
||||
} else {
|
||||
/* Set R0-R12. */
|
||||
for (auto i = 0; i <= 12; ++i) {
|
||||
e_ctx->x[i] = static_cast<u32>(ctx.r[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If control flags are requested, set them. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) {
|
||||
/* Mark ourselve as having adjusted pc. */
|
||||
e_ctx->write = 1;
|
||||
|
||||
if (this->Is64Bit()) {
|
||||
e_ctx->x[29] = ctx.fp;
|
||||
e_ctx->x[30] = ctx.lr;
|
||||
e_ctx->sp = ctx.sp;
|
||||
e_ctx->pc = ctx.pc;
|
||||
e_ctx->psr = ((ctx.pstate & cpu::El0Aarch64PsrMask) | (e_ctx->psr & ~cpu::El0Aarch64PsrMask));
|
||||
e_ctx->tpidr = ctx.tpidr;
|
||||
} else {
|
||||
e_ctx->x[13] = static_cast<u32>(ctx.r[13]);
|
||||
e_ctx->x[14] = static_cast<u32>(ctx.r[14]);
|
||||
e_ctx->x[30] = 0;
|
||||
e_ctx->sp = 0;
|
||||
e_ctx->pc = static_cast<u32>(ctx.pc);
|
||||
e_ctx->psr = ((ctx.pstate & cpu::El0Aarch32PsrMask) | (e_ctx->psr & ~cpu::El0Aarch32PsrMask));
|
||||
e_ctx->tpidr = ctx.tpidr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the FPU context. */
|
||||
R_RETURN(this->SetFpuContext(ctx, thread, context_flags));
|
||||
}
|
||||
|
||||
Result KDebug::GetFpuContext(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) {
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
|
||||
|
||||
/* Succeed if there's nothing to do. */
|
||||
R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0);
|
||||
|
||||
/* Get the thread context. */
|
||||
KThreadContext *t_ctx = std::addressof(thread->GetContext());
|
||||
|
||||
/* Get the FPU control registers, if required. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) {
|
||||
out->fpsr = t_ctx->GetFpsr();
|
||||
out->fpcr = t_ctx->GetFpcr();
|
||||
}
|
||||
|
||||
/* Get the FPU registers, if required. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) {
|
||||
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
|
||||
const auto &caller_save = thread->GetCallerSaveFpuRegisters();
|
||||
const auto &callee_save = t_ctx->GetCalleeSaveFpuRegisters();
|
||||
|
||||
if (this->Is64Bit()) {
|
||||
KThreadContext::GetFpuRegisters(out->v, caller_save.fpu64, callee_save.fpu64);
|
||||
} else {
|
||||
KThreadContext::GetFpuRegisters(out->v, caller_save.fpu32, callee_save.fpu32);
|
||||
for (size_t i = KThreadContext::NumFpuRegisters / 2; i < KThreadContext::NumFpuRegisters; ++i) {
|
||||
out->v[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KDebug::SetFpuContext(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) {
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
|
||||
|
||||
/* Succeed if there's nothing to do. */
|
||||
R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0);
|
||||
|
||||
/* Get the thread context. */
|
||||
KThreadContext *t_ctx = std::addressof(thread->GetContext());
|
||||
|
||||
/* Set the FPU control registers, if required. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) {
|
||||
t_ctx->SetFpsr(ctx.fpsr);
|
||||
t_ctx->SetFpcr(ctx.fpcr);
|
||||
}
|
||||
|
||||
/* Set the FPU registers, if required. */
|
||||
if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) {
|
||||
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
|
||||
auto &caller_save = thread->GetCallerSaveFpuRegisters();
|
||||
auto &callee_save = t_ctx->GetCalleeSaveFpuRegisters();
|
||||
|
||||
if (this->Is64Bit()) {
|
||||
KThreadContext::SetFpuRegisters(caller_save.fpu64, callee_save.fpu64, ctx.v);
|
||||
} else {
|
||||
KThreadContext::SetFpuRegisters(caller_save.fpu32, callee_save.fpu32, ctx.v);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KDebug::BreakIfAttached(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) {
|
||||
const uintptr_t params[5] = { ams::svc::DebugException_UserBreak, GetProgramCounter(GetCurrentThread()), break_reason, address, size };
|
||||
R_RETURN(KDebugBase::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params)));
|
||||
}
|
||||
|
||||
#define MESOSPHERE_SET_HW_BREAK_POINT(ID, FLAGS, VALUE) \
|
||||
({ \
|
||||
cpu::SetDbgBcr##ID##El1(0); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
cpu::SetDbgBvr##ID##El1(VALUE); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
cpu::SetDbgBcr##ID##El1(FLAGS); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
})
|
||||
|
||||
#define MESOSPHERE_SET_HW_WATCH_POINT(ID, FLAGS, VALUE) \
|
||||
({ \
|
||||
cpu::SetDbgWcr##ID##El1(0); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
cpu::SetDbgWvr##ID##El1(VALUE); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
cpu::SetDbgWcr##ID##El1(FLAGS); \
|
||||
cpu::EnsureInstructionConsistencyFullSystem(); \
|
||||
})
|
||||
|
||||
Result KDebug::SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value) {
|
||||
/* Get the debug feature register. */
|
||||
cpu::DebugFeatureRegisterAccessor dfr0;
|
||||
|
||||
/* Extract interesting info from the debug feature register. */
|
||||
const auto num_bp = dfr0.GetNumBreakpoints();
|
||||
const auto num_wp = dfr0.GetNumWatchpoints();
|
||||
const auto num_ctx = dfr0.GetNumContextAwareBreakpoints();
|
||||
|
||||
if (ams::svc::HardwareBreakPointRegisterName_I0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_I15) {
|
||||
/* Check that the name is a valid instruction breakpoint. */
|
||||
R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) <= num_bp, svc::ResultNotSupported());
|
||||
|
||||
/* Configure flags/value. */
|
||||
if ((flags & 1) != 0) {
|
||||
/* We're enabling the breakpoint. Check that the flags are allowable. */
|
||||
R_UNLESS((flags & ForbiddenBreakPointFlagsMask) == 0, svc::ResultInvalidCombination());
|
||||
|
||||
/* Require that the breakpoint be linked or match context id. */
|
||||
R_UNLESS((flags & ((1ul << 21) | (1ul << 20))) != 0, svc::ResultInvalidCombination());
|
||||
|
||||
/* If the breakpoint matches context id, we need to get the context id. */
|
||||
if ((flags & (1ul << 21)) != 0) {
|
||||
/* Ensure that the breakpoint is context-aware. */
|
||||
R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) >= (num_bp - num_ctx), svc::ResultNotSupported());
|
||||
|
||||
/* Check that the breakpoint does not have the mismatch bit. */
|
||||
R_UNLESS((flags & (1ul << 22)) == 0, svc::ResultInvalidCombination());
|
||||
|
||||
/* Get the debug object from the current handle table. */
|
||||
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(static_cast<ams::svc::Handle>(value));
|
||||
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
||||
|
||||
/* Get the process from the debug object. */
|
||||
R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
|
||||
R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
|
||||
|
||||
/* Close the process when we're done. */
|
||||
ON_SCOPE_EXIT { debug->CloseProcess(); };
|
||||
|
||||
/* Get the proces. */
|
||||
KProcess * const process = debug->GetProcessUnsafe();
|
||||
|
||||
/* Set the value to be the context id. */
|
||||
value = process->GetId() & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* Set the breakpoint as non-secure EL0-only. */
|
||||
flags |= (1ul << 14) | (2ul << 1);
|
||||
} else {
|
||||
/* We're disabling the breakpoint. */
|
||||
flags = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
/* Set the breakpoint. */
|
||||
switch (name) {
|
||||
case ams::svc::HardwareBreakPointRegisterName_I0: MESOSPHERE_SET_HW_BREAK_POINT( 0, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I1: MESOSPHERE_SET_HW_BREAK_POINT( 1, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I2: MESOSPHERE_SET_HW_BREAK_POINT( 2, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I3: MESOSPHERE_SET_HW_BREAK_POINT( 3, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I4: MESOSPHERE_SET_HW_BREAK_POINT( 4, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I5: MESOSPHERE_SET_HW_BREAK_POINT( 5, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I6: MESOSPHERE_SET_HW_BREAK_POINT( 6, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I7: MESOSPHERE_SET_HW_BREAK_POINT( 7, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I8: MESOSPHERE_SET_HW_BREAK_POINT( 8, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I9: MESOSPHERE_SET_HW_BREAK_POINT( 9, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I10: MESOSPHERE_SET_HW_BREAK_POINT(10, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I11: MESOSPHERE_SET_HW_BREAK_POINT(11, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I12: MESOSPHERE_SET_HW_BREAK_POINT(12, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I13: MESOSPHERE_SET_HW_BREAK_POINT(13, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I14: MESOSPHERE_SET_HW_BREAK_POINT(14, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_I15: MESOSPHERE_SET_HW_BREAK_POINT(15, flags, value); break;
|
||||
default: break;
|
||||
}
|
||||
} else if (ams::svc::HardwareBreakPointRegisterName_D0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_D15) {
|
||||
/* Check that the name is a valid data breakpoint. */
|
||||
R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_D0) <= num_wp, svc::ResultNotSupported());
|
||||
|
||||
/* Configure flags/value. */
|
||||
if ((flags & 1) != 0) {
|
||||
/* We're enabling the watchpoint. Check that the flags are allowable. */
|
||||
R_UNLESS((flags & ForbiddenWatchPointFlagsMask) == 0, svc::ResultInvalidCombination());
|
||||
|
||||
/* Set the breakpoint as linked non-secure EL0-only. */
|
||||
flags |= (1ul << 20) | (1ul << 14) | (2ul << 1);
|
||||
} else {
|
||||
/* We're disabling the watchpoint. */
|
||||
flags = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
/* Set the watchpoint. */
|
||||
switch (name) {
|
||||
case ams::svc::HardwareBreakPointRegisterName_D0: MESOSPHERE_SET_HW_WATCH_POINT( 0, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D1: MESOSPHERE_SET_HW_WATCH_POINT( 1, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D2: MESOSPHERE_SET_HW_WATCH_POINT( 2, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D3: MESOSPHERE_SET_HW_WATCH_POINT( 3, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D4: MESOSPHERE_SET_HW_WATCH_POINT( 4, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D5: MESOSPHERE_SET_HW_WATCH_POINT( 5, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D6: MESOSPHERE_SET_HW_WATCH_POINT( 6, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D7: MESOSPHERE_SET_HW_WATCH_POINT( 7, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D8: MESOSPHERE_SET_HW_WATCH_POINT( 8, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D9: MESOSPHERE_SET_HW_WATCH_POINT( 9, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D10: MESOSPHERE_SET_HW_WATCH_POINT(10, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D11: MESOSPHERE_SET_HW_WATCH_POINT(11, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D12: MESOSPHERE_SET_HW_WATCH_POINT(12, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D13: MESOSPHERE_SET_HW_WATCH_POINT(13, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D14: MESOSPHERE_SET_HW_WATCH_POINT(14, flags, value); break;
|
||||
case ams::svc::HardwareBreakPointRegisterName_D15: MESOSPHERE_SET_HW_WATCH_POINT(15, flags, value); break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
/* Invalid name. */
|
||||
R_THROW(svc::ResultInvalidEnumValue());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
#undef MESOSPHERE_SET_HW_WATCH_POINT
|
||||
#undef MESOSPHERE_SET_HW_BREAK_POINT
|
||||
|
||||
void KDebug::PrintRegister(KThread *thread) {
|
||||
#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING)
|
||||
{
|
||||
/* Treat no thread as current thread. */
|
||||
if (thread == nullptr) {
|
||||
thread = GetCurrentThreadPointer();
|
||||
}
|
||||
|
||||
/* Get the exception context. */
|
||||
KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
|
||||
/* Get the owner process. */
|
||||
if (auto *process = thread->GetOwnerProcess(); process != nullptr) {
|
||||
/* Lock the owner process. */
|
||||
KScopedLightLock state_lk(process->GetStateLock());
|
||||
KScopedLightLock list_lk(process->GetListLock());
|
||||
|
||||
/* Suspend all the process's threads. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto end = process->GetThreadList().end();
|
||||
for (auto it = process->GetThreadList().begin(); it != end; ++it) {
|
||||
if (std::addressof(*it) != GetCurrentThreadPointer()) {
|
||||
it->RequestSuspend(KThread::SuspendType_Backtrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print the registers. */
|
||||
MESOSPHERE_RELEASE_LOG("Registers\n");
|
||||
if ((e_ctx->psr & 0x10) == 0) {
|
||||
/* 64-bit thread. */
|
||||
for (auto i = 0; i < 31; ++i) {
|
||||
MESOSPHERE_RELEASE_LOG(" X[%2d]: 0x%016lx\n", i, e_ctx->x[i]);
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG(" SP: 0x%016lx\n", e_ctx->sp);
|
||||
MESOSPHERE_RELEASE_LOG(" PC: 0x%016lx\n", e_ctx->pc - sizeof(u32));
|
||||
MESOSPHERE_RELEASE_LOG(" PSR: 0x%08x\n", e_ctx->psr);
|
||||
MESOSPHERE_RELEASE_LOG(" TPIDR_EL0: 0x%016lx\n", e_ctx->tpidr);
|
||||
} else {
|
||||
/* 32-bit thread. */
|
||||
for (auto i = 0; i < 13; ++i) {
|
||||
MESOSPHERE_RELEASE_LOG(" R[%2d]: 0x%08x\n", i, static_cast<u32>(e_ctx->x[i]));
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG(" SP: 0x%08x\n", static_cast<u32>(e_ctx->x[13]));
|
||||
MESOSPHERE_RELEASE_LOG(" LR: 0x%08x\n", static_cast<u32>(e_ctx->x[14]));
|
||||
MESOSPHERE_RELEASE_LOG(" PC: 0x%08x\n", static_cast<u32>(e_ctx->pc) - static_cast<u32>((e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32)));
|
||||
MESOSPHERE_RELEASE_LOG(" PSR: 0x%08x\n", e_ctx->psr);
|
||||
MESOSPHERE_RELEASE_LOG(" TPIDR: 0x%08x\n", static_cast<u32>(e_ctx->tpidr));
|
||||
}
|
||||
|
||||
/* Resume the threads that we suspended. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto end = process->GetThreadList().end();
|
||||
for (auto it = process->GetThreadList().begin(); it != end; ++it) {
|
||||
if (std::addressof(*it) != GetCurrentThreadPointer()) {
|
||||
it->Resume(KThread::SuspendType_Backtrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
MESOSPHERE_UNUSED(thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING)
|
||||
namespace {
|
||||
|
||||
bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr) {
|
||||
const KMemoryRegion *cached = nullptr;
|
||||
return KMemoryLayout::IsHeapPhysicalAddress(cached, phys_addr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ReadValue(T *out, KProcess *process, uintptr_t address) {
|
||||
KPhysicalAddress phys_addr;
|
||||
KMemoryInfo mem_info;
|
||||
ams::svc::PageInfo page_info;
|
||||
|
||||
if (!util::IsAligned(address, sizeof(T))) {
|
||||
return false;
|
||||
}
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), address))) {
|
||||
return false;
|
||||
}
|
||||
if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) {
|
||||
return false;
|
||||
}
|
||||
if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), address)) {
|
||||
return false;
|
||||
}
|
||||
if (!IsHeapPhysicalAddress(phys_addr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = *GetPointer<T>(process->GetPageTable().GetHeapVirtualAddress(phys_addr));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetModuleName(char *dst, size_t dst_size, KProcess *process, uintptr_t base_address) {
|
||||
/* Locate .rodata. */
|
||||
KMemoryInfo mem_info;
|
||||
ams::svc::PageInfo page_info;
|
||||
KMemoryState mem_state = KMemoryState_None;
|
||||
|
||||
while (true) {
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address))) {
|
||||
return false;
|
||||
}
|
||||
if (mem_state == KMemoryState_None) {
|
||||
mem_state = mem_info.GetState();
|
||||
if (mem_state != KMemoryState_Code && mem_state != KMemoryState_AliasCode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mem_info.GetState() != mem_state) {
|
||||
return false;
|
||||
}
|
||||
if (mem_info.GetPermission() == KMemoryPermission_UserRead) {
|
||||
break;
|
||||
}
|
||||
base_address = mem_info.GetEndAddress();
|
||||
}
|
||||
|
||||
/* Check that first value is 0. */
|
||||
u32 val;
|
||||
if (!ReadValue(std::addressof(val), process, base_address)) {
|
||||
return false;
|
||||
}
|
||||
if (val != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read the name length. */
|
||||
if (!ReadValue(std::addressof(val), process, base_address + sizeof(u32))) {
|
||||
return false;
|
||||
}
|
||||
if (!(0 < val && val < dst_size)) {
|
||||
return false;
|
||||
}
|
||||
const size_t name_len = val;
|
||||
|
||||
/* Read the name, one character at a time. */
|
||||
for (size_t i = 0; i < name_len; ++i) {
|
||||
if (!ReadValue(dst + i, process, base_address + 2 * sizeof(u32) + i)) {
|
||||
return false;
|
||||
}
|
||||
if (!(0 < dst[i] && dst[i] <= 0x7F)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* NULL-terminate. */
|
||||
dst[name_len] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintAddress(uintptr_t address) {
|
||||
MESOSPHERE_RELEASE_LOG(" %p\n", reinterpret_cast<void *>(address));
|
||||
}
|
||||
|
||||
void PrintAddressWithModuleName(uintptr_t address, bool has_module_name, const char *module_name, uintptr_t base_address) {
|
||||
if (has_module_name) {
|
||||
MESOSPHERE_RELEASE_LOG(" %p [%10s + %8lx]\n", reinterpret_cast<void *>(address), module_name, address - base_address);
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG(" %p [%10lx + %8lx]\n", reinterpret_cast<void *>(address), base_address, address - base_address);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintAddressWithSymbol(uintptr_t address, bool has_module_name, const char *module_name, uintptr_t base_address, const char *symbol_name, uintptr_t func_address) {
|
||||
if (has_module_name) {
|
||||
MESOSPHERE_RELEASE_LOG(" %p [%10s + %8lx] (%s + %lx)\n", reinterpret_cast<void *>(address), module_name, address - base_address, symbol_name, address - func_address);
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG(" %p [%10lx + %8lx] (%s + %lx)\n", reinterpret_cast<void *>(address), base_address, address - base_address, symbol_name, address - func_address);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintCodeAddress(KProcess *process, uintptr_t address, bool is_lr = true) {
|
||||
/* Prepare to parse + print the address. */
|
||||
uintptr_t test_address = is_lr ? address - sizeof(u32) : address;
|
||||
uintptr_t base_address = address;
|
||||
uintptr_t dyn_address = 0;
|
||||
uintptr_t sym_tab = 0;
|
||||
uintptr_t str_tab = 0;
|
||||
size_t num_sym = 0;
|
||||
|
||||
u64 temp_64;
|
||||
u32 temp_32;
|
||||
|
||||
/* Locate the start of .text. */
|
||||
KMemoryInfo mem_info;
|
||||
ams::svc::PageInfo page_info;
|
||||
KMemoryState mem_state = KMemoryState_None;
|
||||
while (true) {
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address))) {
|
||||
return PrintAddress(address);
|
||||
}
|
||||
if (mem_state == KMemoryState_None) {
|
||||
mem_state = mem_info.GetState();
|
||||
if (mem_state != KMemoryState_Code && mem_state != KMemoryState_AliasCode) {
|
||||
return PrintAddress(address);
|
||||
}
|
||||
} else if (mem_info.GetState() != mem_state) {
|
||||
return PrintAddress(address);
|
||||
}
|
||||
if (mem_info.GetPermission() != KMemoryPermission_UserReadExecute) {
|
||||
return PrintAddress(address);
|
||||
}
|
||||
base_address = mem_info.GetAddress();
|
||||
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address - 1))) {
|
||||
return PrintAddress(address);
|
||||
}
|
||||
if (mem_info.GetState() != mem_state) {
|
||||
break;
|
||||
}
|
||||
if (mem_info.GetPermission() != KMemoryPermission_UserReadExecute) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the module name. */
|
||||
char module_name[0x20];
|
||||
const bool has_module_name = GetModuleName(module_name, sizeof(module_name), process, base_address);
|
||||
|
||||
/* If the process is 32-bit, just print the module. */
|
||||
if (!process->Is64Bit()) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
/* Locate .dyn using rocrt::ModuleHeader. */
|
||||
{
|
||||
/* Determine the ModuleHeader offset. */
|
||||
u32 mod_offset;
|
||||
if (!ReadValue(std::addressof(mod_offset), process, base_address + sizeof(u32))) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
/* Read the signature. */
|
||||
constexpr u32 SignatureFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, signature);
|
||||
if (!ReadValue(std::addressof(temp_32), process, base_address + mod_offset + SignatureFieldOffset)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
/* Check that the module signature is expected. */
|
||||
if (temp_32 != rocrt::ModuleHeaderVersion) { /* MOD0 */
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
/* Determine the dynamic offset. */
|
||||
constexpr u32 DynamicFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, dynamic_offset);
|
||||
if (!ReadValue(std::addressof(temp_32), process, base_address + mod_offset + DynamicFieldOffset)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
dyn_address = base_address + mod_offset + temp_32;
|
||||
}
|
||||
|
||||
/* Locate tables inside .dyn. */
|
||||
for (size_t ofs = 0; /* ... */; ofs += 0x10) {
|
||||
/* Read the DynamicTag. */
|
||||
if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
if (temp_64 == 0) {
|
||||
/* We're done parsing .dyn. */
|
||||
break;
|
||||
} else if (temp_64 == 4) {
|
||||
/* We found DT_HASH */
|
||||
if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
/* Read nchain, to get the number of symbols. */
|
||||
if (!ReadValue(std::addressof(temp_32), process, base_address + temp_64 + sizeof(u32))) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
num_sym = temp_32;
|
||||
} else if (temp_64 == 5) {
|
||||
/* We found DT_STRTAB */
|
||||
if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
str_tab = base_address + temp_64;
|
||||
} else if (temp_64 == 6) {
|
||||
/* We found DT_SYMTAB */
|
||||
if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
sym_tab = base_address + temp_64;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that we found all the tables. */
|
||||
if (!(sym_tab != 0 && str_tab != 0 && num_sym != 0)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
/* Try to locate an appropriate symbol. */
|
||||
for (size_t i = 0; i < num_sym; ++i) {
|
||||
/* Read the symbol from userspace. */
|
||||
struct {
|
||||
u32 st_name;
|
||||
u8 st_info;
|
||||
u8 st_other;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
} sym;
|
||||
{
|
||||
u64 x[sizeof(sym) / sizeof(u64)];
|
||||
for (size_t j = 0; j < util::size(x); ++j) {
|
||||
if (!ReadValue(x + j, process, sym_tab + sizeof(sym) * i + sizeof(u64) * j)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
}
|
||||
std::memcpy(std::addressof(sym), x, sizeof(sym));
|
||||
}
|
||||
|
||||
/* Check the symbol is valid/STT_FUNC. */
|
||||
if (sym.st_shndx == 0 || ((sym.st_shndx & 0xFF00) == 0xFF00)) {
|
||||
continue;
|
||||
}
|
||||
if ((sym.st_info & 0xF) != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check the address. */
|
||||
const uintptr_t func_start = base_address + sym.st_value;
|
||||
if (func_start <= test_address && test_address < func_start + sym.st_size) {
|
||||
/* Read the symbol name. */
|
||||
const uintptr_t sym_address = str_tab + sym.st_name;
|
||||
char sym_name[0x80];
|
||||
sym_name[util::size(sym_name) - 1] = 0;
|
||||
for (size_t j = 0; j < util::size(sym_name) - 1; ++j) {
|
||||
if (!ReadValue(sym_name + j, process, sym_address + j)) {
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
if (sym_name[j] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print the symbol. */
|
||||
return PrintAddressWithSymbol(address, has_module_name, module_name, base_address, sym_name, func_start);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fall back to printing the module. */
|
||||
return PrintAddressWithModuleName(address, has_module_name, module_name, base_address);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void KDebug::PrintBacktrace(KThread *thread) {
|
||||
#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING)
|
||||
{
|
||||
/* Treat no thread as current thread. */
|
||||
if (thread == nullptr) {
|
||||
thread = GetCurrentThreadPointer();
|
||||
}
|
||||
|
||||
/* Get the exception context. */
|
||||
KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
|
||||
/* Get the owner process. */
|
||||
if (auto *process = thread->GetOwnerProcess(); process != nullptr) {
|
||||
/* Lock the owner process. */
|
||||
KScopedLightLock state_lk(process->GetStateLock());
|
||||
KScopedLightLock list_lk(process->GetListLock());
|
||||
|
||||
/* Suspend all the process's threads. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto end = process->GetThreadList().end();
|
||||
for (auto it = process->GetThreadList().begin(); it != end; ++it) {
|
||||
if (std::addressof(*it) != GetCurrentThreadPointer()) {
|
||||
it->RequestSuspend(KThread::SuspendType_Backtrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print the backtrace. */
|
||||
MESOSPHERE_RELEASE_LOG("User Backtrace\n");
|
||||
if ((e_ctx->psr & 0x10) == 0) {
|
||||
/* 64-bit thread. */
|
||||
PrintCodeAddress(process, e_ctx->pc, false);
|
||||
PrintCodeAddress(process, e_ctx->x[30]);
|
||||
|
||||
/* Walk the stack frames. */
|
||||
uintptr_t fp = static_cast<uintptr_t>(e_ctx->x[29]);
|
||||
for (auto i = 0; i < 0x20 && fp != 0 && util::IsAligned(fp, 0x10); ++i) {
|
||||
/* Read the next frame. */
|
||||
struct {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
} stack_frame;
|
||||
{
|
||||
KMemoryInfo mem_info;
|
||||
ams::svc::PageInfo page_info;
|
||||
KPhysicalAddress phys_addr;
|
||||
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), fp))) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) {
|
||||
break;
|
||||
}
|
||||
if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), fp)) {
|
||||
break;
|
||||
}
|
||||
if (!IsHeapPhysicalAddress(phys_addr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
u64 *frame_ptr = GetPointer<u64>(process->GetPageTable().GetHeapVirtualAddress(phys_addr));
|
||||
stack_frame.fp = frame_ptr[0];
|
||||
stack_frame.lr = frame_ptr[1];
|
||||
}
|
||||
|
||||
/* Print and advance. */
|
||||
PrintCodeAddress(process, stack_frame.lr);
|
||||
fp = stack_frame.fp;
|
||||
}
|
||||
} else {
|
||||
/* 32-bit thread. */
|
||||
PrintCodeAddress(process, e_ctx->pc, false);
|
||||
PrintCodeAddress(process, e_ctx->x[14]);
|
||||
|
||||
/* Walk the stack frames. */
|
||||
uintptr_t fp = static_cast<uintptr_t>(e_ctx->x[11]);
|
||||
for (auto i = 0; i < 0x20 && fp != 0 && util::IsAligned(fp, 4); ++i) {
|
||||
/* Read the next frame. */
|
||||
struct {
|
||||
u32 fp;
|
||||
u32 lr;
|
||||
} stack_frame;
|
||||
{
|
||||
KMemoryInfo mem_info;
|
||||
ams::svc::PageInfo page_info;
|
||||
KPhysicalAddress phys_addr;
|
||||
|
||||
/* Read FP */
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), fp))) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) {
|
||||
break;
|
||||
}
|
||||
if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), fp)) {
|
||||
break;
|
||||
}
|
||||
if (!IsHeapPhysicalAddress(phys_addr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
stack_frame.fp = *GetPointer<u32>(process->GetPageTable().GetHeapVirtualAddress(phys_addr));
|
||||
|
||||
/* Read LR. */
|
||||
uintptr_t lr_ptr = (e_ctx->x[13] <= stack_frame.fp && stack_frame.fp < e_ctx->x[13] + PageSize) ? fp + 4 : fp - 4;
|
||||
if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), lr_ptr))) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) {
|
||||
break;
|
||||
}
|
||||
if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) {
|
||||
break;
|
||||
}
|
||||
if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), lr_ptr)) {
|
||||
break;
|
||||
}
|
||||
if (!IsHeapPhysicalAddress(phys_addr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
stack_frame.lr = *GetPointer<u32>(process->GetPageTable().GetHeapVirtualAddress(phys_addr));
|
||||
}
|
||||
|
||||
/* Print and advance. */
|
||||
PrintCodeAddress(process, stack_frame.lr);
|
||||
fp = stack_frame.fp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resume the threads that we suspended. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto end = process->GetThreadList().end();
|
||||
for (auto it = process->GetThreadList().begin(); it != end; ++it) {
|
||||
if (std::addressof(*it) != GetCurrentThreadPointer()) {
|
||||
it->Resume(KThread::SuspendType_Backtrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
MESOSPHERE_UNUSED(thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
void KHardwareTimer::Initialize() {
|
||||
/* Setup the global timer for the core. */
|
||||
InitializeGlobalTimer();
|
||||
|
||||
/* Set maximum time. */
|
||||
m_maximum_time = static_cast<s64>(std::min<u64>(std::numeric_limits<s64>::max(), cpu::CounterTimerPhysicalTimerCompareValueRegisterAccessor().GetCompareValue()));
|
||||
|
||||
/* Bind the interrupt task for this core. */
|
||||
Kernel::GetInterruptManager().BindHandler(this, KInterruptName_NonSecurePhysicalTimer, GetCurrentCoreId(), KInterruptController::PriorityLevel_Timer, true, true);
|
||||
}
|
||||
|
||||
void KHardwareTimer::Finalize() {
|
||||
/* Stop the hardware timer. */
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
void KHardwareTimer::DoTask() {
|
||||
/* Handle the interrupt. */
|
||||
{
|
||||
KScopedSchedulerLock slk;
|
||||
KScopedSpinLock lk(this->GetLock());
|
||||
|
||||
/* Disable the timer interrupt while we handle this. */
|
||||
DisableInterrupt();
|
||||
if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); 0 < next_time && next_time <= m_maximum_time) {
|
||||
/* We have a next time, so we should set the time to interrupt and turn the interrupt on. */
|
||||
SetCompareValue(next_time);
|
||||
EnableInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the timer interrupt. */
|
||||
Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer, GetCurrentCoreId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
/* Include the common implementation. */
|
||||
#include "../arm/kern_generic_interrupt_controller.inc"
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
void KInterruptManager::Initialize(s32 core_id) {
|
||||
m_interrupt_controller.Initialize(core_id);
|
||||
}
|
||||
|
||||
void KInterruptManager::Finalize(s32 core_id) {
|
||||
m_interrupt_controller.Finalize(core_id);
|
||||
}
|
||||
|
||||
void KInterruptManager::Save(s32 core_id) {
|
||||
/* Verify core id. */
|
||||
MESOSPHERE_ASSERT(core_id == GetCurrentCoreId());
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* If on core 0, save the global interrupts. */
|
||||
if (core_id == 0) {
|
||||
MESOSPHERE_ABORT_UNLESS(!m_global_state_saved);
|
||||
m_interrupt_controller.SaveGlobal(std::addressof(m_global_state));
|
||||
m_global_state_saved = true;
|
||||
}
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Save all local interrupts. */
|
||||
MESOSPHERE_ABORT_UNLESS(!m_local_state_saved[core_id]);
|
||||
m_interrupt_controller.SaveCoreLocal(std::addressof(m_local_states[core_id]));
|
||||
m_local_state_saved[core_id] = true;
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Finalize all cores other than core 0. */
|
||||
if (core_id != 0) {
|
||||
this->Finalize(core_id);
|
||||
}
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Finalize core 0. */
|
||||
if (core_id == 0) {
|
||||
this->Finalize(core_id);
|
||||
}
|
||||
}
|
||||
|
||||
void KInterruptManager::Restore(s32 core_id) {
|
||||
/* Verify core id. */
|
||||
MESOSPHERE_ASSERT(core_id == GetCurrentCoreId());
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Initialize core 0. */
|
||||
if (core_id == 0) {
|
||||
this->Initialize(core_id);
|
||||
}
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Initialize all cores other than core 0. */
|
||||
if (core_id != 0) {
|
||||
this->Initialize(core_id);
|
||||
}
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Restore all local interrupts. */
|
||||
MESOSPHERE_ASSERT(m_local_state_saved[core_id]);
|
||||
m_interrupt_controller.RestoreCoreLocal(std::addressof(m_local_states[core_id]));
|
||||
m_local_state_saved[core_id] = false;
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* If on core 0, restore the global interrupts. */
|
||||
if (core_id == 0) {
|
||||
MESOSPHERE_ASSERT(m_global_state_saved);
|
||||
m_interrupt_controller.RestoreGlobal(std::addressof(m_global_state));
|
||||
m_global_state_saved = false;
|
||||
}
|
||||
|
||||
/* Ensure all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
}
|
||||
|
||||
bool KInterruptManager::OnHandleInterrupt() {
|
||||
/* Get the interrupt id. */
|
||||
const u32 raw_irq = m_interrupt_controller.GetIrq();
|
||||
const s32 irq = KInterruptController::ConvertRawIrq(raw_irq);
|
||||
|
||||
/* Trace the interrupt. */
|
||||
MESOSPHERE_KTRACE_INTERRUPT(irq);
|
||||
|
||||
/* If the IRQ is spurious, we don't need to reschedule. */
|
||||
if (irq < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KInterruptTask *task = nullptr;
|
||||
if (KInterruptController::IsLocal(irq)) {
|
||||
/* Get local interrupt entry. */
|
||||
auto &entry = GetLocalInterruptEntry(irq);
|
||||
if (entry.handler != nullptr) {
|
||||
/* Set manual clear needed if relevant. */
|
||||
if (entry.manually_cleared) {
|
||||
m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low);
|
||||
entry.needs_clear = true;
|
||||
}
|
||||
|
||||
/* Set the handler. */
|
||||
task = entry.handler->OnInterrupt(irq);
|
||||
} else {
|
||||
MESOSPHERE_LOG("Core%d: Unhandled local interrupt %d\n", GetCurrentCoreId(), irq);
|
||||
}
|
||||
} else if (KInterruptController::IsGlobal(irq)) {
|
||||
KScopedSpinLock lk(this->GetGlobalInterruptLock());
|
||||
|
||||
/* Get global interrupt entry. */
|
||||
auto &entry = GetGlobalInterruptEntry(irq);
|
||||
if (entry.handler != nullptr) {
|
||||
/* Set manual clear needed if relevant. */
|
||||
if (entry.manually_cleared) {
|
||||
m_interrupt_controller.Disable(irq);
|
||||
entry.needs_clear = true;
|
||||
}
|
||||
|
||||
/* Set the handler. */
|
||||
task = entry.handler->OnInterrupt(irq);
|
||||
} else {
|
||||
MESOSPHERE_LOG("Core%d: Unhandled global interrupt %d\n", GetCurrentCoreId(), irq);
|
||||
}
|
||||
} else {
|
||||
MESOSPHERE_LOG("Invalid interrupt %d\n", irq);
|
||||
}
|
||||
|
||||
/* Acknowledge the interrupt. */
|
||||
m_interrupt_controller.EndOfInterrupt(raw_irq);
|
||||
|
||||
/* If we found no task, then we don't need to reschedule. */
|
||||
if (task == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the task isn't the dummy task, we should add it to the queue. */
|
||||
if (task != GetDummyInterruptTask()) {
|
||||
Kernel::GetInterruptTaskManager().EnqueueTask(task);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KInterruptManager::HandleInterrupt(bool user_mode) {
|
||||
/* On interrupt, call OnHandleInterrupt() to determine if we need rescheduling and handle. */
|
||||
const bool needs_scheduling = Kernel::GetInterruptManager().OnHandleInterrupt();
|
||||
|
||||
/* If we need scheduling, */
|
||||
if (needs_scheduling) {
|
||||
if (user_mode) {
|
||||
/* If the interrupt occurred in the middle of a userland cache maintenance operation, ensure memory consistency before rescheduling. */
|
||||
if (GetCurrentThread().IsInUserCacheMaintenanceOperation()) {
|
||||
cpu::DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
/* If the user disable count is set, we may need to pin the current thread. */
|
||||
if (GetCurrentThread().GetUserDisableCount() != 0 && GetCurrentProcess().GetPinnedThread(GetCurrentCoreId()) == nullptr) {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Pin the current thread. */
|
||||
GetCurrentProcess().PinCurrentThread();
|
||||
|
||||
/* Set the interrupt flag for the thread. */
|
||||
GetCurrentThread().SetInterruptFlag();
|
||||
|
||||
/* Request interrupt scheduling. */
|
||||
Kernel::GetScheduler().RequestScheduleOnInterrupt();
|
||||
} else {
|
||||
/* Request interrupt scheduling. */
|
||||
Kernel::GetScheduler().RequestScheduleOnInterrupt();
|
||||
}
|
||||
} else {
|
||||
/* If the interrupt occurred in the middle of a cache maintenance operation, ensure memory consistency before rescheduling. */
|
||||
if (GetCurrentThread().IsInCacheMaintenanceOperation()) {
|
||||
cpu::DataSynchronizationBarrier();
|
||||
} else if (GetCurrentThread().IsInTlbMaintenanceOperation()) {
|
||||
/* Otherwise, if we're in the middle of a tlb maintenance operation, ensure inner shareable memory consistency before rescheduling. */
|
||||
cpu::DataSynchronizationBarrierInnerShareable();
|
||||
}
|
||||
|
||||
/* Request interrupt scheduling. */
|
||||
Kernel::GetScheduler().RequestScheduleOnInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/* If user mode, check if the thread needs termination. */
|
||||
/* If it does, we can take advantage of this to terminate it. */
|
||||
if (user_mode) {
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
if (cur_thread->IsTerminationRequested()) {
|
||||
EnableInterrupts();
|
||||
cur_thread->Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result KInterruptManager::BindHandler(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level) {
|
||||
MESOSPHERE_UNUSED(core_id);
|
||||
|
||||
R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange());
|
||||
|
||||
if (KInterruptController::IsGlobal(irq)) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(this->GetGlobalInterruptLock());
|
||||
R_RETURN(this->BindGlobal(handler, irq, core_id, priority, manual_clear, level));
|
||||
} else {
|
||||
MESOSPHERE_ASSERT(core_id == GetCurrentCoreId());
|
||||
|
||||
KScopedInterruptDisable di;
|
||||
R_RETURN(this->BindLocal(handler, irq, priority, manual_clear));
|
||||
}
|
||||
}
|
||||
|
||||
Result KInterruptManager::UnbindHandler(s32 irq, s32 core_id) {
|
||||
MESOSPHERE_UNUSED(core_id);
|
||||
|
||||
R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange());
|
||||
|
||||
|
||||
if (KInterruptController::IsGlobal(irq)) {
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
KScopedSpinLock lk(this->GetGlobalInterruptLock());
|
||||
R_RETURN(this->UnbindGlobal(irq));
|
||||
} else {
|
||||
MESOSPHERE_ASSERT(core_id == GetCurrentCoreId());
|
||||
|
||||
KScopedInterruptDisable di;
|
||||
R_RETURN(this->UnbindLocal(irq));
|
||||
}
|
||||
}
|
||||
|
||||
Result KInterruptManager::ClearInterrupt(s32 irq, s32 core_id) {
|
||||
MESOSPHERE_UNUSED(core_id);
|
||||
|
||||
R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange());
|
||||
|
||||
|
||||
if (KInterruptController::IsGlobal(irq)) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(this->GetGlobalInterruptLock());
|
||||
R_RETURN(this->ClearGlobal(irq));
|
||||
} else {
|
||||
MESOSPHERE_ASSERT(core_id == GetCurrentCoreId());
|
||||
|
||||
KScopedInterruptDisable di;
|
||||
R_RETURN(this->ClearLocal(irq));
|
||||
}
|
||||
}
|
||||
|
||||
Result KInterruptManager::BindGlobal(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level) {
|
||||
/* Ensure the priority level is valid. */
|
||||
R_UNLESS(KInterruptController::PriorityLevel_High <= priority, svc::ResultOutOfRange());
|
||||
R_UNLESS(priority <= KInterruptController::PriorityLevel_Low, svc::ResultOutOfRange());
|
||||
|
||||
/* Ensure we aren't already bound. */
|
||||
auto &entry = GetGlobalInterruptEntry(irq);
|
||||
R_UNLESS(entry.handler == nullptr, svc::ResultBusy());
|
||||
|
||||
/* Set entry fields. */
|
||||
entry.needs_clear = false;
|
||||
entry.manually_cleared = manual_clear;
|
||||
entry.handler = handler;
|
||||
|
||||
/* Configure the interrupt as level or edge. */
|
||||
if (level) {
|
||||
m_interrupt_controller.SetLevel(irq);
|
||||
} else {
|
||||
m_interrupt_controller.SetEdge(irq);
|
||||
}
|
||||
|
||||
/* Configure the interrupt. */
|
||||
m_interrupt_controller.Clear(irq);
|
||||
m_interrupt_controller.SetTarget(irq, core_id);
|
||||
m_interrupt_controller.SetPriorityLevel(irq, priority);
|
||||
m_interrupt_controller.Enable(irq);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KInterruptManager::BindLocal(KInterruptHandler *handler, s32 irq, s32 priority, bool manual_clear) {
|
||||
/* Ensure the priority level is valid. */
|
||||
R_UNLESS(KInterruptController::PriorityLevel_High <= priority, svc::ResultOutOfRange());
|
||||
R_UNLESS(priority <= KInterruptController::PriorityLevel_Low, svc::ResultOutOfRange());
|
||||
|
||||
/* Ensure we aren't already bound. */
|
||||
auto &entry = this->GetLocalInterruptEntry(irq);
|
||||
R_UNLESS(entry.handler == nullptr, svc::ResultBusy());
|
||||
|
||||
/* Set entry fields. */
|
||||
entry.needs_clear = false;
|
||||
entry.manually_cleared = manual_clear;
|
||||
entry.handler = handler;
|
||||
entry.priority = static_cast<u8>(priority);
|
||||
|
||||
/* Configure the interrupt. */
|
||||
m_interrupt_controller.Clear(irq);
|
||||
m_interrupt_controller.SetPriorityLevel(irq, priority);
|
||||
m_interrupt_controller.Enable(irq);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KInterruptManager::UnbindGlobal(s32 irq) {
|
||||
for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) {
|
||||
m_interrupt_controller.ClearTarget(irq, static_cast<s32>(core_id));
|
||||
}
|
||||
m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low);
|
||||
m_interrupt_controller.Disable(irq);
|
||||
|
||||
GetGlobalInterruptEntry(irq).handler = nullptr;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KInterruptManager::UnbindLocal(s32 irq) {
|
||||
auto &entry = this->GetLocalInterruptEntry(irq);
|
||||
R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState());
|
||||
|
||||
m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low);
|
||||
m_interrupt_controller.Disable(irq);
|
||||
|
||||
entry.handler = nullptr;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KInterruptManager::ClearGlobal(s32 irq) {
|
||||
/* We can't clear an entry with no handler. */
|
||||
auto &entry = GetGlobalInterruptEntry(irq);
|
||||
R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState());
|
||||
|
||||
/* If auto-cleared, we can succeed immediately. */
|
||||
R_SUCCEED_IF(!entry.manually_cleared);
|
||||
R_SUCCEED_IF(!entry.needs_clear);
|
||||
|
||||
/* Clear and enable. */
|
||||
entry.needs_clear = false;
|
||||
m_interrupt_controller.Enable(irq);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KInterruptManager::ClearLocal(s32 irq) {
|
||||
/* We can't clear an entry with no handler. */
|
||||
auto &entry = this->GetLocalInterruptEntry(irq);
|
||||
R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState());
|
||||
|
||||
/* If auto-cleared, we can succeed immediately. */
|
||||
R_SUCCEED_IF(!entry.manually_cleared);
|
||||
R_SUCCEED_IF(!entry.needs_clear);
|
||||
|
||||
/* Clear and set priority. */
|
||||
entry.needs_clear = false;
|
||||
m_interrupt_controller.SetPriorityLevel(irq, entry.priority);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
void KPageTableImpl::InitializeForKernel(void *tb, KVirtualAddress start, KVirtualAddress end) {
|
||||
m_table = static_cast<L1PageTableEntry *>(tb);
|
||||
m_is_kernel = true;
|
||||
m_num_entries = util::AlignUp(end - start, L1BlockSize) / L1BlockSize;
|
||||
|
||||
/* Page table entries created by KInitialPageTable need to be iterated and modified to ensure KPageTable invariants. */
|
||||
PageTableEntry *level_entries[EntryLevel_Count] = { nullptr, nullptr, m_table };
|
||||
u32 level = EntryLevel_L1;
|
||||
while (level != EntryLevel_L1 || (level_entries[EntryLevel_L1] - static_cast<PageTableEntry *>(m_table)) < m_num_entries) {
|
||||
/* Get the pte; it must never have the validity-extension flag set. */
|
||||
auto *pte = level_entries[level];
|
||||
MESOSPHERE_ASSERT((pte->GetSoftwareReservedBits() & PageTableEntry::SoftwareReservedBit_Valid) == 0);
|
||||
|
||||
/* While we're a table, recurse, fixing up the reference counts. */
|
||||
while (level > EntryLevel_L3 && pte->IsMappedTable()) {
|
||||
/* Count how many references are in the table. */
|
||||
auto *table = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(pte->GetTable()));
|
||||
|
||||
size_t ref_count = 0;
|
||||
for (size_t i = 0; i < BlocksPerTable; ++i) {
|
||||
if (table[i].IsMapped()) {
|
||||
++ref_count;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the reference count for our new page, adding one additional uncloseable reference; kernel pages must never be unreferenced. */
|
||||
pte->SetTableReferenceCount(ref_count + 1).SetValid();
|
||||
|
||||
/* Iterate downwards. */
|
||||
level -= 1;
|
||||
level_entries[level] = table;
|
||||
pte = level_entries[level];
|
||||
|
||||
/* Check that the entry isn't unexpected. */
|
||||
MESOSPHERE_ASSERT((pte->GetSoftwareReservedBits() & PageTableEntry::SoftwareReservedBit_Valid) == 0);
|
||||
}
|
||||
|
||||
/* We're dealing with some block. If it's mapped, set it valid. */
|
||||
if (pte->IsMapped()) {
|
||||
pte->SetValid();
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
while (true) {
|
||||
/* Advance to the next entry at the current level. */
|
||||
if (!util::IsAligned(reinterpret_cast<uintptr_t>(++level_entries[level]), PageSize)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we're at the end of a level, advance upwards. */
|
||||
level_entries[level++] = nullptr;
|
||||
|
||||
if (level > EntryLevel_L1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KPageTableImpl::InitializeForProcess(void *tb, KVirtualAddress start, KVirtualAddress end) {
|
||||
m_table = static_cast<L1PageTableEntry *>(tb);
|
||||
m_is_kernel = false;
|
||||
m_num_entries = util::AlignUp(end - start, L1BlockSize) / L1BlockSize;
|
||||
}
|
||||
|
||||
L1PageTableEntry *KPageTableImpl::Finalize() {
|
||||
return m_table;
|
||||
}
|
||||
|
||||
bool KPageTableImpl::BeginTraversal(TraversalEntry *out_entry, TraversalContext *out_context, KProcessAddress address) const {
|
||||
/* Setup invalid defaults. */
|
||||
*out_entry = {};
|
||||
*out_context = {};
|
||||
|
||||
/* Validate that we can read the actual entry. */
|
||||
const size_t l0_index = GetL0Index(address);
|
||||
const size_t l1_index = GetL1Index(address);
|
||||
if (m_is_kernel) {
|
||||
/* Kernel entries must be accessed via TTBR1. */
|
||||
if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* User entries must be accessed with TTBR0. */
|
||||
if ((l0_index != 0) || l1_index >= m_num_entries) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the L1 entry, and check if it's a table. */
|
||||
out_context->level_entries[EntryLevel_L1] = this->GetL1Entry(address);
|
||||
if (out_context->level_entries[EntryLevel_L1]->IsMappedTable()) {
|
||||
/* Get the L2 entry, and check if it's a table. */
|
||||
out_context->level_entries[EntryLevel_L2] = this->GetL2EntryFromTable(GetPageTableVirtualAddress(out_context->level_entries[EntryLevel_L1]->GetTable()), address);
|
||||
if (out_context->level_entries[EntryLevel_L2]->IsMappedTable()) {
|
||||
/* Get the L3 entry. */
|
||||
out_context->level_entries[EntryLevel_L3] = this->GetL3EntryFromTable(GetPageTableVirtualAddress(out_context->level_entries[EntryLevel_L2]->GetTable()), address);
|
||||
|
||||
/* It's either a page or not. */
|
||||
out_context->level = EntryLevel_L3;
|
||||
} else {
|
||||
/* Not a L2 table, so possibly an L2 block. */
|
||||
out_context->level = EntryLevel_L2;
|
||||
}
|
||||
} else {
|
||||
/* Not a L1 table, so possibly an L1 block. */
|
||||
out_context->level = EntryLevel_L1;
|
||||
}
|
||||
|
||||
/* Determine other fields. */
|
||||
const auto *pte = out_context->level_entries[out_context->level];
|
||||
|
||||
out_context->is_contiguous = pte->IsContiguous();
|
||||
|
||||
out_entry->sw_reserved_bits = pte->GetSoftwareReservedBits();
|
||||
out_entry->attr = 0;
|
||||
out_entry->phys_addr = this->GetBlock(pte, out_context->level) + this->GetOffset(address, out_context->level);
|
||||
out_entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * out_context->level + 4 * out_context->is_contiguous);
|
||||
|
||||
return out_context->level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock();
|
||||
}
|
||||
|
||||
bool KPageTableImpl::ContinueTraversal(TraversalEntry *out_entry, TraversalContext *context) const {
|
||||
/* Advance entry. */
|
||||
auto *cur_pte = context->level_entries[context->level];
|
||||
auto *next_pte = reinterpret_cast<PageTableEntry *>(context->is_contiguous ? util::AlignDown(reinterpret_cast<uintptr_t>(cur_pte), BlocksPerContiguousBlock * sizeof(PageTableEntry)) + BlocksPerContiguousBlock * sizeof(PageTableEntry) : reinterpret_cast<uintptr_t>(cur_pte) + sizeof(PageTableEntry));
|
||||
|
||||
/* Set the pte. */
|
||||
context->level_entries[context->level] = next_pte;
|
||||
|
||||
/* Advance appropriately. */
|
||||
while (context->level < EntryLevel_L1 && util::IsAligned(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), PageSize)) {
|
||||
/* Advance the above table by one entry. */
|
||||
context->level_entries[context->level + 1]++;
|
||||
context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) + 1);
|
||||
}
|
||||
|
||||
/* Check if we've hit the end of the L1 table. */
|
||||
if (context->level == EntryLevel_L1) {
|
||||
if (context->level_entries[EntryLevel_L1] - static_cast<const PageTableEntry *>(m_table) >= m_num_entries) {
|
||||
*context = {};
|
||||
*out_entry = {};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* We may have advanced to a new table, and if we have we should descend. */
|
||||
while (context->level > EntryLevel_L3 && context->level_entries[context->level]->IsMappedTable()) {
|
||||
context->level_entries[context->level - 1] = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(context->level_entries[context->level]->GetTable()));
|
||||
context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) - 1);
|
||||
}
|
||||
|
||||
const auto *pte = context->level_entries[context->level];
|
||||
|
||||
context->is_contiguous = pte->IsContiguous();
|
||||
|
||||
out_entry->sw_reserved_bits = pte->GetSoftwareReservedBits();
|
||||
out_entry->attr = 0;
|
||||
out_entry->phys_addr = this->GetBlock(pte, context->level);
|
||||
out_entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * context->level + 4 * context->is_contiguous);
|
||||
return context->level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock();
|
||||
}
|
||||
|
||||
bool KPageTableImpl::GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress address) const {
|
||||
/* Validate that we can read the actual entry. */
|
||||
const size_t l0_index = GetL0Index(address);
|
||||
const size_t l1_index = GetL1Index(address);
|
||||
if (m_is_kernel) {
|
||||
/* Kernel entries must be accessed via TTBR1. */
|
||||
if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* User entries must be accessed with TTBR0. */
|
||||
if ((l0_index != 0) || l1_index >= m_num_entries) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the L1 entry, and check if it's a table. */
|
||||
const PageTableEntry *pte = this->GetL1Entry(address);
|
||||
EntryLevel level = EntryLevel_L1;
|
||||
if (pte->IsMappedTable()) {
|
||||
/* Get the L2 entry, and check if it's a table. */
|
||||
pte = this->GetL2EntryFromTable(GetPageTableVirtualAddress(pte->GetTable()), address);
|
||||
level = EntryLevel_L2;
|
||||
if (pte->IsMappedTable()) {
|
||||
pte = this->GetL3EntryFromTable(GetPageTableVirtualAddress(pte->GetTable()), address);
|
||||
level = EntryLevel_L3;
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_block = level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock();
|
||||
if (is_block) {
|
||||
*out = this->GetBlock(pte, level) + this->GetOffset(address, level);
|
||||
} else {
|
||||
*out = Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
return is_block;
|
||||
}
|
||||
|
||||
bool KPageTableImpl::MergePages(KVirtualAddress *out, TraversalContext *context, EntryUpdatedCallback on_entry_updated, const void *pt) {
|
||||
/* We want to upgrade the pages by one step. */
|
||||
if (context->is_contiguous) {
|
||||
/* We can't merge an L1 table. */
|
||||
if (context->level == EntryLevel_L1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We want to upgrade a contiguous mapping in a table to a block. */
|
||||
PageTableEntry *pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerTable * sizeof(PageTableEntry)));
|
||||
const KPhysicalAddress phys_addr = util::AlignDown(GetBlock(pte, context->level), GetBlockSize(static_cast<EntryLevel>(context->level + 1), false));
|
||||
|
||||
/* First, check that all entries are valid for us to merge. */
|
||||
const u64 entry_template = pte->GetEntryTemplateForMerge();
|
||||
for (size_t i = 0; i < BlocksPerTable; ++i) {
|
||||
if (!pte[i].IsForMerge(entry_template | GetInteger(phys_addr + (i << (PageBits + LevelBits * context->level))) | PageTableEntry::ContigType_Contiguous | pte->GetTestTableMask())) {
|
||||
return false;
|
||||
}
|
||||
if (i > 0 && pte[i].IsHeadOrHeadAndBodyMergeDisabled()) {
|
||||
return false;
|
||||
}
|
||||
if (i < BlocksPerTable - 1 && pte[i].IsTailMergeDisabled()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* The entries are valid for us to merge, so merge them. */
|
||||
const auto *head_pte = pte;
|
||||
const auto *tail_pte = pte + BlocksPerTable - 1;
|
||||
const auto sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(head_pte->IsHeadMergeDisabled(), head_pte->IsHeadAndBodyMergeDisabled(), tail_pte->IsTailMergeDisabled());
|
||||
|
||||
*context->level_entries[context->level + 1] = PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, PageTableEntry(entry_template), sw_reserved_bits, false, false);
|
||||
on_entry_updated(pt);
|
||||
|
||||
/* Update our context. */
|
||||
context->is_contiguous = false;
|
||||
context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) + 1);
|
||||
|
||||
/* Set the output to the table we just freed. */
|
||||
*out = KVirtualAddress(pte);
|
||||
} else {
|
||||
/* We want to upgrade a non-contiguous mapping to a contiguous mapping. */
|
||||
PageTableEntry *pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerContiguousBlock * sizeof(PageTableEntry)));
|
||||
const KPhysicalAddress phys_addr = util::AlignDown(GetBlock(pte, context->level), GetBlockSize(context->level, true));
|
||||
|
||||
/* First, check that all entries are valid for us to merge. */
|
||||
const u64 entry_template = pte->GetEntryTemplateForMerge();
|
||||
for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) {
|
||||
if (!pte[i].IsForMerge(entry_template | GetInteger(phys_addr + (i << (PageBits + LevelBits * context->level))) | pte->GetTestTableMask())) {
|
||||
return false;
|
||||
}
|
||||
if (i > 0 && pte[i].IsHeadOrHeadAndBodyMergeDisabled()) {
|
||||
return false;
|
||||
}
|
||||
if (i < BlocksPerContiguousBlock - 1 && pte[i].IsTailMergeDisabled()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* The entries are valid for us to merge, so merge them. */
|
||||
const auto *head_pte = pte;
|
||||
const auto *tail_pte = pte + BlocksPerContiguousBlock - 1;
|
||||
const auto sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(head_pte->IsHeadMergeDisabled(), head_pte->IsHeadAndBodyMergeDisabled(), tail_pte->IsTailMergeDisabled());
|
||||
|
||||
for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) {
|
||||
pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, phys_addr + (i << (PageBits + LevelBits * context->level)), PageTableEntry(entry_template), sw_reserved_bits, true, context->level == EntryLevel_L3);
|
||||
}
|
||||
on_entry_updated(pt);
|
||||
|
||||
/* Update our context. */
|
||||
context->level_entries[context->level] = pte;
|
||||
context->is_contiguous = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KPageTableImpl::SeparatePages(TraversalEntry *entry, TraversalContext *context, KProcessAddress address, PageTableEntry *pte, EntryUpdatedCallback on_entry_updated, const void *pt) const {
|
||||
/* We want to downgrade the pages by one step. */
|
||||
if (context->is_contiguous) {
|
||||
/* We want to downgrade a contiguous mapping to a non-contiguous mapping. */
|
||||
pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerContiguousBlock * sizeof(PageTableEntry)));
|
||||
|
||||
auto * const first = pte;
|
||||
const KPhysicalAddress block = this->GetBlock(first, context->level);
|
||||
for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) {
|
||||
pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, block + (i << (PageBits + LevelBits * context->level)), PageTableEntry(first->GetEntryTemplateForSeparateContiguous(i)), PageTableEntry::SeparateContiguousTag{});
|
||||
}
|
||||
on_entry_updated(pt);
|
||||
|
||||
context->is_contiguous = false;
|
||||
|
||||
context->level_entries[context->level] = pte + (this->GetLevelIndex(address, context->level) & (BlocksPerContiguousBlock - 1));
|
||||
} else {
|
||||
/* We want to downgrade a block into a table. */
|
||||
auto * const first = context->level_entries[context->level];
|
||||
const KPhysicalAddress block = this->GetBlock(first, context->level);
|
||||
for (size_t i = 0; i < BlocksPerTable; ++i) {
|
||||
pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, block + (i << (PageBits + LevelBits * (context->level - 1))), PageTableEntry(first->GetEntryTemplateForSeparate(i)), PageTableEntry::SoftwareReservedBit_None, true, context->level - 1 == EntryLevel_L3);
|
||||
}
|
||||
|
||||
context->is_contiguous = true;
|
||||
context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) - 1);
|
||||
|
||||
/* Wait for pending stores to complete. */
|
||||
cpu::DataSynchronizationBarrierInnerShareableStore();
|
||||
|
||||
/* Update the block entry to be a table entry. */
|
||||
*context->level_entries[context->level + 1] = PageTableEntry(PageTableEntry::TableTag{}, KPageTable::GetPageTablePhysicalAddress(KVirtualAddress(pte)), m_is_kernel, true, BlocksPerTable);
|
||||
on_entry_updated(pt);
|
||||
|
||||
context->level_entries[context->level] = pte + this->GetLevelIndex(address, context->level);
|
||||
}
|
||||
|
||||
entry->sw_reserved_bits = context->level_entries[context->level]->GetSoftwareReservedBits();
|
||||
entry->attr = 0;
|
||||
entry->phys_addr = this->GetBlock(context->level_entries[context->level], context->level) + this->GetOffset(address, context->level);
|
||||
entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * context->level + 4 * context->is_contiguous);
|
||||
}
|
||||
|
||||
void KPageTableImpl::Dump(uintptr_t start, size_t size) const {
|
||||
/* If zero size, there's nothing to dump. */
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Define extents. */
|
||||
const uintptr_t end = start + size;
|
||||
const uintptr_t last = end - 1;
|
||||
|
||||
/* Define tracking variables. */
|
||||
bool unmapped = false;
|
||||
uintptr_t unmapped_start = 0;
|
||||
|
||||
/* Walk the table. */
|
||||
uintptr_t cur = start;
|
||||
while (cur < end) {
|
||||
/* Validate that we can read the actual entry. */
|
||||
const size_t l0_index = GetL0Index(cur);
|
||||
const size_t l1_index = GetL1Index(cur);
|
||||
if (m_is_kernel) {
|
||||
/* Kernel entries must be accessed via TTBR1. */
|
||||
if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* User entries must be accessed with TTBR0. */
|
||||
if ((l0_index != 0) || l1_index >= m_num_entries) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get from l1 table. */
|
||||
const L1PageTableEntry *l1_entry = this->GetL1Entry(cur);
|
||||
if (l1_entry->IsBlock()) {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L1BlockSize);
|
||||
if (unmapped) {
|
||||
unmapped = false;
|
||||
MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1);
|
||||
}
|
||||
|
||||
/* Print. */
|
||||
MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=1G Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur,
|
||||
*reinterpret_cast<const u64 *>(l1_entry),
|
||||
reinterpret_cast<void *>(GetInteger(l1_entry->GetBlock())),
|
||||
l1_entry->IsMapped(),
|
||||
l1_entry->IsUserExecuteNever(),
|
||||
l1_entry->IsPrivilegedExecuteNever(),
|
||||
l1_entry->IsContiguous(),
|
||||
!l1_entry->IsGlobal(),
|
||||
static_cast<int>(l1_entry->GetAccessFlagInteger()),
|
||||
static_cast<unsigned int>(l1_entry->GetShareableInteger()),
|
||||
l1_entry->IsReadOnly(),
|
||||
l1_entry->IsUserAccessible(),
|
||||
l1_entry->IsNonSecure(),
|
||||
static_cast<int>(l1_entry->GetPageAttributeInteger()),
|
||||
l1_entry->IsHeadMergeDisabled(),
|
||||
l1_entry->IsHeadAndBodyMergeDisabled(),
|
||||
l1_entry->IsTailMergeDisabled());
|
||||
|
||||
/* Advance. */
|
||||
cur += L1BlockSize;
|
||||
continue;
|
||||
} else if (!l1_entry->IsTable()) {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L1BlockSize);
|
||||
if (!unmapped) {
|
||||
unmapped_start = cur;
|
||||
unmapped = true;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
cur += L1BlockSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to get from l2 table. */
|
||||
const L2PageTableEntry *l2_entry = this->GetL2Entry(l1_entry, cur);
|
||||
if (l2_entry->IsBlock()) {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L2BlockSize);
|
||||
if (unmapped) {
|
||||
unmapped = false;
|
||||
MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1);
|
||||
}
|
||||
|
||||
/* Print. */
|
||||
MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=2M Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur,
|
||||
*reinterpret_cast<const u64 *>(l2_entry),
|
||||
reinterpret_cast<void *>(GetInteger(l2_entry->GetBlock())),
|
||||
l2_entry->IsMapped(),
|
||||
l2_entry->IsUserExecuteNever(),
|
||||
l2_entry->IsPrivilegedExecuteNever(),
|
||||
l2_entry->IsContiguous(),
|
||||
!l2_entry->IsGlobal(),
|
||||
static_cast<int>(l2_entry->GetAccessFlagInteger()),
|
||||
static_cast<unsigned int>(l2_entry->GetShareableInteger()),
|
||||
l2_entry->IsReadOnly(),
|
||||
l2_entry->IsUserAccessible(),
|
||||
l2_entry->IsNonSecure(),
|
||||
static_cast<int>(l2_entry->GetPageAttributeInteger()),
|
||||
l2_entry->IsHeadMergeDisabled(),
|
||||
l2_entry->IsHeadAndBodyMergeDisabled(),
|
||||
l2_entry->IsTailMergeDisabled());
|
||||
|
||||
/* Advance. */
|
||||
cur += L2BlockSize;
|
||||
continue;
|
||||
} else if (!l2_entry->IsTable()) {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L2BlockSize);
|
||||
if (!unmapped) {
|
||||
unmapped_start = cur;
|
||||
unmapped = true;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
cur += L2BlockSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to get from l3 table. */
|
||||
const L3PageTableEntry *l3_entry = this->GetL3Entry(l2_entry, cur);
|
||||
if (l3_entry->IsBlock()) {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L3BlockSize);
|
||||
if (unmapped) {
|
||||
unmapped = false;
|
||||
MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1);
|
||||
}
|
||||
|
||||
/* Print. */
|
||||
MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=4K Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur,
|
||||
*reinterpret_cast<const u64 *>(l3_entry),
|
||||
reinterpret_cast<void *>(GetInteger(l3_entry->GetBlock())),
|
||||
l3_entry->IsMapped(),
|
||||
l3_entry->IsUserExecuteNever(),
|
||||
l3_entry->IsPrivilegedExecuteNever(),
|
||||
l3_entry->IsContiguous(),
|
||||
!l3_entry->IsGlobal(),
|
||||
static_cast<int>(l3_entry->GetAccessFlagInteger()),
|
||||
static_cast<unsigned int>(l3_entry->GetShareableInteger()),
|
||||
l3_entry->IsReadOnly(),
|
||||
l3_entry->IsUserAccessible(),
|
||||
l3_entry->IsNonSecure(),
|
||||
static_cast<int>(l3_entry->GetPageAttributeInteger()),
|
||||
l3_entry->IsHeadMergeDisabled(),
|
||||
l3_entry->IsHeadAndBodyMergeDisabled(),
|
||||
l3_entry->IsTailMergeDisabled());
|
||||
|
||||
/* Advance. */
|
||||
cur += L3BlockSize;
|
||||
continue;
|
||||
} else {
|
||||
/* Update. */
|
||||
cur = util::AlignDown(cur, L3BlockSize);
|
||||
if (!unmapped) {
|
||||
unmapped_start = cur;
|
||||
unmapped = true;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
cur += L3BlockSize;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print the last unmapped range if necessary. */
|
||||
if (unmapped) {
|
||||
MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, last);
|
||||
}
|
||||
}
|
||||
|
||||
size_t KPageTableImpl::CountPageTables() const {
|
||||
size_t num_tables = 0;
|
||||
|
||||
#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING)
|
||||
{
|
||||
++num_tables;
|
||||
for (size_t l1_index = 0; l1_index < m_num_entries; ++l1_index) {
|
||||
auto &l1_entry = m_table[l1_index];
|
||||
if (l1_entry.IsTable()) {
|
||||
++num_tables;
|
||||
for (size_t l2_index = 0; l2_index < MaxPageTableEntries; ++l2_index) {
|
||||
auto *l2_entry = GetPointer<L2PageTableEntry>(GetTableEntry(KMemoryLayout::GetLinearVirtualAddress(l1_entry.GetTable()), l2_index));
|
||||
if (l2_entry->IsTable()) {
|
||||
++num_tables;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return num_tables;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
void KSupervisorPageTable::Initialize(s32 core_id) {
|
||||
/* Verify that sctlr_el1 has the wxn bit set. */
|
||||
MESOSPHERE_ABORT_UNLESS(cpu::SystemControlRegisterAccessor().GetWxn());
|
||||
|
||||
/* Invalidate the entire TLB. */
|
||||
cpu::InvalidateEntireTlb();
|
||||
|
||||
/* If core 0, initialize our base page table. */
|
||||
if (core_id == 0) {
|
||||
/* TODO: constexpr defines. */
|
||||
const u64 ttbr1 = cpu::GetTtbr1El1() & 0xFFFFFFFFFFFFul;
|
||||
const u64 kernel_vaddr_start = 0xFFFFFF8000000000ul;
|
||||
const u64 kernel_vaddr_end = 0xFFFFFFFFFFE00000ul;
|
||||
void *table = GetVoidPointer(KPageTableBase::GetLinearMappedVirtualAddress(ttbr1));
|
||||
m_page_table.InitializeForKernel(table, kernel_vaddr_start, kernel_vaddr_end);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::arch::arm64 {
|
||||
|
||||
/* These are implemented elsewhere (asm). */
|
||||
void UserModeThreadStarter();
|
||||
void SupervisorModeThreadStarter();
|
||||
|
||||
void InvokeSupervisorModeThread(uintptr_t argument, uintptr_t entrypoint) {
|
||||
/* Invoke the function. */
|
||||
using SupervisorModeFunctionType = void (*)(uintptr_t);
|
||||
reinterpret_cast<SupervisorModeFunctionType>(entrypoint)(argument);
|
||||
|
||||
/* Wait forever. */
|
||||
AMS_INFINITE_LOOP();
|
||||
}
|
||||
|
||||
void OnThreadStart() {
|
||||
MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled());
|
||||
/* Send KDebug event for this thread's creation. */
|
||||
{
|
||||
KScopedInterruptEnable ei;
|
||||
|
||||
const uintptr_t params[2] = { GetCurrentThread().GetId(), GetInteger(GetCurrentThread().GetThreadLocalRegionAddress()) };
|
||||
KDebug::OnDebugEvent(ams::svc::DebugEvent_CreateThread, params, util::size(params));
|
||||
}
|
||||
|
||||
/* Handle any pending dpc. */
|
||||
while (GetCurrentThread().HasDpc()) {
|
||||
KDpcManager::HandleDpc();
|
||||
}
|
||||
|
||||
/* Clear our status as in an exception handler */
|
||||
GetCurrentThread().ClearInExceptionHandler();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE bool IsFpuEnabled() {
|
||||
return cpu::ArchitecturalFeatureAccessControlRegisterAccessor().IsFpEnabled();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void EnableFpu() {
|
||||
cpu::ArchitecturalFeatureAccessControlRegisterAccessor().SetFpEnabled(true).Store();
|
||||
cpu::InstructionMemoryBarrier();
|
||||
}
|
||||
|
||||
uintptr_t SetupStackForUserModeThreadStarter(KVirtualAddress pc, KVirtualAddress k_sp, KVirtualAddress u_sp, uintptr_t arg, const bool is_64_bit) {
|
||||
/* NOTE: Stack layout on entry looks like following: */
|
||||
/* SP */
|
||||
/* | */
|
||||
/* v */
|
||||
/* | KExceptionContext (size 0x120) | KThread::StackParameters (size 0x130) | */
|
||||
KExceptionContext *ctx = GetPointer<KExceptionContext>(k_sp) - 1;
|
||||
|
||||
/* Clear context. */
|
||||
std::memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
/* Set PC and argument. */
|
||||
ctx->pc = GetInteger(pc) & ~(UINT64_C(1));
|
||||
ctx->x[0] = arg;
|
||||
|
||||
/* Set PSR. */
|
||||
if (is_64_bit) {
|
||||
ctx->psr = 0;
|
||||
} else {
|
||||
constexpr u64 PsrArmValue = 0x00;
|
||||
constexpr u64 PsrThumbValue = 0x20;
|
||||
ctx->psr = ((pc & 1) == 0 ? PsrArmValue : PsrThumbValue) | (0x10);
|
||||
MESOSPHERE_LOG("Creating User 32-Thread, %016lx\n", GetInteger(pc));
|
||||
}
|
||||
|
||||
/* Set CFI-value. */
|
||||
if (is_64_bit) {
|
||||
ctx->x[18] = KSystemControl::GenerateRandomU64() | 1;
|
||||
}
|
||||
|
||||
/* Set stack pointer. */
|
||||
if (is_64_bit) {
|
||||
ctx->sp = GetInteger(u_sp);
|
||||
} else {
|
||||
ctx->x[13] = GetInteger(u_sp);
|
||||
}
|
||||
|
||||
return reinterpret_cast<uintptr_t>(ctx);
|
||||
}
|
||||
|
||||
uintptr_t SetupStackForSupervisorModeThreadStarter(KVirtualAddress pc, KVirtualAddress sp, uintptr_t arg) {
|
||||
/* NOTE: Stack layout on entry looks like following: */
|
||||
/* SP */
|
||||
/* | */
|
||||
/* v */
|
||||
/* | u64 argument | u64 entrypoint | KThread::StackParameters (size 0x140) | */
|
||||
static_assert(sizeof(KThread::StackParameters) == 0x140);
|
||||
|
||||
u64 *stack = GetPointer<u64>(sp);
|
||||
*(--stack) = GetInteger(pc);
|
||||
*(--stack) = arg;
|
||||
return reinterpret_cast<uintptr_t>(stack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result KThreadContext::Initialize(KVirtualAddress u_pc, KVirtualAddress k_sp, KVirtualAddress u_sp, uintptr_t arg, bool is_user, bool is_64_bit, bool is_main) {
|
||||
MESOSPHERE_ASSERT(k_sp != Null<KVirtualAddress>);
|
||||
|
||||
/* Ensure that the stack pointers are aligned. */
|
||||
k_sp = util::AlignDown(GetInteger(k_sp), 16);
|
||||
u_sp = util::AlignDown(GetInteger(u_sp), 16);
|
||||
|
||||
/* Determine LR and SP. */
|
||||
if (is_user) {
|
||||
/* Usermode thread. */
|
||||
m_lr = reinterpret_cast<uintptr_t>(::ams::kern::arch::arm64::UserModeThreadStarter);
|
||||
m_sp = SetupStackForUserModeThreadStarter(u_pc, k_sp, u_sp, arg, is_64_bit);
|
||||
} else {
|
||||
/* Kernel thread. */
|
||||
MESOSPHERE_ASSERT(is_64_bit);
|
||||
|
||||
if (is_main) {
|
||||
/* Main thread. */
|
||||
m_lr = GetInteger(u_pc);
|
||||
m_sp = GetInteger(k_sp);
|
||||
} else {
|
||||
/* Generic Kernel thread. */
|
||||
m_lr = reinterpret_cast<uintptr_t>(::ams::kern::arch::arm64::SupervisorModeThreadStarter);
|
||||
m_sp = SetupStackForSupervisorModeThreadStarter(u_pc, k_sp, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear callee-saved registers. */
|
||||
for (size_t i = 0; i < util::size(m_callee_saved.registers); i++) {
|
||||
m_callee_saved.registers[i] = 0;
|
||||
}
|
||||
|
||||
/* Clear FPU state. */
|
||||
m_fpcr = 0;
|
||||
m_fpsr = 0;
|
||||
for (size_t i = 0; i < util::size(m_callee_saved_fpu.fpu64.v); ++i) {
|
||||
m_callee_saved_fpu.fpu64.v[i] = 0;
|
||||
}
|
||||
|
||||
/* Lock the context, if we're a main thread. */
|
||||
m_locked = is_main;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KThreadContext::SetArguments(uintptr_t arg0, uintptr_t arg1) {
|
||||
u64 *stack = reinterpret_cast<u64 *>(m_sp);
|
||||
stack[0] = arg0;
|
||||
stack[1] = arg1;
|
||||
}
|
||||
|
||||
void KThreadContext::CloneFpuStatus() {
|
||||
u64 pcr, psr;
|
||||
cpu::InstructionMemoryBarrier();
|
||||
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
if (IsFpuEnabled()) {
|
||||
__asm__ __volatile__("mrs %[pcr], fpcr" : [pcr]"=r"(pcr) :: "memory");
|
||||
__asm__ __volatile__("mrs %[psr], fpsr" : [psr]"=r"(psr) :: "memory");
|
||||
} else {
|
||||
pcr = GetCurrentThread().GetContext().GetFpcr();
|
||||
psr = GetCurrentThread().GetContext().GetFpsr();
|
||||
}
|
||||
|
||||
this->SetFpcr(pcr);
|
||||
this->SetFpsr(psr);
|
||||
}
|
||||
|
||||
void GetUserContext(ams::svc::ThreadContext *out, const KThread *thread) {
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
MESOSPHERE_ASSERT(thread->IsSuspended());
|
||||
MESOSPHERE_ASSERT(thread->GetOwnerProcess() != nullptr);
|
||||
|
||||
/* Get the contexts. */
|
||||
const KExceptionContext *e_ctx = GetExceptionContext(thread);
|
||||
const KThreadContext *t_ctx = std::addressof(thread->GetContext());
|
||||
|
||||
if (thread->GetOwnerProcess()->Is64Bit()) {
|
||||
/* Set special registers. */
|
||||
out->fp = e_ctx->x[29];
|
||||
out->lr = e_ctx->x[30];
|
||||
out->sp = e_ctx->sp;
|
||||
out->pc = e_ctx->pc;
|
||||
out->pstate = e_ctx->psr & cpu::El0Aarch64PsrMask;
|
||||
|
||||
/* Get the thread's general purpose registers. */
|
||||
if (thread->IsCallingSvc()) {
|
||||
for (size_t i = 19; i < 29; ++i) {
|
||||
out->r[i] = e_ctx->x[i];
|
||||
}
|
||||
if (e_ctx->write == 0) {
|
||||
out->pc -= sizeof(u32);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 29; ++i) {
|
||||
out->r[i] = e_ctx->x[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy tpidr. */
|
||||
out->tpidr = e_ctx->tpidr;
|
||||
|
||||
/* Copy fpu registers. */
|
||||
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
|
||||
static_assert(KThreadContext::NumCallerSavedFpuRegisters == KThreadContext::NumCalleeSavedFpuRegisters * 3);
|
||||
static_assert(KThreadContext::NumFpuRegisters == KThreadContext::NumCallerSavedFpuRegisters + KThreadContext::NumCalleeSavedFpuRegisters);
|
||||
const auto &caller_save_fpu = thread->GetCallerSaveFpuRegisters().fpu64;
|
||||
const auto &callee_save_fpu = t_ctx->GetCalleeSaveFpuRegisters().fpu64;
|
||||
|
||||
if (!thread->IsCallingSvc() || thread->IsInUsermodeExceptionHandler()) {
|
||||
KThreadContext::GetFpuRegisters(out->v, caller_save_fpu, callee_save_fpu);
|
||||
} else {
|
||||
for (size_t i = 0; i < KThreadContext::NumCalleeSavedFpuRegisters; ++i) {
|
||||
out->v[(KThreadContext::NumCallerSavedFpuRegisters / 3) + i] = caller_save_fpu.v[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Set special registers. */
|
||||
out->pc = static_cast<u32>(e_ctx->pc);
|
||||
out->pstate = e_ctx->psr & cpu::El0Aarch32PsrMask;
|
||||
|
||||
/* Get the thread's general purpose registers. */
|
||||
for (size_t i = 0; i < 15; ++i) {
|
||||
out->r[i] = static_cast<u32>(e_ctx->x[i]);
|
||||
}
|
||||
|
||||
/* Adjust PC, if the thread is calling svc. */
|
||||
if (thread->IsCallingSvc()) {
|
||||
if (e_ctx->write == 0) {
|
||||
/* Adjust by 2 if thumb mode, 4 if arm mode. */
|
||||
out->pc -= ((e_ctx->psr & 0x20) == 0) ? sizeof(u32) : sizeof(u16);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy tpidr. */
|
||||
out->tpidr = static_cast<u32>(e_ctx->tpidr);
|
||||
|
||||
/* Copy fpu registers. */
|
||||
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
|
||||
static_assert(KThreadContext::NumCallerSavedFpuRegisters == KThreadContext::NumCalleeSavedFpuRegisters * 3);
|
||||
static_assert(KThreadContext::NumFpuRegisters == KThreadContext::NumCallerSavedFpuRegisters + KThreadContext::NumCalleeSavedFpuRegisters);
|
||||
const auto &caller_save_fpu = thread->GetCallerSaveFpuRegisters().fpu32;
|
||||
const auto &callee_save_fpu = t_ctx->GetCalleeSaveFpuRegisters().fpu32;
|
||||
|
||||
if (!thread->IsCallingSvc() || thread->IsInUsermodeExceptionHandler()) {
|
||||
KThreadContext::GetFpuRegisters(out->v, caller_save_fpu, callee_save_fpu);
|
||||
} else {
|
||||
for (size_t i = 0; i < KThreadContext::NumCalleeSavedFpuRegisters / 2; ++i) {
|
||||
out->v[((KThreadContext::NumCallerSavedFpuRegisters / 3) / 2) + i] = caller_save_fpu.v[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy fpcr/fpsr. */
|
||||
out->fpcr = t_ctx->GetFpcr();
|
||||
out->fpsr = t_ctx->GetFpsr();
|
||||
}
|
||||
|
||||
void KThreadContext::OnThreadTerminating(const KThread *thread) {
|
||||
MESOSPHERE_UNUSED(thread);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere/kern_build_config.hpp>
|
||||
#include <mesosphere/kern_select_assembly_offsets.h>
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_PANIC_REGISTER_DUMP)
|
||||
|
||||
#define MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT \
|
||||
/* Save x0/x1 to stack. */ \
|
||||
stp x0, x1, [sp, #-16]!; \
|
||||
\
|
||||
/* Get the exception context for the core. */ \
|
||||
adr x0, _ZN3ams4kern26g_panic_exception_contextsE; \
|
||||
mrs x1, mpidr_el1; \
|
||||
and x1, x1, #0xFF; \
|
||||
lsl x1, x1, #0x8; \
|
||||
add x0, x0, x1; \
|
||||
lsr x1, x1, #0x3; \
|
||||
add x0, x0, x1; \
|
||||
\
|
||||
/* Save x0/x1/sp to the context. */ \
|
||||
ldr x1, [sp, #(8 * 0)]; \
|
||||
str x1, [x0, #(EXCEPTION_CONTEXT_X0)]; \
|
||||
ldr x1, [sp, #(8 * 1)]; \
|
||||
str x1, [x0, #(EXCEPTION_CONTEXT_X1)]; \
|
||||
\
|
||||
/* Save all other registers to the context. */ \
|
||||
stp x2, x3, [x0, #(EXCEPTION_CONTEXT_X2_X3)]; \
|
||||
stp x4, x5, [x0, #(EXCEPTION_CONTEXT_X4_X5)]; \
|
||||
stp x6, x7, [x0, #(EXCEPTION_CONTEXT_X6_X7)]; \
|
||||
stp x8, x9, [x0, #(EXCEPTION_CONTEXT_X8_X9)]; \
|
||||
stp x10, x11, [x0, #(EXCEPTION_CONTEXT_X10_X11)]; \
|
||||
stp x12, x13, [x0, #(EXCEPTION_CONTEXT_X12_X13)]; \
|
||||
stp x14, x15, [x0, #(EXCEPTION_CONTEXT_X14_X15)]; \
|
||||
stp x16, x17, [x0, #(EXCEPTION_CONTEXT_X16_X17)]; \
|
||||
stp x18, x19, [x0, #(EXCEPTION_CONTEXT_X18_X19)]; \
|
||||
stp x20, x21, [x0, #(EXCEPTION_CONTEXT_X20_X21)]; \
|
||||
stp x22, x23, [x0, #(EXCEPTION_CONTEXT_X22_X23)]; \
|
||||
stp x24, x25, [x0, #(EXCEPTION_CONTEXT_X24_X25)]; \
|
||||
stp x26, x27, [x0, #(EXCEPTION_CONTEXT_X26_X27)]; \
|
||||
stp x28, x29, [x0, #(EXCEPTION_CONTEXT_X28_X29)]; \
|
||||
\
|
||||
add x1, sp, #16; \
|
||||
stp x30, x1, [x0, #(EXCEPTION_CONTEXT_X30_SP)]; \
|
||||
\
|
||||
/* Restore x0/x1. */ \
|
||||
ldp x0, x1, [sp], #16;
|
||||
|
||||
#else
|
||||
|
||||
#define MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT
|
||||
|
||||
#endif
|
||||
|
||||
/* ams::kern::Panic(const char *file, int line, const char *format, ...) */
|
||||
.section .text._ZN3ams4kern5PanicEPKciS2_z, "ax", %progbits
|
||||
.global _ZN3ams4kern5PanicEPKciS2_z
|
||||
.type _ZN3ams4kern5PanicEPKciS2_z, %function
|
||||
_ZN3ams4kern5PanicEPKciS2_z:
|
||||
/* Generate the exception context. */
|
||||
MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT
|
||||
|
||||
/* Jump to the architecturally-common implementation function. */
|
||||
b _ZN3ams4kern9PanicImplEPKciS2_z
|
||||
|
||||
|
||||
/* ams::kern::Panic() */
|
||||
.section .text._ZN3ams4kern5PanicEv, "ax", %progbits
|
||||
.global _ZN3ams4kern5PanicEv
|
||||
.type _ZN3ams4kern5PanicEv, %function
|
||||
_ZN3ams4kern5PanicEv:
|
||||
/* Generate the exception context. */
|
||||
MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT
|
||||
|
||||
/* Jump to the architecturally-common implementation function. */
|
||||
b _ZN3ams4kern9PanicImplEv
|
||||
@@ -0,0 +1,800 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccessFunctionAreaBegin() */
|
||||
.section .text._ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv
|
||||
.type _ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv:
|
||||
/* NOTE: This is not a real function, and only exists as a label for safety. */
|
||||
|
||||
/* ================ All Userspace Access Functions after this line. ================ */
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUser(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm:
|
||||
/* Check if there's anything to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
|
||||
/* Keep track of the last address. */
|
||||
add x3, x1, x2
|
||||
|
||||
1: /* We're copying memory byte-by-byte. */
|
||||
ldtrb w2, [x1]
|
||||
strb w2, [x0], #1
|
||||
add x1, x1, #1
|
||||
cmp x1, x3
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserAligned32Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm:
|
||||
/* Check if there are 0x40 bytes to copy */
|
||||
cmp x2, #0x3F
|
||||
b.ls 1f
|
||||
ldtr x4, [x1, #0x00]
|
||||
ldtr x5, [x1, #0x08]
|
||||
ldtr x6, [x1, #0x10]
|
||||
ldtr x7, [x1, #0x18]
|
||||
ldtr x8, [x1, #0x20]
|
||||
ldtr x9, [x1, #0x28]
|
||||
ldtr x10, [x1, #0x30]
|
||||
ldtr x11, [x1, #0x38]
|
||||
stp x4, x5, [x0, #0x00]
|
||||
stp x6, x7, [x0, #0x10]
|
||||
stp x8, x9, [x0, #0x20]
|
||||
stp x10, x11, [x0, #0x30]
|
||||
add x0, x0, #0x40
|
||||
add x1, x1, #0x40
|
||||
sub x2, x2, #0x40
|
||||
b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm
|
||||
|
||||
1: /* We have less than 0x40 bytes to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
ldtr w4, [x1]
|
||||
str w4, [x0], #4
|
||||
add x1, x1, #4
|
||||
sub x2, x2, #4
|
||||
b 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserAligned64Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm:
|
||||
/* Check if there are 0x40 bytes to copy */
|
||||
cmp x2, #0x3F
|
||||
b.ls 1f
|
||||
ldtr x4, [x1, #0x00]
|
||||
ldtr x5, [x1, #0x08]
|
||||
ldtr x6, [x1, #0x10]
|
||||
ldtr x7, [x1, #0x18]
|
||||
ldtr x8, [x1, #0x20]
|
||||
ldtr x9, [x1, #0x28]
|
||||
ldtr x10, [x1, #0x30]
|
||||
ldtr x11, [x1, #0x38]
|
||||
stp x4, x5, [x0, #0x00]
|
||||
stp x6, x7, [x0, #0x10]
|
||||
stp x8, x9, [x0, #0x20]
|
||||
stp x10, x11, [x0, #0x30]
|
||||
add x0, x0, #0x40
|
||||
add x1, x1, #0x40
|
||||
sub x2, x2, #0x40
|
||||
b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm
|
||||
|
||||
1: /* We have less than 0x40 bytes to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
ldtr x4, [x1]
|
||||
str x4, [x0], #8
|
||||
add x1, x1, #8
|
||||
sub x2, x2, #8
|
||||
b 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize64Bit(void *dst, const void *src) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv:
|
||||
/* Just load and store a u64. */
|
||||
ldtr x2, [x1]
|
||||
str x2, [x0]
|
||||
|
||||
/* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize32Bit(void *dst, const void *src) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv:
|
||||
/* Just load and store a u32. */
|
||||
ldtr w2, [x1]
|
||||
str w2, [x0]
|
||||
|
||||
/* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize32BitWithSupervisorAccess(void *dst, const void *src) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv:
|
||||
/* Just load and store a u32. */
|
||||
/* NOTE: This is done with supervisor access permissions. */
|
||||
ldr w2, [x1]
|
||||
str w2, [x0]
|
||||
|
||||
/* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyStringFromUser(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm:
|
||||
/* Check if there's anything to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Keep track of the start address and last address. */
|
||||
mov x4, x1
|
||||
add x3, x1, x2
|
||||
|
||||
1: /* We're copying memory byte-by-byte. */
|
||||
ldtrb w2, [x1]
|
||||
strb w2, [x0], #1
|
||||
add x1, x1, #1
|
||||
|
||||
/* If we read a null terminator, we're done. */
|
||||
cmp w2, #0
|
||||
b.eq 2f
|
||||
|
||||
/* Check if we're done. */
|
||||
cmp x1, x3
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done, and we copied some amount of data from the string. */
|
||||
sub x0, x1, x4
|
||||
ret
|
||||
|
||||
3: /* We're done, and there was no string data. */
|
||||
mov x0, #0
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUser(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm:
|
||||
/* Check if there's anything to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
|
||||
/* Keep track of the last address. */
|
||||
add x3, x1, x2
|
||||
|
||||
1: /* We're copying memory byte-by-byte. */
|
||||
ldrb w2, [x1], #1
|
||||
sttrb w2, [x0]
|
||||
add x0, x0, #1
|
||||
cmp x1, x3
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserAligned32Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm:
|
||||
/* Check if there are 0x40 bytes to copy */
|
||||
cmp x2, #0x3F
|
||||
b.ls 1f
|
||||
ldp x4, x5, [x1, #0x00]
|
||||
ldp x6, x7, [x1, #0x10]
|
||||
ldp x8, x9, [x1, #0x20]
|
||||
ldp x10, x11, [x1, #0x30]
|
||||
sttr x4, [x0, #0x00]
|
||||
sttr x5, [x0, #0x08]
|
||||
sttr x6, [x0, #0x10]
|
||||
sttr x7, [x0, #0x18]
|
||||
sttr x8, [x0, #0x20]
|
||||
sttr x9, [x0, #0x28]
|
||||
sttr x10, [x0, #0x30]
|
||||
sttr x11, [x0, #0x38]
|
||||
add x0, x0, #0x40
|
||||
add x1, x1, #0x40
|
||||
sub x2, x2, #0x40
|
||||
b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm
|
||||
|
||||
1: /* We have less than 0x40 bytes to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
ldr w4, [x1], #4
|
||||
sttr w4, [x0]
|
||||
add x0, x0, #4
|
||||
sub x2, x2, #4
|
||||
b 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserAligned64Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm:
|
||||
/* Check if there are 0x40 bytes to copy */
|
||||
cmp x2, #0x3F
|
||||
b.ls 1f
|
||||
ldp x4, x5, [x1, #0x00]
|
||||
ldp x6, x7, [x1, #0x10]
|
||||
ldp x8, x9, [x1, #0x20]
|
||||
ldp x10, x11, [x1, #0x30]
|
||||
sttr x4, [x0, #0x00]
|
||||
sttr x5, [x0, #0x08]
|
||||
sttr x6, [x0, #0x10]
|
||||
sttr x7, [x0, #0x18]
|
||||
sttr x8, [x0, #0x20]
|
||||
sttr x9, [x0, #0x28]
|
||||
sttr x10, [x0, #0x30]
|
||||
sttr x11, [x0, #0x38]
|
||||
add x0, x0, #0x40
|
||||
add x1, x1, #0x40
|
||||
sub x2, x2, #0x40
|
||||
b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm
|
||||
|
||||
1: /* We have less than 0x40 bytes to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 2f
|
||||
ldr x4, [x1], #8
|
||||
sttr x4, [x0]
|
||||
add x0, x0, #8
|
||||
sub x2, x2, #8
|
||||
b 1b
|
||||
|
||||
2: /* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserSize32Bit(void *dst, const void *src) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv:
|
||||
/* Just load and store a u32. */
|
||||
ldr w2, [x1]
|
||||
sttr w2, [x0]
|
||||
|
||||
/* We're done. */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyStringToUser(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm:
|
||||
/* Check if there's anything to copy. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Keep track of the start address and last address. */
|
||||
mov x4, x1
|
||||
add x3, x1, x2
|
||||
|
||||
1: /* We're copying memory byte-by-byte. */
|
||||
ldrb w2, [x1], #1
|
||||
sttrb w2, [x0]
|
||||
add x0, x0, #1
|
||||
|
||||
/* If we read a null terminator, we're done. */
|
||||
cmp w2, #0
|
||||
b.eq 2f
|
||||
|
||||
/* Check if we're done. */
|
||||
cmp x1, x3
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done, and we copied some amount of data from the string. */
|
||||
sub x0, x1, x4
|
||||
ret
|
||||
|
||||
3: /* We're done, and there was no string data. */
|
||||
mov x0, #0
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::UpdateLockAtomic(u32 *out, u32 *address, u32 if_zero, u32 new_orr_mask) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj:
|
||||
/* Load the value from the address. */
|
||||
ldaxr w4, [x1]
|
||||
|
||||
/* Orr in the new mask. */
|
||||
orr w5, w4, w3
|
||||
|
||||
/* If the value is zero, use the if_zero value, otherwise use the newly orr'd value. */
|
||||
cmp w4, wzr
|
||||
csel w5, w2, w5, eq
|
||||
|
||||
/* Try to store. */
|
||||
stlxr w6, w5, [x1]
|
||||
|
||||
/* If we failed to store, try again. */
|
||||
cbnz w6, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj
|
||||
|
||||
/* We're done. */
|
||||
str w4, [x0]
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::UpdateIfEqualAtomic(s32 *out, s32 *address, s32 compare_value, s32 new_value) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii:
|
||||
/* Load the value from the address. */
|
||||
ldaxr w4, [x1]
|
||||
|
||||
/* Compare it to the desired one. */
|
||||
cmp w4, w2
|
||||
|
||||
/* If equal, we want to try to write the new value. */
|
||||
b.eq 1f
|
||||
|
||||
/* Otherwise, clear our exclusive hold and finish. */
|
||||
clrex
|
||||
b 2f
|
||||
|
||||
1: /* Try to store. */
|
||||
stlxr w5, w3, [x1]
|
||||
|
||||
/* If we failed to store, try again. */
|
||||
cbnz w5, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii
|
||||
|
||||
2: /* We're done. */
|
||||
str w4, [x0]
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::DecrementIfLessThanAtomic(s32 *out, s32 *address, s32 compare) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i:
|
||||
/* Load the value from the address. */
|
||||
ldaxr w3, [x1]
|
||||
|
||||
/* Compare it to the desired one. */
|
||||
cmp w3, w2
|
||||
|
||||
/* If less than, we want to try to decrement. */
|
||||
b.lt 1f
|
||||
|
||||
/* Otherwise, clear our exclusive hold and finish. */
|
||||
clrex
|
||||
b 2f
|
||||
|
||||
1: /* Decrement and try to store. */
|
||||
sub w4, w3, #1
|
||||
stlxr w5, w4, [x1]
|
||||
|
||||
/* If we failed to store, try again. */
|
||||
cbnz w5, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i
|
||||
|
||||
2: /* We're done. */
|
||||
str w3, [x0]
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::StoreDataCache(uintptr_t start, uintptr_t end) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x1, x0
|
||||
b.eq 2f
|
||||
|
||||
1: /* Loop, storing each cache line. */
|
||||
dc cvac, x0
|
||||
add x0, x0, #0x40
|
||||
cmp x1, x0
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::FlushDataCache(uintptr_t start, uintptr_t end) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x1, x0
|
||||
b.eq 2f
|
||||
|
||||
1: /* Loop, flushing each cache line. */
|
||||
dc civac, x0
|
||||
add x0, x0, #0x40
|
||||
cmp x1, x0
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::InvalidateDataCache(uintptr_t start, uintptr_t end) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x1, x0
|
||||
b.eq 2f
|
||||
|
||||
1: /* Loop, invalidating each cache line. */
|
||||
dc ivac, x0
|
||||
add x0, x0, #0x40
|
||||
cmp x1, x0
|
||||
b.ne 1b
|
||||
|
||||
2: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory32Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for read failure. */
|
||||
adr x10, 4f
|
||||
|
||||
1: /* Set our return address so that on read failure we continue as though we read -1. */
|
||||
mov x30, x10
|
||||
|
||||
/* Read the word from io. */
|
||||
ldtr w9, [x5]
|
||||
dsb sy
|
||||
nop
|
||||
|
||||
2: /* Restore our return address. */
|
||||
mov x30, x8
|
||||
|
||||
/* Write the value we read. */
|
||||
sttr w9, [x4]
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #4
|
||||
add x5, x5, #4
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
4: /* We failed to read a value, so continue as though we read -1. */
|
||||
mov w9, #0xFFFFFFFF
|
||||
b 2b
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory16Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for read failure. */
|
||||
adr x10, 4f
|
||||
|
||||
1: /* Set our return address so that on read failure we continue as though we read -1. */
|
||||
mov x30, x10
|
||||
|
||||
/* Read the word from io. */
|
||||
ldtrh w9, [x5]
|
||||
dsb sy
|
||||
nop
|
||||
|
||||
2: /* Restore our return address. */
|
||||
mov x30, x8
|
||||
|
||||
/* Write the value we read. */
|
||||
sttrh w9, [x4]
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #2
|
||||
add x5, x5, #2
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
4: /* We failed to read a value, so continue as though we read -1. */
|
||||
mov w9, #0xFFFFFFFF
|
||||
b 2b
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory8Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for read failure. */
|
||||
adr x10, 4f
|
||||
|
||||
1: /* Set our return address so that on read failure we continue as though we read -1. */
|
||||
mov x30, x10
|
||||
|
||||
/* Read the word from io. */
|
||||
ldtrb w9, [x5]
|
||||
dsb sy
|
||||
nop
|
||||
|
||||
2: /* Restore our return address. */
|
||||
mov x30, x8
|
||||
|
||||
/* Write the value we read. */
|
||||
sttrb w9, [x4]
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #1
|
||||
add x5, x5, #1
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
4: /* We failed to read a value, so continue as though we read -1. */
|
||||
mov w9, #0xFFFFFFFF
|
||||
b 2b
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory32Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for failure. */
|
||||
adr x10, 2f
|
||||
|
||||
1: /* Read the word from normal memory. */
|
||||
ldtr w9, [x5]
|
||||
|
||||
/* Set our return address so that on failure we continue. */
|
||||
mov x30, x10
|
||||
|
||||
/* Write the word to io. */
|
||||
sttr w9, [x5]
|
||||
dsb sy
|
||||
|
||||
2: /* Continue. */
|
||||
mov x30, x8
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #4
|
||||
add x5, x5, #4
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory16Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for failure. */
|
||||
adr x10, 2f
|
||||
|
||||
1: /* Read the word from normal memory. */
|
||||
ldtrh w9, [x5]
|
||||
|
||||
/* Set our return address so that on failure we continue. */
|
||||
mov x30, x10
|
||||
|
||||
/* Write the word to io. */
|
||||
sttrh w9, [x5]
|
||||
dsb sy
|
||||
|
||||
2: /* Continue. */
|
||||
mov x30, x8
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #2
|
||||
add x5, x5, #2
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory8Bit(void *dst, const void *src, size_t size) */
|
||||
.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm
|
||||
.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm:
|
||||
/* Check if we have any work to do. */
|
||||
cmp x2, #0
|
||||
b.eq 3f
|
||||
|
||||
/* Save variables in temporary registers. */
|
||||
mov x4, x0
|
||||
mov x5, x1
|
||||
mov x6, x2
|
||||
add x7, x5, x6
|
||||
|
||||
/* Save our return address. */
|
||||
mov x8, x30
|
||||
|
||||
/* Prepare return address for failure. */
|
||||
adr x10, 2f
|
||||
|
||||
1: /* Read the word from normal memory. */
|
||||
ldtrb w9, [x5]
|
||||
|
||||
/* Set our return address so that on failure we continue. */
|
||||
mov x30, x10
|
||||
|
||||
/* Write the word to io. */
|
||||
sttrb w9, [x5]
|
||||
dsb sy
|
||||
|
||||
2: /* Continue. */
|
||||
mov x30, x8
|
||||
|
||||
/* Advance. */
|
||||
add x4, x4, #1
|
||||
add x5, x5, #1
|
||||
cmp x5, x7
|
||||
b.ne 1b
|
||||
|
||||
3: /* We're done! */
|
||||
mov x0, #1
|
||||
ret
|
||||
|
||||
/* ================ All Userspace Access Functions before this line. ================ */
|
||||
|
||||
/* ams::kern::arch::arm64::UserspaceAccessFunctionAreaEnd() */
|
||||
.section .text._ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv
|
||||
.type _ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv:
|
||||
/* NOTE: This is not a real function, and only exists as a label for safety. */
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::svc::CallWaitForAddress64From32() */
|
||||
.section .text._ZN3ams4kern3svc26CallWaitForAddress64From32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc26CallWaitForAddress64From32Ev
|
||||
.type _ZN3ams4kern3svc26CallWaitForAddress64From32Ev, %function
|
||||
_ZN3ams4kern3svc26CallWaitForAddress64From32Ev:
|
||||
/* Save LR + callee-save registers. */
|
||||
str x30, [sp, #-16]!
|
||||
stp x6, x7, [sp, #-16]!
|
||||
|
||||
/* Gather the arguments into correct registers. */
|
||||
/* NOTE: This has to be manually implemented via asm, */
|
||||
/* in order to avoid breaking ABI with pre-19.0.0. */
|
||||
orr x2, x2, x5, lsl#32
|
||||
orr x3, x3, x4, lsl#32
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
bl _ZN3ams4kern3svc22WaitForAddress64From32ENS_3svc7AddressENS2_15ArbitrationTypeEll
|
||||
|
||||
/* Clean up registers. */
|
||||
mov x1, xzr
|
||||
mov x2, xzr
|
||||
mov x3, xzr
|
||||
mov x4, xzr
|
||||
mov x5, xzr
|
||||
|
||||
ldp x6, x7, [sp], #0x10
|
||||
ldr x30, [sp], #0x10
|
||||
ret
|
||||
|
||||
/* ams::kern::svc::CallWaitForAddress64() */
|
||||
.section .text._ZN3ams4kern3svc20CallWaitForAddress64Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc20CallWaitForAddress64Ev
|
||||
.type _ZN3ams4kern3svc20CallWaitForAddress64Ev, %function
|
||||
_ZN3ams4kern3svc20CallWaitForAddress64Ev:
|
||||
/* Save LR + FP. */
|
||||
stp x29, x30, [sp, #-16]!
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
bl _ZN3ams4kern3svc22WaitForAddress64From32ENS_3svc7AddressENS2_15ArbitrationTypeEll
|
||||
|
||||
/* Clean up registers. */
|
||||
mov x1, xzr
|
||||
mov x2, xzr
|
||||
mov x3, xzr
|
||||
mov x4, xzr
|
||||
mov x5, xzr
|
||||
mov x6, xzr
|
||||
mov x7, xzr
|
||||
|
||||
ldp x29, x30, [sp], #0x10
|
||||
ret
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::svc::CallCallSecureMonitor64From32() */
|
||||
.section .text._ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev
|
||||
.type _ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev, %function
|
||||
_ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev:
|
||||
/* Secure Monitor 64-from-32 ABI is not supported. */
|
||||
mov x0, xzr
|
||||
mov x1, xzr
|
||||
mov x2, xzr
|
||||
mov x3, xzr
|
||||
mov x4, xzr
|
||||
mov x5, xzr
|
||||
mov x6, xzr
|
||||
mov x7, xzr
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere/kern_select_assembly_macros.h>
|
||||
|
||||
/* ams::kern::svc::CallReturnFromException64(Result result) */
|
||||
.section .text._ZN3ams4kern3svc25CallReturnFromException64Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc25CallReturnFromException64Ev
|
||||
.type _ZN3ams4kern3svc25CallReturnFromException64Ev, %function
|
||||
_ZN3ams4kern3svc25CallReturnFromException64Ev:
|
||||
/* Save registers the SVC entry handler didn't. */
|
||||
stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)]
|
||||
str x19, [sp, #(EXCEPTION_CONTEXT_X19)]
|
||||
stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)]
|
||||
stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)]
|
||||
stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)]
|
||||
stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)]
|
||||
stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)]
|
||||
|
||||
/* Call ams::kern::arch::arm64::ReturnFromException(result). */
|
||||
bl _ZN3ams4kern4arch5arm6419ReturnFromExceptionENS_6ResultE
|
||||
|
||||
0: /* We should never reach this point. */
|
||||
b 0b
|
||||
|
||||
/* ams::kern::svc::CallReturnFromException64From32(Result result) */
|
||||
.section .text._ZN3ams4kern3svc31CallReturnFromException64From32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc31CallReturnFromException64From32Ev
|
||||
.type _ZN3ams4kern3svc31CallReturnFromException64From32Ev, %function
|
||||
_ZN3ams4kern3svc31CallReturnFromException64From32Ev:
|
||||
/* Save registers the SVC entry handler didn't. */
|
||||
/* ... */
|
||||
|
||||
/* Call ams::kern::arch::arm64::ReturnFromException(result). */
|
||||
bl _ZN3ams4kern4arch5arm6419ReturnFromExceptionENS_6ResultE
|
||||
|
||||
0: /* We should never reach this point. */
|
||||
b 0b
|
||||
|
||||
|
||||
/* ams::kern::svc::RestoreContext(uintptr_t sp) */
|
||||
.section .text._ZN3ams4kern3svc14RestoreContextEm, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc14RestoreContextEm
|
||||
.type _ZN3ams4kern3svc14RestoreContextEm, %function
|
||||
_ZN3ams4kern3svc14RestoreContextEm:
|
||||
/* Set the stack pointer, set daif. */
|
||||
mov sp, x0
|
||||
msr daifset, #2
|
||||
|
||||
0: /* We should handle DPC. */
|
||||
/* Check the dpc flags. */
|
||||
ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)]
|
||||
cbz w8, 1f
|
||||
|
||||
/* We have DPC to do! */
|
||||
/* Save registers and call ams::kern::KDpcManager::HandleDpc(). */
|
||||
sub sp, sp, #0x40
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
bl _ZN3ams4kern11KDpcManager9HandleDpcEv
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
add sp, sp, #0x40
|
||||
b 0b
|
||||
|
||||
1: /* We're done with DPC, and should return from the svc. */
|
||||
|
||||
/* Get our exception flags. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* Clear in-svc, in-user-exception, and needs-fpu-restore flags. */
|
||||
and w10, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED))
|
||||
and w10, w10, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC))
|
||||
and w10, w10, #(~(THREAD_EXCEPTION_FLAG_IS_IN_USERMODE_EXCEPTION_HANDLER))
|
||||
strb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* If we don't need to restore the fpu, skip restoring it. */
|
||||
tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 3f
|
||||
|
||||
/* Enable and restore the fpu. */
|
||||
ENABLE_AND_RESTORE_FPU(x10, x8, x9, w8, w9, 2, 3)
|
||||
|
||||
/* Restore registers. */
|
||||
ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
|
||||
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */
|
||||
orr x10, x10, #(1 << 21)
|
||||
#endif
|
||||
|
||||
msr sp_el0, x8
|
||||
msr elr_el1, x9
|
||||
msr spsr_el1, x10
|
||||
msr tpidr_el0, x11
|
||||
ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)]
|
||||
ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)]
|
||||
ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)]
|
||||
ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)]
|
||||
ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)]
|
||||
ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)]
|
||||
ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)]
|
||||
|
||||
/* Return. */
|
||||
add sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
ERET_WITH_SPECULATION_BARRIER
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::svc {
|
||||
|
||||
void TraceSvcEntry(const u64 *data) {
|
||||
MESOSPHERE_KTRACE_SVC_ENTRY(GetCurrentThread().GetSvcId(), data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
||||
}
|
||||
|
||||
void TraceSvcExit(const u64 *data) {
|
||||
MESOSPHERE_KTRACE_SVC_EXIT(GetCurrentThread().GetSvcId(), data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere/kern_build_config.hpp>
|
||||
#include <mesosphere/kern_select_assembly_macros.h>
|
||||
|
||||
/* ams::kern::arch::arm64::SvcHandler64() */
|
||||
.section .text._ZN3ams4kern4arch5arm6412SvcHandler64Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6412SvcHandler64Ev
|
||||
.type _ZN3ams4kern4arch5arm6412SvcHandler64Ev, %function
|
||||
_ZN3ams4kern4arch5arm6412SvcHandler64Ev:
|
||||
/* Create a KExceptionContext for the exception. */
|
||||
sub sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
|
||||
/* Save registers needed for ReturnFromException */
|
||||
stp x9, x10, [sp, #(EXCEPTION_CONTEXT_X9_X10)]
|
||||
str x11, [sp, #(EXCEPTION_CONTEXT_X11)]
|
||||
str x18, [sp, #(EXCEPTION_CONTEXT_X18)]
|
||||
|
||||
mrs x8, sp_el0
|
||||
mrs x9, elr_el1
|
||||
mrs x10, spsr_el1
|
||||
mrs x11, tpidr_el0
|
||||
ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)]
|
||||
|
||||
/* Save callee-saved registers. */
|
||||
stp x19, x20, [sp, #(EXCEPTION_CONTEXT_X19_X20)]
|
||||
stp x21, x22, [sp, #(EXCEPTION_CONTEXT_X21_X22)]
|
||||
stp x23, x24, [sp, #(EXCEPTION_CONTEXT_X23_X24)]
|
||||
stp x25, x26, [sp, #(EXCEPTION_CONTEXT_X25_X26)]
|
||||
stp x27, x28, [sp, #(EXCEPTION_CONTEXT_X27_X28)]
|
||||
|
||||
/* Save miscellaneous registers. */
|
||||
stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
stp x29, x30, [sp, #(EXCEPTION_CONTEXT_X29_X30)]
|
||||
stp x8, x9, [sp, #(EXCEPTION_CONTEXT_SP_PC)]
|
||||
stp x10, x11, [sp, #(EXCEPTION_CONTEXT_PSR_TPIDR)]
|
||||
|
||||
/* Check if the SVC index is out of range. */
|
||||
mrs x8, esr_el1
|
||||
and x8, x8, #0xFF
|
||||
cmp x8, #(AMS_KERN_NUM_SUPERVISOR_CALLS)
|
||||
b.ge 3f
|
||||
|
||||
/* Check the specific SVC permission bit for allowal. */
|
||||
mov x9, sp
|
||||
add x9, x9, x8, lsr#3
|
||||
ldrb w9, [x9, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_SVC_PERMISSION)]
|
||||
and x10, x8, #0x7
|
||||
lsr x10, x9, x10
|
||||
tst x10, #1
|
||||
b.eq 3f
|
||||
|
||||
/* Check if our disable count allows us to call SVCs. */
|
||||
mrs x10, tpidrro_el0
|
||||
add x10, x10, #(THREAD_LOCAL_REGION_DISABLE_COUNT)
|
||||
ldtrh w10, [x10]
|
||||
cbz w10, 1f
|
||||
|
||||
/* It might not, so check the stack params to see if we must not allow the SVC. */
|
||||
ldrb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_IS_PINNED)]
|
||||
cbz w10, 3f
|
||||
|
||||
1: /* We can call the SVC. */
|
||||
adr x10, _ZN3ams4kern3svc10SvcTable64E
|
||||
ldr x11, [x10, x8, lsl#3]
|
||||
cbz x11, 3f
|
||||
|
||||
/* Note that we're calling the SVC. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
orr w9, w9, #(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC)
|
||||
strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CURRENT_SVC_ID)]
|
||||
|
||||
/* If we should, trace the svc entry. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_TRACING)
|
||||
sub sp, sp, #0x50
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
str x11, [sp, #(8 * 8)]
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern3svc13TraceSvcEntryEPKm
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
ldr x11, [sp, #(8 * 8)]
|
||||
add sp, sp, #0x50
|
||||
#endif
|
||||
|
||||
/* Invoke the SVC handler. */
|
||||
msr daifclr, #2
|
||||
blr x11
|
||||
msr daifset, #2
|
||||
|
||||
2: /* We completed the SVC, and we should handle DPC. */
|
||||
/* Check the dpc flags. */
|
||||
ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)]
|
||||
cbz w8, 5f
|
||||
|
||||
/* We have DPC to do! */
|
||||
/* Save registers and call ams::kern::KDpcManager::HandleDpc(). */
|
||||
sub sp, sp, #0x40
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
bl _ZN3ams4kern11KDpcManager9HandleDpcEv
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
add sp, sp, #0x40
|
||||
b 2b
|
||||
|
||||
3: /* Invalid SVC. */
|
||||
/* Setup the context to call into HandleException. */
|
||||
stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X16_X17)]
|
||||
str x19, [sp, #(EXCEPTION_CONTEXT_X19)]
|
||||
stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)]
|
||||
stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)]
|
||||
stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)]
|
||||
stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)]
|
||||
stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)]
|
||||
|
||||
/* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE
|
||||
|
||||
/* If we don't need to restore the fpu, skip restoring it. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 4f
|
||||
|
||||
/* Clear the needs-fpu-restore flag. */
|
||||
and w9, w9, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED)
|
||||
strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* Enable and restore the fpu. */
|
||||
ENABLE_AND_RESTORE_FPU64(x10, x8, x9, w8, w9)
|
||||
|
||||
4: /* Restore registers. */
|
||||
ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
|
||||
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
|
||||
bic x10, x10, #(1 << 21)
|
||||
#endif
|
||||
|
||||
msr sp_el0, x8
|
||||
msr elr_el1, x9
|
||||
msr spsr_el1, x10
|
||||
msr tpidr_el0, x11
|
||||
ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)]
|
||||
ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)]
|
||||
ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)]
|
||||
ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)]
|
||||
ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)]
|
||||
ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)]
|
||||
ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)]
|
||||
|
||||
/* Return. */
|
||||
add sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
ERET_WITH_SPECULATION_BARRIER
|
||||
|
||||
5: /* Return from SVC. */
|
||||
|
||||
/* If we should, trace the svc exit. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_TRACING)
|
||||
sub sp, sp, #0x40
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern3svc12TraceSvcExitEPKm
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
add sp, sp, #0x40
|
||||
#endif
|
||||
|
||||
/* Get our exception flags. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* Clear in-svc and needs-fpu-restore flags. */
|
||||
and w8, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED))
|
||||
and w8, w8, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC))
|
||||
strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* If we don't need to restore the fpu, skip restoring it. */
|
||||
tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 7f
|
||||
|
||||
/* If we need to restore the fpu, check if we need to do a full restore. */
|
||||
tbnz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 6f
|
||||
|
||||
/* Enable the fpu. */
|
||||
ENABLE_FPU(x8)
|
||||
|
||||
/* Get the thread context and restore fpsr/fpcr. */
|
||||
GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(x10, x8, x9, w8, w9)
|
||||
|
||||
/* Restore callee-saved registers to 64-bit fpu. */
|
||||
RESTORE_FPU64_CALLEE_SAVE_REGISTERS(x10)
|
||||
|
||||
/* Clear caller-saved registers to 64-bit fpu. */
|
||||
movi v0.2d, #0
|
||||
movi v1.2d, #0
|
||||
movi v2.2d, #0
|
||||
movi v3.2d, #0
|
||||
movi v4.2d, #0
|
||||
movi v5.2d, #0
|
||||
movi v6.2d, #0
|
||||
movi v7.2d, #0
|
||||
movi v16.2d, #0
|
||||
movi v17.2d, #0
|
||||
movi v18.2d, #0
|
||||
movi v19.2d, #0
|
||||
movi v20.2d, #0
|
||||
movi v21.2d, #0
|
||||
movi v22.2d, #0
|
||||
movi v23.2d, #0
|
||||
movi v24.2d, #0
|
||||
movi v25.2d, #0
|
||||
movi v26.2d, #0
|
||||
movi v27.2d, #0
|
||||
movi v28.2d, #0
|
||||
movi v29.2d, #0
|
||||
movi v30.2d, #0
|
||||
movi v31.2d, #0
|
||||
b 7f
|
||||
|
||||
6: /* We need to do a full fpu restore. */
|
||||
ENABLE_AND_RESTORE_FPU64(x10, x8, x9, w8, w9)
|
||||
|
||||
7: /* Restore registers. */
|
||||
ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
|
||||
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
ldr x18, [sp, #(EXCEPTION_CONTEXT_X18)]
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
|
||||
bic x10, x10, #(1 << 21)
|
||||
#endif
|
||||
|
||||
msr sp_el0, x8
|
||||
msr elr_el1, x9
|
||||
msr spsr_el1, x10
|
||||
msr tpidr_el0, x11
|
||||
|
||||
/* Clear registers. */
|
||||
mov x8, xzr
|
||||
mov x9, xzr
|
||||
mov x10, xzr
|
||||
mov x11, xzr
|
||||
mov x12, xzr
|
||||
mov x13, xzr
|
||||
mov x14, xzr
|
||||
mov x15, xzr
|
||||
mov x16, xzr
|
||||
mov x17, xzr
|
||||
|
||||
/* Return. */
|
||||
add sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
ERET_WITH_SPECULATION_BARRIER
|
||||
|
||||
/* ams::kern::arch::arm64::SvcHandler32() */
|
||||
.section .text._ZN3ams4kern4arch5arm6412SvcHandler32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern4arch5arm6412SvcHandler32Ev
|
||||
.type _ZN3ams4kern4arch5arm6412SvcHandler32Ev, %function
|
||||
_ZN3ams4kern4arch5arm6412SvcHandler32Ev:
|
||||
/* Ensure that our registers are 32-bit. */
|
||||
mov w0, w0
|
||||
mov w1, w1
|
||||
mov w2, w2
|
||||
mov w3, w3
|
||||
mov w4, w4
|
||||
mov w5, w5
|
||||
mov w6, w6
|
||||
mov w7, w7
|
||||
|
||||
/* Create a KExceptionContext for the exception. */
|
||||
sub sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
|
||||
/* Save system registers */
|
||||
mrs x17, elr_el1
|
||||
mrs x20, spsr_el1
|
||||
mrs x19, tpidr_el0
|
||||
ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)]
|
||||
stp x17, x20, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
str x19, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
|
||||
/* Save registers. */
|
||||
stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
stp x14, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
|
||||
/* Check if the SVC index is out of range. */
|
||||
mrs x8, esr_el1
|
||||
and x8, x8, #0xFF
|
||||
cmp x8, #(AMS_KERN_NUM_SUPERVISOR_CALLS)
|
||||
b.ge 3f
|
||||
|
||||
/* Check the specific SVC permission bit for allowal. */
|
||||
mov x9, sp
|
||||
add x9, x9, x8, lsr#3
|
||||
ldrb w9, [x9, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_SVC_PERMISSION)]
|
||||
and x10, x8, #0x7
|
||||
lsr x10, x9, x10
|
||||
tst x10, #1
|
||||
b.eq 3f
|
||||
|
||||
/* Check if our disable count allows us to call SVCs. */
|
||||
mrs x10, tpidrro_el0
|
||||
add x10, x10, #(THREAD_LOCAL_REGION_DISABLE_COUNT)
|
||||
ldtrh w10, [x10]
|
||||
cbz w10, 1f
|
||||
|
||||
/* It might not, so check the stack params to see if we must not allow the SVC. */
|
||||
ldrb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_IS_PINNED)]
|
||||
cbz w10, 3f
|
||||
|
||||
1: /* We can call the SVC. */
|
||||
adr x10, _ZN3ams4kern3svc16SvcTable64From32E
|
||||
ldr x11, [x10, x8, lsl#3]
|
||||
cbz x11, 3f
|
||||
|
||||
|
||||
/* Note that we're calling the SVC. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
orr w9, w9, #(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC)
|
||||
strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CURRENT_SVC_ID)]
|
||||
|
||||
/* If we should, trace the svc entry. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_TRACING)
|
||||
sub sp, sp, #0x50
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
str x11, [sp, #(8 * 8)]
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern3svc13TraceSvcEntryEPKm
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
ldr x11, [sp, #(8 * 8)]
|
||||
add sp, sp, #0x50
|
||||
#endif
|
||||
|
||||
/* Invoke the SVC handler. */
|
||||
msr daifclr, #2
|
||||
blr x11
|
||||
msr daifset, #2
|
||||
|
||||
2: /* We completed the SVC, and we should handle DPC. */
|
||||
/* Check the dpc flags. */
|
||||
ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)]
|
||||
cbz w8, 5f
|
||||
|
||||
/* We have DPC to do! */
|
||||
/* Save registers and call ams::kern::KDpcManager::HandleDpc(). */
|
||||
sub sp, sp, #0x20
|
||||
stp w0, w1, [sp, #(4 * 0)]
|
||||
stp w2, w3, [sp, #(4 * 2)]
|
||||
stp w4, w5, [sp, #(4 * 4)]
|
||||
stp w6, w7, [sp, #(4 * 6)]
|
||||
bl _ZN3ams4kern11KDpcManager9HandleDpcEv
|
||||
ldp w0, w1, [sp, #(4 * 0)]
|
||||
ldp w2, w3, [sp, #(4 * 2)]
|
||||
ldp w4, w5, [sp, #(4 * 4)]
|
||||
ldp w6, w7, [sp, #(4 * 6)]
|
||||
add sp, sp, #0x20
|
||||
b 2b
|
||||
|
||||
3: /* Invalid SVC. */
|
||||
/* Setup the context to call into HandleException. */
|
||||
stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X16_X17)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X18_X19)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X20_X21)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X22_X23)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X24_X25)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X26_X27)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X28_X29)]
|
||||
stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
|
||||
|
||||
/* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE
|
||||
|
||||
/* If we don't need to restore the fpu, skip restoring it. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 4f
|
||||
|
||||
/* Clear the needs-fpu-restore flag. */
|
||||
and w9, w9, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED)
|
||||
strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* Enable and restore the fpu. */
|
||||
ENABLE_AND_RESTORE_FPU32(x10, x8, x9, w8, w9)
|
||||
|
||||
4: /* Restore registers. */
|
||||
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
|
||||
bic x10, x10, #(1 << 21)
|
||||
#endif
|
||||
|
||||
msr elr_el1, x9
|
||||
msr spsr_el1, x10
|
||||
msr tpidr_el0, x11
|
||||
ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)]
|
||||
ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)]
|
||||
ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)]
|
||||
ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)]
|
||||
ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
|
||||
/* Return. */
|
||||
add sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
ERET_WITH_SPECULATION_BARRIER
|
||||
|
||||
5: /* Return from SVC. */
|
||||
|
||||
/* If we should, trace the svc exit. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_TRACING)
|
||||
sub sp, sp, #0x40
|
||||
stp x0, x1, [sp, #(8 * 0)]
|
||||
stp x2, x3, [sp, #(8 * 2)]
|
||||
stp x4, x5, [sp, #(8 * 4)]
|
||||
stp x6, x7, [sp, #(8 * 6)]
|
||||
mov x0, sp
|
||||
bl _ZN3ams4kern3svc12TraceSvcExitEPKm
|
||||
ldp x0, x1, [sp, #(8 * 0)]
|
||||
ldp x2, x3, [sp, #(8 * 2)]
|
||||
ldp x4, x5, [sp, #(8 * 4)]
|
||||
ldp x6, x7, [sp, #(8 * 6)]
|
||||
add sp, sp, #0x40
|
||||
#endif
|
||||
|
||||
/* Get our exception flags. */
|
||||
ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* Clear in-svc and needs-fpu-restore flags. */
|
||||
and w8, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED))
|
||||
and w8, w8, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC))
|
||||
strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]
|
||||
|
||||
/* If we don't need to restore the fpu, skip restoring it. */
|
||||
tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 7f
|
||||
|
||||
/* If we need to restore the fpu, check if we need to do a full restore. */
|
||||
tbnz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 6f
|
||||
|
||||
/* Enable the fpu. */
|
||||
ENABLE_FPU(x8)
|
||||
|
||||
/* Get the thread context and restore fpsr/fpcr. */
|
||||
GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(x10, x8, x9, w8, w9)
|
||||
|
||||
/* Restore callee-saved registers to 32-bit fpu. */
|
||||
RESTORE_FPU32_CALLEE_SAVE_REGISTERS(x10)
|
||||
|
||||
/* Clear caller-saved registers to 32-bit fpu. */
|
||||
movi v0.2d, #0
|
||||
movi v1.2d, #0
|
||||
movi v2.2d, #0
|
||||
movi v3.2d, #0
|
||||
movi v8.2d, #0
|
||||
movi v9.2d, #0
|
||||
movi v10.2d, #0
|
||||
movi v11.2d, #0
|
||||
movi v12.2d, #0
|
||||
movi v13.2d, #0
|
||||
movi v14.2d, #0
|
||||
movi v15.2d, #0
|
||||
b 7f
|
||||
|
||||
6: /* We need to do a full fpu restore. */
|
||||
ENABLE_AND_RESTORE_FPU32(x10, x8, x9, w8, w9)
|
||||
|
||||
7: /* Restore registers. */
|
||||
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
|
||||
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
|
||||
bic x10, x10, #(1 << 21)
|
||||
#endif
|
||||
|
||||
msr elr_el1, x9
|
||||
msr spsr_el1, x10
|
||||
msr tpidr_el0, x11
|
||||
ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)]
|
||||
ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)]
|
||||
ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)]
|
||||
ldp x14, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
|
||||
|
||||
/* Return. */
|
||||
add sp, sp, #(EXCEPTION_CONTEXT_SIZE)
|
||||
ERET_WITH_SPECULATION_BARRIER
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::svc::CallSendSyncRequestLight64() */
|
||||
.section .text._ZN3ams4kern3svc26CallSendSyncRequestLight64Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc26CallSendSyncRequestLight64Ev
|
||||
.type _ZN3ams4kern3svc26CallSendSyncRequestLight64Ev, %function
|
||||
_ZN3ams4kern3svc26CallSendSyncRequestLight64Ev:
|
||||
/* Allocate space for the light ipc data. */
|
||||
sub sp, sp, #(4 * 8)
|
||||
|
||||
/* Store the light ipc data. */
|
||||
stp w1, w2, [sp, #(4 * 0)]
|
||||
stp w3, w4, [sp, #(4 * 2)]
|
||||
stp w5, w6, [sp, #(4 * 4)]
|
||||
str w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
mov x1, sp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
bl _ZN3ams4kern3svc22SendSyncRequestLight64EjPj
|
||||
ldp x29, x30, [sp], #16
|
||||
|
||||
/* Load the light ipc data. */
|
||||
ldp w1, w2, [sp, #(4 * 0)]
|
||||
ldp w3, w4, [sp, #(4 * 2)]
|
||||
ldp w5, w6, [sp, #(4 * 4)]
|
||||
ldr w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Free the stack space for the light ipc data. */
|
||||
add sp, sp, #(4 * 8)
|
||||
|
||||
ret
|
||||
|
||||
/* ams::kern::svc::CallSendSyncRequestLight64From32() */
|
||||
.section .text._ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev
|
||||
.type _ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev, %function
|
||||
_ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev:
|
||||
/* Allocate space for the light ipc data. */
|
||||
sub sp, sp, #(4 * 8)
|
||||
|
||||
/* Store the light ipc data. */
|
||||
stp w1, w2, [sp, #(4 * 0)]
|
||||
stp w3, w4, [sp, #(4 * 2)]
|
||||
stp w5, w6, [sp, #(4 * 4)]
|
||||
str w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
mov x1, sp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
bl _ZN3ams4kern3svc28SendSyncRequestLight64From32EjPj
|
||||
ldp x29, x30, [sp], #16
|
||||
|
||||
/* Load the light ipc data. */
|
||||
ldp w1, w2, [sp, #(4 * 0)]
|
||||
ldp w3, w4, [sp, #(4 * 2)]
|
||||
ldp w5, w6, [sp, #(4 * 4)]
|
||||
ldr w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Free the stack space for the light ipc data. */
|
||||
add sp, sp, #(4 * 8)
|
||||
|
||||
ret
|
||||
|
||||
/* ams::kern::svc::CallReplyAndReceiveLight64() */
|
||||
.section .text._ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev
|
||||
.type _ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev, %function
|
||||
_ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev:
|
||||
/* Allocate space for the light ipc data. */
|
||||
sub sp, sp, #(4 * 8)
|
||||
|
||||
/* Store the light ipc data. */
|
||||
stp w1, w2, [sp, #(4 * 0)]
|
||||
stp w3, w4, [sp, #(4 * 2)]
|
||||
stp w5, w6, [sp, #(4 * 4)]
|
||||
str w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
mov x1, sp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
bl _ZN3ams4kern3svc22ReplyAndReceiveLight64EjPj
|
||||
ldp x29, x30, [sp], #16
|
||||
|
||||
/* Load the light ipc data. */
|
||||
ldp w1, w2, [sp, #(4 * 0)]
|
||||
ldp w3, w4, [sp, #(4 * 2)]
|
||||
ldp w5, w6, [sp, #(4 * 4)]
|
||||
ldr w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Free the stack space for the light ipc data. */
|
||||
add sp, sp, #(4 * 8)
|
||||
|
||||
ret
|
||||
|
||||
/* ams::kern::svc::CallReplyAndReceiveLight64From32() */
|
||||
.section .text._ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev, "ax", %progbits
|
||||
.global _ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev
|
||||
.type _ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev, %function
|
||||
_ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev:
|
||||
/* Allocate space for the light ipc data. */
|
||||
sub sp, sp, #(4 * 8)
|
||||
|
||||
/* Store the light ipc data. */
|
||||
stp w1, w2, [sp, #(4 * 0)]
|
||||
stp w3, w4, [sp, #(4 * 2)]
|
||||
stp w5, w6, [sp, #(4 * 4)]
|
||||
str w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Invoke the svc handler. */
|
||||
mov x1, sp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
bl _ZN3ams4kern3svc28ReplyAndReceiveLight64From32EjPj
|
||||
ldp x29, x30, [sp], #16
|
||||
|
||||
/* Load the light ipc data. */
|
||||
ldp w1, w2, [sp, #(4 * 0)]
|
||||
ldp w3, w4, [sp, #(4 * 2)]
|
||||
ldp w5, w6, [sp, #(4 * 4)]
|
||||
ldr w7, [sp, #(4 * 6)]
|
||||
|
||||
/* Free the stack space for the light ipc data. */
|
||||
add sp, sp, #(4 * 8)
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#ifdef MESOSPHERE_USE_STUBBED_SVC_TABLES
|
||||
#include <mesosphere/kern_debug_log.hpp>
|
||||
#endif
|
||||
|
||||
#include <mesosphere/svc/kern_svc_tables.hpp>
|
||||
#include <vapours/svc/svc_codegen.hpp>
|
||||
|
||||
namespace ams::kern::svc {
|
||||
|
||||
/* Declare special prototypes for the light ipc handlers. */
|
||||
void CallSendSyncRequestLight64();
|
||||
void CallSendSyncRequestLight64From32();
|
||||
|
||||
void CallReplyAndReceiveLight64();
|
||||
void CallReplyAndReceiveLight64From32();
|
||||
|
||||
/* Declare special prototypes for ReturnFromException. */
|
||||
void CallReturnFromException64();
|
||||
void CallReturnFromException64From32();
|
||||
|
||||
/* Declare special prototype for (unsupported) CallCallSecureMonitor64From32. */
|
||||
void CallCallSecureMonitor64From32();
|
||||
|
||||
/* Declare special prototypes for WaitForAddress. */
|
||||
void CallWaitForAddress64();
|
||||
void CallWaitForAddress64From32();
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef MESOSPHERE_USE_STUBBED_SVC_TABLES
|
||||
#define DECLARE_SVC_STRUCT(ID, RETURN_TYPE, NAME, ...) \
|
||||
class NAME { \
|
||||
private: \
|
||||
using Impl = ::ams::svc::codegen::KernelSvcWrapper<::ams::kern::svc::NAME##64, ::ams::kern::svc::NAME##64From32>; \
|
||||
public: \
|
||||
static NOINLINE void Call64() { return Impl::Call64(); } \
|
||||
static NOINLINE void Call64From32() { return Impl::Call64From32(); } \
|
||||
};
|
||||
#else
|
||||
#define DECLARE_SVC_STRUCT(ID, RETURN_TYPE, NAME, ...) \
|
||||
class NAME { \
|
||||
public: \
|
||||
static NOINLINE void Call64() { MESOSPHERE_PANIC("Stubbed Svc"#NAME"64 was called"); } \
|
||||
static NOINLINE void Call64From32() { MESOSPHERE_PANIC("Stubbed Svc"#NAME"64From32 was called"); } \
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/* Set omit-frame-pointer to prevent GCC from emitting MOV X29, SP instructions. */
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
#pragma GCC optimize ("omit-frame-pointer")
|
||||
|
||||
AMS_SVC_FOREACH_KERN_DEFINITION(DECLARE_SVC_STRUCT, _)
|
||||
|
||||
#pragma GCC pop_options
|
||||
|
||||
constexpr const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64From32Impl = [] {
|
||||
std::array<SvcTableEntry, NumSupervisorCalls> table = {};
|
||||
|
||||
#define AMS_KERN_SVC_SET_TABLE_ENTRY(ID, RETURN_TYPE, NAME, ...) \
|
||||
if (table[ID] == nullptr) { table[ID] = NAME::Call64From32; }
|
||||
AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_SET_TABLE_ENTRY, _)
|
||||
#undef AMS_KERN_SVC_SET_TABLE_ENTRY
|
||||
|
||||
table[svc::SvcId_SendSyncRequestLight] = CallSendSyncRequestLight64From32;
|
||||
table[svc::SvcId_ReplyAndReceiveLight] = CallReplyAndReceiveLight64From32;
|
||||
|
||||
table[svc::SvcId_ReturnFromException] = CallReturnFromException64From32;
|
||||
|
||||
table[svc::SvcId_CallSecureMonitor] = CallCallSecureMonitor64From32;
|
||||
|
||||
table[svc::SvcId_WaitForAddress] = CallWaitForAddress64From32;
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
constexpr const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64Impl = [] {
|
||||
std::array<SvcTableEntry, NumSupervisorCalls> table = {};
|
||||
|
||||
#define AMS_KERN_SVC_SET_TABLE_ENTRY(ID, RETURN_TYPE, NAME, ...) \
|
||||
if (table[ID] == nullptr) { table[ID] = NAME::Call64; }
|
||||
AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_SET_TABLE_ENTRY, _)
|
||||
#undef AMS_KERN_SVC_SET_TABLE_ENTRY
|
||||
|
||||
table[svc::SvcId_SendSyncRequestLight] = CallSendSyncRequestLight64;
|
||||
table[svc::SvcId_ReplyAndReceiveLight] = CallReplyAndReceiveLight64;
|
||||
|
||||
table[svc::SvcId_ReturnFromException] = CallReturnFromException64;
|
||||
|
||||
table[svc::SvcId_WaitForAddress] = CallWaitForAddress64;
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
constexpr bool IsValidSvcTable(const std::array<SvcTableEntry, NumSupervisorCalls> &table) {
|
||||
for (size_t i = 0; i < NumSupervisorCalls; i++) {
|
||||
if (table[i] != nullptr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static_assert(IsValidSvcTable(SvcTable64Impl));
|
||||
static_assert(IsValidSvcTable(SvcTable64From32Impl));
|
||||
|
||||
}
|
||||
|
||||
constinit const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64 = SvcTable64Impl;
|
||||
|
||||
constinit const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64From32 = SvcTable64From32Impl;
|
||||
|
||||
void PatchSvcTableEntry(const SvcTableEntry *table, u32 id, SvcTableEntry entry);
|
||||
|
||||
namespace {
|
||||
|
||||
/* NOTE: Although the SVC tables are constants, our global constructor will run before .rodata is protected R--. */
|
||||
class SvcTablePatcher {
|
||||
private:
|
||||
using SvcTable = std::array<SvcTableEntry, NumSupervisorCalls>;
|
||||
private:
|
||||
static SvcTablePatcher s_instance;
|
||||
private:
|
||||
ALWAYS_INLINE const SvcTableEntry *GetTableData(const SvcTable *table) {
|
||||
if (table != nullptr) {
|
||||
return table->data();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NOINLINE void PatchTables(const SvcTableEntry *table_64, const SvcTableEntry *table_64_from_32) {
|
||||
/* Get the target firmware. */
|
||||
const auto target_fw = kern::GetTargetFirmware();
|
||||
|
||||
/* 10.0.0 broke the ABI for QueryIoMapping, and renamed it to QueryMemoryMapping. */
|
||||
if (target_fw < TargetFirmware_10_0_0) {
|
||||
if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_QueryMemoryMapping, LegacyQueryIoMapping::Call64); }
|
||||
if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_QueryMemoryMapping, LegacyQueryIoMapping::Call64From32); }
|
||||
}
|
||||
|
||||
/* 6.0.0 broke the ABI for GetFutureThreadInfo, and renamed it to GetDebugFutureThreadInfo. */
|
||||
if (target_fw < TargetFirmware_6_0_0) {
|
||||
static_assert(svc::SvcId_GetDebugFutureThreadInfo == svc::SvcId_LegacyGetFutureThreadInfo);
|
||||
if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_GetDebugFutureThreadInfo, LegacyGetFutureThreadInfo::Call64); }
|
||||
if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_GetDebugFutureThreadInfo, LegacyGetFutureThreadInfo::Call64From32); }
|
||||
}
|
||||
|
||||
/* 3.0.0 broke the ABI for ContinueDebugEvent. */
|
||||
if (target_fw < TargetFirmware_3_0_0) {
|
||||
if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_ContinueDebugEvent, LegacyContinueDebugEvent::Call64); }
|
||||
if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_ContinueDebugEvent, LegacyContinueDebugEvent::Call64From32); }
|
||||
}
|
||||
}
|
||||
public:
|
||||
SvcTablePatcher(const SvcTable *table_64, const SvcTable *table_64_from_32) {
|
||||
PatchTables(GetTableData(table_64), GetTableData(table_64_from_32));
|
||||
}
|
||||
};
|
||||
|
||||
SvcTablePatcher SvcTablePatcher::s_instance(std::addressof(SvcTable64), std::addressof(SvcTable64From32));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define ATOMICS_AP0_TRIGGER 0x000
|
||||
#define ATOMICS_AP0_RESULT(id) (0xc00 + id * 4)
|
||||
|
||||
#define TRIGGER_CMD_GET 4
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
/* Message Flags */
|
||||
#define BPMP_MSG_DO_ACK (1 << 0)
|
||||
#define BPMP_MSG_RING_DOORBELL (1 << 1)
|
||||
|
||||
/* Messages */
|
||||
#define MRQ_PING 0
|
||||
#define MRQ_ENABLE_SUSPEND 17
|
||||
#define MRQ_CPU_PMIC_SELECT 28
|
||||
|
||||
/* BPMP Power states. */
|
||||
#define TEGRA_BPMP_PM_CC1 9
|
||||
#define TEGRA_BPMP_PM_CC4 12
|
||||
#define TEGRA_BPMP_PM_CC6 14
|
||||
#define TEGRA_BPMP_PM_CC7 15
|
||||
#define TEGRA_BPMP_PM_SC1 17
|
||||
#define TEGRA_BPMP_PM_SC2 18
|
||||
#define TEGRA_BPMP_PM_SC3 19
|
||||
#define TEGRA_BPMP_PM_SC4 20
|
||||
#define TEGRA_BPMP_PM_SC7 23
|
||||
|
||||
/* Channel states. */
|
||||
#define CH_MASK(ch) (0x3u << ((ch) * 2))
|
||||
#define SL_SIGL(ch) (0x0u << ((ch) * 2))
|
||||
#define SL_QUED(ch) (0x1u << ((ch) * 2))
|
||||
#define MA_FREE(ch) (0x2u << ((ch) * 2))
|
||||
#define MA_ACKD(ch) (0x3u << ((ch) * 2))
|
||||
|
||||
constexpr inline int MessageSize = 0x80;
|
||||
constexpr inline int MessageDataSizeMax = 0x78;
|
||||
|
||||
struct MailboxData {
|
||||
s32 code;
|
||||
s32 flags;
|
||||
u8 data[MessageDataSizeMax];
|
||||
};
|
||||
|
||||
static_assert(ams::util::is_pod<MailboxData>::value);
|
||||
static_assert(sizeof(MailboxData) == MessageSize);
|
||||
|
||||
struct ChannelData {
|
||||
MailboxData *ib;
|
||||
MailboxData *ob;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define ICTLR_REG_BASE(irq) ((((irq) - 32) >> 5) * 0x100)
|
||||
#define ICTLR_FIR_SET(irq) (ICTLR_REG_BASE(irq) + 0x18)
|
||||
#define ICTLR_FIR_CLR(irq) (ICTLR_REG_BASE(irq) + 0x1c)
|
||||
#define FIR_BIT(irq) (1 << ((irq) & 0x1f))
|
||||
|
||||
#define INT_GIC_BASE (0)
|
||||
#define INT_PRI_BASE (INT_GIC_BASE + 32)
|
||||
#define INT_SHR_SEM_OUTBOX_IBF (INT_PRI_BASE + 6)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
constexpr IoRegionExtents g_io_region_extents[4] = {
|
||||
{ KPhysicalAddress(0x12000000), 224_MB }, /* PCIE_A2 */
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
};
|
||||
|
||||
constexpr bool IsValidIoPoolTypeImpl(ams::svc::IoPoolType pool_type) {
|
||||
return pool_type == ams::svc::IoPoolType_PcieA2;
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_k_sleep_manager.hpp"
|
||||
#include "kern_secure_monitor.hpp"
|
||||
#include "kern_lps_driver.hpp"
|
||||
|
||||
namespace ams::kern::init {
|
||||
|
||||
void StartOtherCore(const ams::kern::init::KInitArguments *init_args);
|
||||
|
||||
}
|
||||
|
||||
namespace ams::kern::board::nintendo::nx {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Struct representing registers saved on wake/sleep. */
|
||||
class SavedSystemRegisters {
|
||||
private:
|
||||
u64 elr_el1;
|
||||
u64 sp_el0;
|
||||
u64 spsr_el1;
|
||||
u64 daif;
|
||||
u64 cpacr_el1;
|
||||
u64 vbar_el1;
|
||||
u64 csselr_el1;
|
||||
u64 cntp_ctl_el0;
|
||||
u64 cntp_cval_el0;
|
||||
u64 cntkctl_el1;
|
||||
u64 tpidr_el0;
|
||||
u64 tpidrro_el0;
|
||||
u64 mdscr_el1;
|
||||
u64 contextidr_el1;
|
||||
u64 dbgwcrN_el1[16];
|
||||
u64 dbgwvrN_el1[16];
|
||||
u64 dbgbcrN_el1[16];
|
||||
u64 dbgbvrN_el1[16];
|
||||
u64 pmccfiltr_el0;
|
||||
u64 pmccntr_el0;
|
||||
u64 pmcntenset_el0;
|
||||
u64 pmcr_el0;
|
||||
u64 pmevcntrN_el0[31];
|
||||
u64 pmevtyperN_el0[31];
|
||||
u64 pmintenset_el1;
|
||||
u64 pmovsset_el0;
|
||||
u64 pmselr_el0;
|
||||
u64 pmuserenr_el0;
|
||||
public:
|
||||
void Save();
|
||||
void Restore() const;
|
||||
};
|
||||
|
||||
constexpr s32 SleepManagerThreadPriority = 2;
|
||||
|
||||
/* Globals for sleep/wake. */
|
||||
constinit u64 g_sleep_target_cores;
|
||||
constinit KLightLock g_request_lock;
|
||||
constinit KLightLock g_cv_lock;
|
||||
constinit KLightConditionVariable g_cv{util::ConstantInitialize};
|
||||
alignas(1_KB) constinit u64 g_sleep_buffers[cpu::NumCores][1_KB / sizeof(u64)];
|
||||
constinit ams::kern::init::KInitArguments g_sleep_init_arguments[cpu::NumCores];
|
||||
constinit SavedSystemRegisters g_sleep_system_registers[cpu::NumCores] = {};
|
||||
|
||||
void WaitOtherCpuPowerOff() {
|
||||
constexpr u64 PmcPhysicalAddress = 0x7000E400;
|
||||
constexpr u32 PWRGATE_STATUS_CE123_MASK = ((1u << 3) - 1) << 9;
|
||||
|
||||
u32 value;
|
||||
do {
|
||||
bool res = smc::ReadWriteRegister(std::addressof(value), PmcPhysicalAddress + APBDEV_PMC_PWRGATE_STATUS, 0, 0);
|
||||
MESOSPHERE_ASSERT(res);
|
||||
MESOSPHERE_UNUSED(res);
|
||||
} while ((value & PWRGATE_STATUS_CE123_MASK) != 0);
|
||||
}
|
||||
|
||||
void SavedSystemRegisters::Save() {
|
||||
/* Save system registers. */
|
||||
this->tpidr_el0 = cpu::GetTpidrEl0();
|
||||
this->elr_el1 = cpu::GetElrEl1();
|
||||
this->sp_el0 = cpu::GetSpEl0();
|
||||
this->spsr_el1 = cpu::GetSpsrEl1();
|
||||
this->daif = cpu::GetDaif();
|
||||
this->cpacr_el1 = cpu::GetCpacrEl1();
|
||||
this->vbar_el1 = cpu::GetVbarEl1();
|
||||
this->csselr_el1 = cpu::GetCsselrEl1();
|
||||
this->cntp_ctl_el0 = cpu::GetCntpCtlEl0();
|
||||
this->cntp_cval_el0 = cpu::GetCntpCvalEl0();
|
||||
this->cntkctl_el1 = cpu::GetCntkCtlEl1();
|
||||
this->tpidrro_el0 = cpu::GetTpidrRoEl0();
|
||||
|
||||
/* Save pmu registers. */
|
||||
{
|
||||
/* Get and clear pmcr_el0 */
|
||||
this->pmcr_el0 = cpu::GetPmcrEl0();
|
||||
cpu::SetPmcrEl0(0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Save other pmu registers. */
|
||||
this->pmuserenr_el0 = cpu::GetPmUserEnrEl0();
|
||||
this->pmselr_el0 = cpu::GetPmSelrEl0();
|
||||
this->pmccfiltr_el0 = cpu::GetPmcCfiltrEl0();
|
||||
this->pmcntenset_el0 = cpu::GetPmCntEnSetEl0();
|
||||
this->pmintenset_el1 = cpu::GetPmIntEnSetEl1();
|
||||
this->pmovsset_el0 = cpu::GetPmOvsSetEl0();
|
||||
this->pmccntr_el0 = cpu::GetPmcCntrEl0();
|
||||
|
||||
switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) {
|
||||
#define HANDLE_PMU_CASE(N) \
|
||||
case (N+1): \
|
||||
this->pmevcntrN_el0 [ N ] = cpu::GetPmevCntr##N##El0(); \
|
||||
this->pmevtyperN_el0[ N ] = cpu::GetPmevTyper##N##El0(); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_PMU_CASE(30);
|
||||
HANDLE_PMU_CASE(29);
|
||||
HANDLE_PMU_CASE(28);
|
||||
HANDLE_PMU_CASE(27);
|
||||
HANDLE_PMU_CASE(26);
|
||||
HANDLE_PMU_CASE(25);
|
||||
HANDLE_PMU_CASE(24);
|
||||
HANDLE_PMU_CASE(23);
|
||||
HANDLE_PMU_CASE(22);
|
||||
HANDLE_PMU_CASE(21);
|
||||
HANDLE_PMU_CASE(20);
|
||||
HANDLE_PMU_CASE(19);
|
||||
HANDLE_PMU_CASE(18);
|
||||
HANDLE_PMU_CASE(17);
|
||||
HANDLE_PMU_CASE(16);
|
||||
HANDLE_PMU_CASE(15);
|
||||
HANDLE_PMU_CASE(14);
|
||||
HANDLE_PMU_CASE(13);
|
||||
HANDLE_PMU_CASE(12);
|
||||
HANDLE_PMU_CASE(11);
|
||||
HANDLE_PMU_CASE(10);
|
||||
HANDLE_PMU_CASE( 9);
|
||||
HANDLE_PMU_CASE( 8);
|
||||
HANDLE_PMU_CASE( 7);
|
||||
HANDLE_PMU_CASE( 6);
|
||||
HANDLE_PMU_CASE( 5);
|
||||
HANDLE_PMU_CASE( 4);
|
||||
HANDLE_PMU_CASE( 3);
|
||||
HANDLE_PMU_CASE( 2);
|
||||
HANDLE_PMU_CASE( 1);
|
||||
HANDLE_PMU_CASE( 0);
|
||||
|
||||
#undef HANDLE_PMU_CASE
|
||||
case 0:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save debug registers. */
|
||||
const u64 dfr0 = cpu::GetIdAa64Dfr0El1();
|
||||
|
||||
this->mdscr_el1 = cpu::GetMdscrEl1();
|
||||
this->contextidr_el1 = cpu::GetContextidrEl1();
|
||||
|
||||
/* Save watchpoints. */
|
||||
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) {
|
||||
#define HANDLE_DBG_CASE(N) \
|
||||
case N: \
|
||||
this->dbgwcrN_el1[ N ] = cpu::GetDbgWcr##N##El1(); \
|
||||
this->dbgwvrN_el1[ N ] = cpu::GetDbgWvr##N##El1(); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_DBG_CASE(15);
|
||||
HANDLE_DBG_CASE(14);
|
||||
HANDLE_DBG_CASE(13);
|
||||
HANDLE_DBG_CASE(12);
|
||||
HANDLE_DBG_CASE(11);
|
||||
HANDLE_DBG_CASE(10);
|
||||
HANDLE_DBG_CASE( 9);
|
||||
HANDLE_DBG_CASE( 8);
|
||||
HANDLE_DBG_CASE( 7);
|
||||
HANDLE_DBG_CASE( 6);
|
||||
HANDLE_DBG_CASE( 5);
|
||||
HANDLE_DBG_CASE( 4);
|
||||
HANDLE_DBG_CASE( 3);
|
||||
HANDLE_DBG_CASE( 2);
|
||||
|
||||
#undef HANDLE_DBG_CASE
|
||||
|
||||
case 1:
|
||||
this->dbgwcrN_el1[1] = cpu::GetDbgWcr1El1();
|
||||
this->dbgwvrN_el1[1] = cpu::GetDbgWvr1El1();
|
||||
this->dbgwcrN_el1[0] = cpu::GetDbgWcr0El1();
|
||||
this->dbgwvrN_el1[0] = cpu::GetDbgWvr0El1();
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Save breakpoints. */
|
||||
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) {
|
||||
#define HANDLE_DBG_CASE(N) \
|
||||
case N: \
|
||||
this->dbgbcrN_el1[ N ] = cpu::GetDbgBcr##N##El1(); \
|
||||
this->dbgbvrN_el1[ N ] = cpu::GetDbgBvr##N##El1(); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_DBG_CASE(15);
|
||||
HANDLE_DBG_CASE(14);
|
||||
HANDLE_DBG_CASE(13);
|
||||
HANDLE_DBG_CASE(12);
|
||||
HANDLE_DBG_CASE(11);
|
||||
HANDLE_DBG_CASE(10);
|
||||
HANDLE_DBG_CASE( 9);
|
||||
HANDLE_DBG_CASE( 8);
|
||||
HANDLE_DBG_CASE( 7);
|
||||
HANDLE_DBG_CASE( 6);
|
||||
HANDLE_DBG_CASE( 5);
|
||||
HANDLE_DBG_CASE( 4);
|
||||
HANDLE_DBG_CASE( 3);
|
||||
HANDLE_DBG_CASE( 2);
|
||||
|
||||
#undef HANDLE_DBG_CASE
|
||||
|
||||
case 1:
|
||||
this->dbgbcrN_el1[1] = cpu::GetDbgBcr1El1();
|
||||
this->dbgbvrN_el1[1] = cpu::GetDbgBvr1El1();
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->dbgbcrN_el1[0] = cpu::GetDbgBcr0El1();
|
||||
this->dbgbvrN_el1[0] = cpu::GetDbgBvr0El1();
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Clear mdscr_el1. */
|
||||
cpu::SetMdscrEl1(0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
}
|
||||
|
||||
void SavedSystemRegisters::Restore() const {
|
||||
/* Restore debug registers. */
|
||||
const u64 dfr0 = cpu::GetIdAa64Dfr0El1();
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetMdscrEl1(0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetOslarEl1(0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Restore watchpoints. */
|
||||
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) {
|
||||
#define HANDLE_DBG_CASE(N) \
|
||||
case N: \
|
||||
cpu::SetDbgWcr##N##El1(this->dbgwcrN_el1[ N ]); \
|
||||
cpu::SetDbgWvr##N##El1(this->dbgwvrN_el1[ N ]); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_DBG_CASE(15);
|
||||
HANDLE_DBG_CASE(14);
|
||||
HANDLE_DBG_CASE(13);
|
||||
HANDLE_DBG_CASE(12);
|
||||
HANDLE_DBG_CASE(11);
|
||||
HANDLE_DBG_CASE(10);
|
||||
HANDLE_DBG_CASE( 9);
|
||||
HANDLE_DBG_CASE( 8);
|
||||
HANDLE_DBG_CASE( 7);
|
||||
HANDLE_DBG_CASE( 6);
|
||||
HANDLE_DBG_CASE( 5);
|
||||
HANDLE_DBG_CASE( 4);
|
||||
HANDLE_DBG_CASE( 3);
|
||||
HANDLE_DBG_CASE( 2);
|
||||
|
||||
#undef HANDLE_DBG_CASE
|
||||
|
||||
case 1:
|
||||
cpu::SetDbgWcr1El1(this->dbgwcrN_el1[1]);
|
||||
cpu::SetDbgWvr1El1(this->dbgwvrN_el1[1]);
|
||||
cpu::SetDbgWcr0El1(this->dbgwcrN_el1[0]);
|
||||
cpu::SetDbgWvr0El1(this->dbgwvrN_el1[0]);
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Restore breakpoints. */
|
||||
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) {
|
||||
#define HANDLE_DBG_CASE(N) \
|
||||
case N: \
|
||||
cpu::SetDbgBcr##N##El1(this->dbgbcrN_el1[ N ]); \
|
||||
cpu::SetDbgBvr##N##El1(this->dbgbvrN_el1[ N ]); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_DBG_CASE(15);
|
||||
HANDLE_DBG_CASE(14);
|
||||
HANDLE_DBG_CASE(13);
|
||||
HANDLE_DBG_CASE(12);
|
||||
HANDLE_DBG_CASE(11);
|
||||
HANDLE_DBG_CASE(10);
|
||||
HANDLE_DBG_CASE( 9);
|
||||
HANDLE_DBG_CASE( 8);
|
||||
HANDLE_DBG_CASE( 7);
|
||||
HANDLE_DBG_CASE( 6);
|
||||
HANDLE_DBG_CASE( 5);
|
||||
HANDLE_DBG_CASE( 4);
|
||||
HANDLE_DBG_CASE( 3);
|
||||
HANDLE_DBG_CASE( 2);
|
||||
|
||||
#undef HANDLE_DBG_CASE
|
||||
|
||||
case 1:
|
||||
cpu::SetDbgBcr1El1(this->dbgbcrN_el1[1]);
|
||||
cpu::SetDbgBvr1El1(this->dbgbvrN_el1[1]);
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cpu::SetDbgBcr0El1(this->dbgbcrN_el1[0]);
|
||||
cpu::SetDbgBvr0El1(this->dbgbvrN_el1[0]);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetContextidrEl1(this->contextidr_el1);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetMdscrEl1(this->mdscr_el1);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Restore pmu registers. */
|
||||
cpu::SetPmUserEnrEl0(0);
|
||||
cpu::PerformanceMonitorsControlRegisterAccessor(0).SetEventCounterReset(true).SetCycleCounterReset(true).Store();
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetPmOvsClrEl0(static_cast<u64>(static_cast<u32>(~u32())));
|
||||
cpu::SetPmIntEnClrEl1(static_cast<u64>(static_cast<u32>(~u32())));
|
||||
cpu::SetPmCntEnClrEl0(static_cast<u64>(static_cast<u32>(~u32())));
|
||||
|
||||
switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) {
|
||||
#define HANDLE_PMU_CASE(N) \
|
||||
case (N+1): \
|
||||
cpu::SetPmevCntr##N##El0 (this->pmevcntrN_el0 [ N ]); \
|
||||
cpu::SetPmevTyper##N##El0(this->pmevtyperN_el0[ N ]); \
|
||||
[[fallthrough]]
|
||||
|
||||
HANDLE_PMU_CASE(30);
|
||||
HANDLE_PMU_CASE(29);
|
||||
HANDLE_PMU_CASE(28);
|
||||
HANDLE_PMU_CASE(27);
|
||||
HANDLE_PMU_CASE(26);
|
||||
HANDLE_PMU_CASE(25);
|
||||
HANDLE_PMU_CASE(24);
|
||||
HANDLE_PMU_CASE(23);
|
||||
HANDLE_PMU_CASE(22);
|
||||
HANDLE_PMU_CASE(21);
|
||||
HANDLE_PMU_CASE(20);
|
||||
HANDLE_PMU_CASE(19);
|
||||
HANDLE_PMU_CASE(18);
|
||||
HANDLE_PMU_CASE(17);
|
||||
HANDLE_PMU_CASE(16);
|
||||
HANDLE_PMU_CASE(15);
|
||||
HANDLE_PMU_CASE(14);
|
||||
HANDLE_PMU_CASE(13);
|
||||
HANDLE_PMU_CASE(12);
|
||||
HANDLE_PMU_CASE(11);
|
||||
HANDLE_PMU_CASE(10);
|
||||
HANDLE_PMU_CASE( 9);
|
||||
HANDLE_PMU_CASE( 8);
|
||||
HANDLE_PMU_CASE( 7);
|
||||
HANDLE_PMU_CASE( 6);
|
||||
HANDLE_PMU_CASE( 5);
|
||||
HANDLE_PMU_CASE( 4);
|
||||
HANDLE_PMU_CASE( 3);
|
||||
HANDLE_PMU_CASE( 2);
|
||||
HANDLE_PMU_CASE( 1);
|
||||
HANDLE_PMU_CASE( 0);
|
||||
|
||||
#undef HANDLE_PMU_CASE
|
||||
case 0:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cpu::SetPmUserEnrEl0 (this->pmuserenr_el0);
|
||||
cpu::SetPmSelrEl0 (this->pmselr_el0);
|
||||
cpu::SetPmcCfiltrEl0 (this->pmccfiltr_el0);
|
||||
cpu::SetPmCntEnSetEl0(this->pmcntenset_el0);
|
||||
cpu::SetPmIntEnSetEl1(this->pmintenset_el1);
|
||||
cpu::SetPmOvsSetEl0 (this->pmovsset_el0);
|
||||
cpu::SetPmcCntrEl0 (this->pmccntr_el0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
cpu::SetPmcrEl0(this->pmcr_el0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Restore system registers. */
|
||||
cpu::SetTtbr0El1 (KPageTable::GetKernelTtbr0());
|
||||
cpu::SetTpidrEl0 (this->tpidr_el0);
|
||||
cpu::SetElrEl1 (this->elr_el1);
|
||||
cpu::SetSpEl0 (this->sp_el0);
|
||||
cpu::SetSpsrEl1 (this->spsr_el1);
|
||||
cpu::SetDaif (this->daif);
|
||||
cpu::SetCpacrEl1 (this->cpacr_el1);
|
||||
cpu::SetVbarEl1 (this->vbar_el1);
|
||||
cpu::SetCsselrEl1 (this->csselr_el1);
|
||||
cpu::SetCntpCtlEl0 (this->cntp_ctl_el0);
|
||||
cpu::SetCntpCvalEl0(this->cntp_cval_el0);
|
||||
cpu::SetCntkCtlEl1 (this->cntkctl_el1);
|
||||
cpu::SetTpidrRoEl0 (this->tpidrro_el0);
|
||||
cpu::EnsureInstructionConsistency();
|
||||
|
||||
/* Invalidate the entire tlb. */
|
||||
cpu::InvalidateEntireTlb();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KSleepManager::Initialize() {
|
||||
/* Create a sleep manager thread for each core. */
|
||||
for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) {
|
||||
/* Reserve a thread from the system limit. */
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1));
|
||||
|
||||
/* Create a new thread. */
|
||||
KThread *new_thread = KThread::Create();
|
||||
MESOSPHERE_ABORT_UNLESS(new_thread != nullptr);
|
||||
|
||||
/* Launch the new thread. */
|
||||
MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, KSleepManager::ProcessRequests, reinterpret_cast<uintptr_t>(g_sleep_buffers[core_id]), SleepManagerThreadPriority, static_cast<s32>(core_id)));
|
||||
|
||||
/* Register the new thread. */
|
||||
KThread::Register(new_thread);
|
||||
|
||||
/* Run the thread. */
|
||||
new_thread->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void KSleepManager::SleepSystem() {
|
||||
/* Ensure device mappings are not modified during sleep. */
|
||||
KDevicePageTable::Lock();
|
||||
ON_SCOPE_EXIT { KDevicePageTable::Unlock(); };
|
||||
|
||||
/* Request that the system sleep. */
|
||||
{
|
||||
KScopedLightLock lk(g_request_lock);
|
||||
|
||||
/* Signal the manager to sleep on all cores. */
|
||||
{
|
||||
KScopedLightLock lk(g_cv_lock);
|
||||
MESOSPHERE_ABORT_UNLESS(g_sleep_target_cores == 0);
|
||||
|
||||
g_sleep_target_cores = (1ul << cpu::NumCores) - 1;
|
||||
g_cv.Broadcast();
|
||||
|
||||
while (g_sleep_target_cores != 0) {
|
||||
g_cv.Wait(std::addressof(g_cv_lock));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KSleepManager::ProcessRequests(uintptr_t sleep_buffer) {
|
||||
const auto target_fw = GetTargetFirmware();
|
||||
const s32 core_id = GetCurrentCoreId();
|
||||
|
||||
ams::kern::init::KInitArguments * const init_args = g_sleep_init_arguments + core_id;
|
||||
KPhysicalAddress start_core_phys_addr = Null<KPhysicalAddress>;
|
||||
KPhysicalAddress init_args_phys_addr = Null<KPhysicalAddress>;
|
||||
|
||||
/* Get the physical addresses we'll need. */
|
||||
{
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(start_core_phys_addr), KProcessAddress(&::ams::kern::init::StartOtherCore)));
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(init_args_phys_addr), KProcessAddress(init_args)));
|
||||
}
|
||||
|
||||
const u64 target_core_mask = (1ul << core_id);
|
||||
|
||||
const bool use_legacy_lps_driver = target_fw < TargetFirmware_2_0_0;
|
||||
|
||||
/* Loop, processing sleep when requested. */
|
||||
while (true) {
|
||||
/* Wait for a request. */
|
||||
{
|
||||
KScopedLightLock lk(g_cv_lock);
|
||||
while ((g_sleep_target_cores & target_core_mask) == 0) {
|
||||
g_cv.Wait(std::addressof(g_cv_lock));
|
||||
}
|
||||
}
|
||||
|
||||
/* If on core 0, ensure the legacy lps driver is initialized. */
|
||||
if (use_legacy_lps_driver && core_id == 0) {
|
||||
lps::Initialize();
|
||||
}
|
||||
|
||||
/* Perform Sleep/Wake sequence. */
|
||||
{
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Save the system registers for the current core. */
|
||||
g_sleep_system_registers[core_id].Save();
|
||||
|
||||
/* Invalidate the entire tlb. */
|
||||
cpu::InvalidateEntireTlb();
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* If on core 0, put the device page tables to sleep. */
|
||||
if (core_id == 0) {
|
||||
KDevicePageTable::Sleep();
|
||||
}
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Wait 100us before continuing. */
|
||||
{
|
||||
const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromMicroSeconds(100));
|
||||
while (KHardwareTimer::GetTick() < timeout) {
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the interrupt manager's state. */
|
||||
Kernel::GetInterruptManager().Save(core_id);
|
||||
|
||||
/* Setup the initial arguments. */
|
||||
{
|
||||
/* Determine whether we're running on a cortex-a53 or a-57. */
|
||||
cpu::MainIdRegisterAccessor midr_el1;
|
||||
const auto implementer = midr_el1.GetImplementer();
|
||||
const auto primary_part = midr_el1.GetPrimaryPartNumber();
|
||||
const bool needs_cpu_ctlr = (implementer == cpu::MainIdRegisterAccessor::Implementer::ArmLimited) && (primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA57 || primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA53);
|
||||
|
||||
init_args->cpuactlr = needs_cpu_ctlr ? cpu::GetCpuActlrEl1() : 0;
|
||||
init_args->cpuectlr = needs_cpu_ctlr ? cpu::GetCpuEctlrEl1() : 0;
|
||||
init_args->sp = 0;
|
||||
init_args->entrypoint = reinterpret_cast<uintptr_t>(::ams::kern::board::nintendo::nx::KSleepManager::ResumeEntry);
|
||||
init_args->argument = sleep_buffer;
|
||||
}
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Log that the core is going to sleep. */
|
||||
MESOSPHERE_LOG("Core[%d]: Going to sleep, buffer = %010lx\n", core_id, sleep_buffer);
|
||||
|
||||
/* If we're on a core other than zero, we can just invoke the sleep handler. */
|
||||
if (core_id != 0) {
|
||||
CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
||||
} else {
|
||||
/* Wait for all other cores to be powered off. */
|
||||
WaitOtherCpuPowerOff();
|
||||
|
||||
/* If we're using the legacy lps driver, enable suspend. */
|
||||
if (use_legacy_lps_driver) {
|
||||
MESOSPHERE_R_ABORT_UNLESS(lps::EnableSuspend(true));
|
||||
}
|
||||
|
||||
/* Log that we're about to enter SC7. */
|
||||
MESOSPHERE_LOG("Entering SC7\n");
|
||||
|
||||
/* Save the debug log state. */
|
||||
KDebugLog::Save();
|
||||
|
||||
/* Invoke the sleep handler. */
|
||||
if (!use_legacy_lps_driver) {
|
||||
/* When not using the legacy driver, invoke directly. */
|
||||
CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
||||
} else {
|
||||
lps::InvokeCpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
||||
}
|
||||
|
||||
/* Restore the debug log state. */
|
||||
KDebugLog::Restore();
|
||||
|
||||
/* Log that we're about to exit SC7. */
|
||||
MESOSPHERE_LOG("Exiting SC7\n");
|
||||
|
||||
/* Wake up the other cores. */
|
||||
cpu::MultiprocessorAffinityRegisterAccessor mpidr;
|
||||
const auto arg = mpidr.GetCpuOnArgument();
|
||||
for (s32 i = 1; i < static_cast<s32>(cpu::NumCores); ++i) {
|
||||
KSystemControl::Init::TurnOnCpu(arg | i, g_sleep_init_arguments + i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Log that the core is waking from sleep. */
|
||||
MESOSPHERE_LOG("Core[%d]: Woke from sleep.\n", core_id);
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Restore the interrupt manager's state. */
|
||||
Kernel::GetInterruptManager().Restore(core_id);
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* If on core 0, wake up the device page tables. */
|
||||
if (core_id == 0) {
|
||||
KDevicePageTable::Wakeup();
|
||||
|
||||
/* If we're using the legacy driver, resume the bpmp firmware. */
|
||||
if (use_legacy_lps_driver) {
|
||||
lps::ResumeBpmpFirmware();
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure that all cores get to this point before continuing. */
|
||||
cpu::SynchronizeAllCores();
|
||||
|
||||
/* Restore the system registers for the current core. */
|
||||
g_sleep_system_registers[core_id].Restore();
|
||||
}
|
||||
|
||||
/* Signal request completed. */
|
||||
{
|
||||
KScopedLightLock lk(g_cv_lock);
|
||||
g_sleep_target_cores &= ~target_core_mask;
|
||||
if (g_sleep_target_cores == 0) {
|
||||
g_cv.Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::board::nintendo::nx {
|
||||
|
||||
class KSleepManager {
|
||||
private:
|
||||
static void ResumeEntry(uintptr_t arg);
|
||||
|
||||
static void ProcessRequests(uintptr_t buffer);
|
||||
public:
|
||||
static void Initialize();
|
||||
static void SleepSystem();
|
||||
public:
|
||||
static void CpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_args);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* For some reason GAS doesn't know about it, even with .cpu cortex-a57 */
|
||||
#define cpuactlr_el1 s3_1_c15_c2_0
|
||||
#define cpuectlr_el1 s3_1_c15_c2_1
|
||||
|
||||
#define LOAD_IMMEDIATE_32(reg, val) \
|
||||
mov reg, #(((val) >> 0x00) & 0xFFFF); \
|
||||
movk reg, #(((val) >> 0x10) & 0xFFFF), lsl#16
|
||||
|
||||
/* ams::kern::board::nintendo::nx::KSleepManager::CpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg) */
|
||||
.section .sleep._ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm, "ax", %progbits
|
||||
.global _ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm
|
||||
.type _ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm, %function
|
||||
_ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm:
|
||||
/* Save arguments. */
|
||||
mov x16, x1
|
||||
mov x17, x2
|
||||
|
||||
/* Enable access to FPU registers. */
|
||||
mrs x1, cpacr_el1
|
||||
orr x1, x1, #0x100000
|
||||
msr cpacr_el1, x1
|
||||
dsb sy
|
||||
isb
|
||||
|
||||
/* Save callee-save registers. */
|
||||
stp x18, x19, [x0], #0x10
|
||||
stp x20, x21, [x0], #0x10
|
||||
stp x22, x23, [x0], #0x10
|
||||
stp x24, x25, [x0], #0x10
|
||||
stp x26, x27, [x0], #0x10
|
||||
stp x28, x29, [x0], #0x10
|
||||
stp x30, xzr, [x0], #0x10
|
||||
|
||||
/* Save stack pointer. */
|
||||
mov x1, sp
|
||||
str x1, [x0], #8
|
||||
|
||||
/* Save fpcr/fpsr. */
|
||||
mrs x1, fpcr
|
||||
mrs x2, fpsr
|
||||
stp w1, w2, [x0], #8
|
||||
|
||||
/* Save the floating point registers. */
|
||||
stp q0, q1, [x0], #0x20
|
||||
stp q2, q3, [x0], #0x20
|
||||
stp q4, q5, [x0], #0x20
|
||||
stp q6, q7, [x0], #0x20
|
||||
stp q8, q9, [x0], #0x20
|
||||
stp q10, q11, [x0], #0x20
|
||||
stp q12, q13, [x0], #0x20
|
||||
stp q14, q15, [x0], #0x20
|
||||
stp q16, q17, [x0], #0x20
|
||||
stp q28, q19, [x0], #0x20
|
||||
stp q20, q21, [x0], #0x20
|
||||
stp q22, q23, [x0], #0x20
|
||||
stp q24, q25, [x0], #0x20
|
||||
stp q26, q27, [x0], #0x20
|
||||
stp q28, q29, [x0], #0x20
|
||||
stp q30, q31, [x0], #0x20
|
||||
|
||||
/* Save tpidr/cntv_cval_el0. */
|
||||
mrs x1, tpidr_el1
|
||||
mrs x2, cntv_cval_el0
|
||||
stp x1, x2, [x0], #0x10
|
||||
|
||||
/* Get the current core id. */
|
||||
mrs x0, mpidr_el1
|
||||
and x0, x0, #0xFF
|
||||
|
||||
/* If we're on core 0, suspend. */
|
||||
cbz x0, 1f
|
||||
|
||||
/* Otherwise, power off. */
|
||||
LOAD_IMMEDIATE_32(x0, 0x84000002)
|
||||
smc #1
|
||||
0: b 0b
|
||||
|
||||
1: /* Suspend. */
|
||||
LOAD_IMMEDIATE_32(x0, 0xC4000001)
|
||||
LOAD_IMMEDIATE_32(x1, 0x0201001B)
|
||||
mov x2, x16
|
||||
mov x3, x17
|
||||
smc #1
|
||||
0: b 0b
|
||||
|
||||
/* ams::kern::board::nintendo::nx::KSleepManager::ResumeEntry(uintptr_t arg) */
|
||||
.section .sleep._ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm, "ax", %progbits
|
||||
.global _ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm
|
||||
.type _ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm, %function
|
||||
_ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm:
|
||||
/* Enable access to FPU registers. */
|
||||
mrs x1, cpacr_el1
|
||||
orr x1, x1, #0x100000
|
||||
msr cpacr_el1, x1
|
||||
dsb sy
|
||||
isb
|
||||
|
||||
/* Restore callee-save registers. */
|
||||
ldp x18, x19, [x0], #0x10
|
||||
ldp x20, x21, [x0], #0x10
|
||||
ldp x22, x23, [x0], #0x10
|
||||
ldp x24, x25, [x0], #0x10
|
||||
ldp x26, x27, [x0], #0x10
|
||||
ldp x28, x29, [x0], #0x10
|
||||
ldp x30, xzr, [x0], #0x10
|
||||
|
||||
/* Restore stack pointer. */
|
||||
ldr x1, [x0], #8
|
||||
mov sp, x1
|
||||
|
||||
/* Restore fpcr/fpsr. */
|
||||
ldp w1, w2, [x0], #8
|
||||
msr fpcr, x1
|
||||
msr fpsr, x2
|
||||
|
||||
/* Restore the floating point registers. */
|
||||
ldp q0, q1, [x0], #0x20
|
||||
ldp q2, q3, [x0], #0x20
|
||||
ldp q4, q5, [x0], #0x20
|
||||
ldp q6, q7, [x0], #0x20
|
||||
ldp q8, q9, [x0], #0x20
|
||||
ldp q10, q11, [x0], #0x20
|
||||
ldp q12, q13, [x0], #0x20
|
||||
ldp q14, q15, [x0], #0x20
|
||||
ldp q16, q17, [x0], #0x20
|
||||
ldp q28, q19, [x0], #0x20
|
||||
ldp q20, q21, [x0], #0x20
|
||||
ldp q22, q23, [x0], #0x20
|
||||
ldp q24, q25, [x0], #0x20
|
||||
ldp q26, q27, [x0], #0x20
|
||||
ldp q28, q29, [x0], #0x20
|
||||
ldp q30, q31, [x0], #0x20
|
||||
|
||||
/* Restore tpidr/cntv_cval_el0. */
|
||||
ldp x1, x2, [x0], #0x10
|
||||
msr tpidr_el1, x1
|
||||
msr cntv_cval_el0, x2
|
||||
dsb sy
|
||||
isb
|
||||
|
||||
/* Return. */
|
||||
ret
|
||||
@@ -0,0 +1,715 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_secure_monitor.hpp"
|
||||
#include "kern_k_sleep_manager.hpp"
|
||||
|
||||
namespace ams::kern::board::nintendo::nx {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t SecureAlignment = 128_KB;
|
||||
|
||||
constexpr size_t SecureSizeMax = util::AlignDown(512_MB - 1, SecureAlignment);
|
||||
|
||||
/* Global variables for panic. */
|
||||
constinit const volatile bool g_call_smc_on_panic = false;
|
||||
|
||||
/* Global variables for secure memory. */
|
||||
constinit KSpinLock g_secure_applet_lock;
|
||||
constinit bool g_secure_applet_memory_used = false;
|
||||
constinit KVirtualAddress g_secure_applet_memory_address = Null<KVirtualAddress>;
|
||||
|
||||
constinit KSpinLock g_secure_region_lock;
|
||||
constinit bool g_secure_region_used = false;
|
||||
constinit KPhysicalAddress g_secure_region_phys_addr = Null<KPhysicalAddress>;
|
||||
constinit size_t g_secure_region_size = 0;
|
||||
|
||||
ALWAYS_INLINE util::BitPack32 GetKernelConfigurationForInit() {
|
||||
u64 value = 0;
|
||||
smc::init::GetConfig(&value, 1, smc::ConfigItem::KernelConfiguration);
|
||||
return util::BitPack32{static_cast<u32>(value)};
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u32 GetMemoryModeForInit() {
|
||||
u64 value = 0;
|
||||
smc::init::GetConfig(&value, 1, smc::ConfigItem::MemoryMode);
|
||||
return static_cast<u32>(value);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE smc::MemoryArrangement GetMemoryArrangeForInit() {
|
||||
switch(GetMemoryModeForInit() & 0x3F) {
|
||||
case 0x01:
|
||||
default:
|
||||
return smc::MemoryArrangement_4GB;
|
||||
case 0x02:
|
||||
return smc::MemoryArrangement_4GBForAppletDev;
|
||||
case 0x03:
|
||||
return smc::MemoryArrangement_4GBForSystemDev;
|
||||
case 0x11:
|
||||
return smc::MemoryArrangement_6GB;
|
||||
case 0x12:
|
||||
return smc::MemoryArrangement_6GBForAppletDev;
|
||||
case 0x21:
|
||||
return smc::MemoryArrangement_8GB;
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u64 GenerateRandomU64ForInit() {
|
||||
u64 value;
|
||||
smc::init::GenerateRandomBytes(std::addressof(value), sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u64 GenerateRandomU64FromSmc() {
|
||||
u64 value;
|
||||
smc::GenerateRandomBytes(std::addressof(value), sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u64 GetConfigU64(smc::ConfigItem which) {
|
||||
u64 value;
|
||||
smc::GetConfig(&value, 1, which);
|
||||
return value;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u32 GetConfigU32(smc::ConfigItem which) {
|
||||
return static_cast<u32>(GetConfigU64(which));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool GetConfigBool(smc::ConfigItem which) {
|
||||
return GetConfigU64(which) != 0;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool CheckRegisterAllowedTable(const u8 *table, const size_t offset) {
|
||||
return (table[(offset / sizeof(u32)) / BITSIZEOF(u8)] & (1u << ((offset / sizeof(u32)) % BITSIZEOF(u8)))) != 0;
|
||||
}
|
||||
|
||||
/* TODO: Generate this from a list of register names (see similar logic in exosphere)? */
|
||||
constexpr inline const u8 McKernelRegisterWhitelist[(PageSize / sizeof(u32)) / BITSIZEOF(u8)] = {
|
||||
0x9F, 0x31, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xC0, 0x73, 0x3E, 0x6F, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* TODO: Generate this from a list of register names (see similar logic in exosphere)? */
|
||||
constexpr inline const u8 McUserRegisterWhitelist[(PageSize / sizeof(u32)) / BITSIZEOF(u8)] = {
|
||||
0x00, 0x00, 0x20, 0x00, 0xF0, 0xFF, 0xF7, 0x01,
|
||||
0xCD, 0xFE, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E,
|
||||
0x30, 0x05, 0x06, 0xB0, 0x71, 0xC8, 0x43, 0x04,
|
||||
0x80, 0xFF, 0x08, 0x80, 0x03, 0x38, 0x8E, 0x1F,
|
||||
0xC8, 0xFF, 0xFF, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||
0xF0, 0x1F, 0x00, 0x30, 0xF0, 0x03, 0x03, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xFE, 0x0F,
|
||||
0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
bool IsRegisterAccessibleToPrivileged(ams::svc::PhysicalAddress address) {
|
||||
/* Find the region for the address. */
|
||||
const KMemoryRegion *region = KMemoryLayout::Find(KPhysicalAddress(address));
|
||||
if (AMS_LIKELY(region != nullptr)) {
|
||||
if (AMS_LIKELY(region->IsDerivedFrom(KMemoryRegionType_MemoryController))) {
|
||||
/* Check the region is valid. */
|
||||
MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0);
|
||||
|
||||
/* Get the offset within the region. */
|
||||
const size_t offset = address - region->GetAddress();
|
||||
MESOSPHERE_ABORT_UNLESS(offset < region->GetSize());
|
||||
|
||||
/* Check the whitelist. */
|
||||
if (AMS_LIKELY(CheckRegisterAllowedTable(McKernelRegisterWhitelist, offset))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsRegisterAccessibleToUser(ams::svc::PhysicalAddress address) {
|
||||
/* Find the region for the address. */
|
||||
const KMemoryRegion *region = KMemoryLayout::Find(KPhysicalAddress(address));
|
||||
if (AMS_LIKELY(region != nullptr)) {
|
||||
/* The PMC is always allowed. */
|
||||
if (region->IsDerivedFrom(KMemoryRegionType_PowerManagementController)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Memory controller is allowed if the register is whitelisted. */
|
||||
if (region->IsDerivedFrom(KMemoryRegionType_MemoryController ) ||
|
||||
region->IsDerivedFrom(KMemoryRegionType_MemoryController0) ||
|
||||
region->IsDerivedFrom(KMemoryRegionType_MemoryController1))
|
||||
{
|
||||
/* Check the region is valid. */
|
||||
MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0);
|
||||
|
||||
/* Get the offset within the region. */
|
||||
const size_t offset = address - region->GetAddress();
|
||||
MESOSPHERE_ABORT_UNLESS(offset < region->GetSize());
|
||||
|
||||
/* Check the whitelist. */
|
||||
if (AMS_LIKELY(CheckRegisterAllowedTable(McUserRegisterWhitelist, offset))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetSecureRegion(KPhysicalAddress phys_addr, size_t size) {
|
||||
/* Ensure size is valid. */
|
||||
if (size > SecureSizeMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure address and size are aligned. */
|
||||
if (!util::IsAligned(GetInteger(phys_addr), SecureAlignment)) {
|
||||
return false;
|
||||
}
|
||||
if (!util::IsAligned(size, SecureAlignment)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Disable interrupts and acquire the secure region lock. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_secure_region_lock);
|
||||
|
||||
/* If size is non-zero, we're allocating the secure region. Otherwise, we're freeing it. */
|
||||
if (size != 0) {
|
||||
/* Verify that the secure region is free. */
|
||||
if (g_secure_region_used) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the secure region. */
|
||||
g_secure_region_used = true;
|
||||
g_secure_region_phys_addr = phys_addr;
|
||||
g_secure_region_size = size;
|
||||
} else {
|
||||
/* Verify that the secure region is in use. */
|
||||
if (!g_secure_region_used) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Verify that the address being freed is the secure region. */
|
||||
if (phys_addr != g_secure_region_phys_addr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Clear the secure region. */
|
||||
g_secure_region_used = false;
|
||||
g_secure_region_phys_addr = Null<KPhysicalAddress>;
|
||||
g_secure_region_size = 0;
|
||||
}
|
||||
|
||||
/* Configure the carveout with the secure monitor. */
|
||||
smc::ConfigureCarveout(1, GetInteger(phys_addr), size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result AllocateSecureMemoryForApplet(KVirtualAddress *out, size_t size) {
|
||||
/* Verify that the size is valid. */
|
||||
R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize());
|
||||
R_UNLESS(size <= KSystemControl::SecureAppletMemorySize, svc::ResultOutOfMemory());
|
||||
|
||||
/* Disable interrupts and acquire the secure applet lock. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_secure_applet_lock);
|
||||
|
||||
/* Check that memory is reserved for secure applet use. */
|
||||
MESOSPHERE_ABORT_UNLESS(g_secure_applet_memory_address != Null<KVirtualAddress>);
|
||||
|
||||
/* Verify that the secure applet memory isn't already being used. */
|
||||
R_UNLESS(!g_secure_applet_memory_used, svc::ResultOutOfMemory());
|
||||
|
||||
/* Return the secure applet memory. */
|
||||
g_secure_applet_memory_used = true;
|
||||
*out = g_secure_applet_memory_address;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void FreeSecureMemoryForApplet(KVirtualAddress address, size_t size) {
|
||||
/* Disable interrupts and acquire the secure applet lock. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_secure_applet_lock);
|
||||
|
||||
/* Verify that the memory being freed is correct. */
|
||||
MESOSPHERE_ABORT_UNLESS(address == g_secure_applet_memory_address);
|
||||
MESOSPHERE_ABORT_UNLESS(size <= KSystemControl::SecureAppletMemorySize);
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, PageSize));
|
||||
MESOSPHERE_ABORT_UNLESS(g_secure_applet_memory_used);
|
||||
|
||||
/* Release the secure applet memory. */
|
||||
g_secure_applet_memory_used = false;
|
||||
}
|
||||
|
||||
u32 GetVersionIdentifier() {
|
||||
u32 value = 0;
|
||||
value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO) << 0;
|
||||
value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR) << 8;
|
||||
value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR) << 16;
|
||||
value |= static_cast<u64>('M') << 24;
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Initialization. */
|
||||
size_t KSystemControl::Init::GetRealMemorySize() {
|
||||
/* TODO: Move this into a header for the MC in general. */
|
||||
constexpr u32 MemoryControllerConfigurationRegister = 0x70019050;
|
||||
u32 config_value;
|
||||
smc::init::ReadWriteRegister(std::addressof(config_value), MemoryControllerConfigurationRegister, 0, 0);
|
||||
return static_cast<size_t>(config_value & 0x3FFF) << 20;
|
||||
}
|
||||
|
||||
size_t KSystemControl::Init::GetIntendedMemorySize() {
|
||||
switch (GetKernelConfigurationForInit().Get<smc::KernelConfiguration::MemorySize>()) {
|
||||
case smc::MemorySize_4GB:
|
||||
default: /* All invalid modes should go to 4GB. */
|
||||
return 4_GB;
|
||||
case smc::MemorySize_6GB:
|
||||
return 6_GB;
|
||||
case smc::MemorySize_8GB:
|
||||
return 8_GB;
|
||||
}
|
||||
}
|
||||
|
||||
bool KSystemControl::Init::ShouldIncreaseThreadResourceLimit() {
|
||||
return GetKernelConfigurationForInit().Get<smc::KernelConfiguration::IncreaseThreadResourceLimit>();
|
||||
}
|
||||
|
||||
size_t KSystemControl::Init::GetApplicationPoolSize() {
|
||||
/* Get the base pool size. */
|
||||
const size_t base_pool_size = []() ALWAYS_INLINE_LAMBDA -> size_t {
|
||||
switch (GetMemoryArrangeForInit()) {
|
||||
case smc::MemoryArrangement_4GB:
|
||||
default:
|
||||
return 3285_MB;
|
||||
case smc::MemoryArrangement_4GBForAppletDev:
|
||||
return 2048_MB;
|
||||
case smc::MemoryArrangement_4GBForSystemDev:
|
||||
return 3285_MB;
|
||||
case smc::MemoryArrangement_6GB:
|
||||
return 4916_MB;
|
||||
case smc::MemoryArrangement_6GBForAppletDev:
|
||||
return 3285_MB;
|
||||
case smc::MemoryArrangement_8GB:
|
||||
return 6964_MB;
|
||||
}
|
||||
}();
|
||||
|
||||
/* Return (possibly) adjusted size. */
|
||||
return base_pool_size;
|
||||
}
|
||||
|
||||
size_t KSystemControl::Init::GetAppletPoolSize() {
|
||||
/* Get the base pool size. */
|
||||
const size_t base_pool_size = []() ALWAYS_INLINE_LAMBDA -> size_t {
|
||||
switch (GetMemoryArrangeForInit()) {
|
||||
case smc::MemoryArrangement_4GB:
|
||||
default:
|
||||
return 507_MB;
|
||||
case smc::MemoryArrangement_4GBForAppletDev:
|
||||
return 1554_MB;
|
||||
case smc::MemoryArrangement_4GBForSystemDev:
|
||||
return 448_MB;
|
||||
case smc::MemoryArrangement_6GB:
|
||||
return 562_MB;
|
||||
case smc::MemoryArrangement_6GBForAppletDev:
|
||||
return 2193_MB;
|
||||
case smc::MemoryArrangement_8GB:
|
||||
return 562_MB;
|
||||
}
|
||||
}();
|
||||
|
||||
/* Return (possibly) adjusted size. */
|
||||
/* NOTE: On 20.0.0+ the browser requires much more memory in the applet pool in order to function. */
|
||||
/* Thus, we have to reduce our extra system memory size by 26 MB to compensate. */
|
||||
const size_t ExtraSystemMemoryForAtmosphere = kern::GetTargetFirmware() >= ams::TargetFirmware_20_0_0 ? 14_MB : 40_MB;
|
||||
return base_pool_size - ExtraSystemMemoryForAtmosphere - KTraceBufferSize;
|
||||
}
|
||||
|
||||
size_t KSystemControl::Init::GetMinimumNonSecureSystemPoolSize() {
|
||||
/* Verify that our minimum is at least as large as Nintendo's. */
|
||||
constexpr size_t MinimumSizeWithFatal = ::ams::svc::RequiredNonSecureSystemMemorySizeWithFatal;
|
||||
static_assert(MinimumSizeWithFatal >= 0x2C04000);
|
||||
|
||||
constexpr size_t MinimumSizeWithoutFatal = ::ams::svc::RequiredNonSecureSystemMemorySize;
|
||||
static_assert(MinimumSizeWithoutFatal >= 0x2A00000);
|
||||
|
||||
/* Include fatal in non-seure size on 16.0.0+. */
|
||||
return kern::GetTargetFirmware() >= ams::TargetFirmware_16_0_0 ? MinimumSizeWithFatal : MinimumSizeWithoutFatal;
|
||||
}
|
||||
|
||||
u8 KSystemControl::Init::GetDebugLogUartPort() {
|
||||
/* Get the log configuration. */
|
||||
u64 value = 0;
|
||||
smc::init::GetConfig(std::addressof(value), 1, smc::ConfigItem::ExosphereLogConfiguration);
|
||||
|
||||
/* Extract the port. */
|
||||
return static_cast<u8>((value >> 32) & 0xFF);
|
||||
}
|
||||
|
||||
void KSystemControl::Init::CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg) {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS((::ams::kern::arch::arm64::smc::CpuOn<smc::SmcId_Supervisor>(core_id, entrypoint, arg)) == 0);
|
||||
}
|
||||
|
||||
/* Randomness for Initialization. */
|
||||
void KSystemControl::Init::GenerateRandom(u64 *dst, size_t count) {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(count <= 7);
|
||||
smc::init::GenerateRandomBytes(dst, count * sizeof(u64));
|
||||
}
|
||||
|
||||
u64 KSystemControl::Init::GenerateRandomRange(u64 min, u64 max) {
|
||||
return KSystemControlBase::GenerateUniformRange(min, max, GenerateRandomU64ForInit);
|
||||
}
|
||||
|
||||
/* System Initialization. */
|
||||
void KSystemControl::ConfigureKTargetSystem() {
|
||||
/* Configure KTargetSystem. */
|
||||
volatile auto *ts = const_cast<volatile KTargetSystem::KTargetSystemData *>(std::addressof(KTargetSystem::s_data));
|
||||
{
|
||||
/* Set whether we're in debug mode. */
|
||||
{
|
||||
ts->is_not_debug_mode = !GetConfigBool(smc::ConfigItem::IsDebugMode);
|
||||
|
||||
/* If we're not in debug mode, we don't want to initialize uart logging. */
|
||||
ts->disable_debug_logging = ts->is_not_debug_mode;
|
||||
}
|
||||
|
||||
/* Set Kernel Configuration. */
|
||||
{
|
||||
const auto kernel_config = util::BitPack32{GetConfigU32(smc::ConfigItem::KernelConfiguration)};
|
||||
|
||||
ts->disable_debug_memory_fill = !kernel_config.Get<smc::KernelConfiguration::DebugFillMemory>();
|
||||
ts->disable_user_exception_handlers = !kernel_config.Get<smc::KernelConfiguration::EnableUserExceptionHandlers>();
|
||||
ts->disable_dynamic_resource_limits = kernel_config.Get<smc::KernelConfiguration::DisableDynamicResourceLimits>();
|
||||
ts->disable_user_pmu_access = !kernel_config.Get<smc::KernelConfiguration::EnableUserPmuAccess>();
|
||||
|
||||
/* Configure call smc on panic. */
|
||||
*const_cast<volatile bool *>(std::addressof(g_call_smc_on_panic)) = kernel_config.Get<smc::KernelConfiguration::UseSecureMonitorPanicCall>();
|
||||
}
|
||||
|
||||
/* Set Kernel Debugging. */
|
||||
{
|
||||
/* NOTE: This is used to restrict access to SvcKernelDebug/SvcChangeKernelTraceState. */
|
||||
/* Mesosphere may wish to not require this, as we'd ideally keep ProgramVerification enabled for userland. */
|
||||
ts->disable_kernel_debugging = !GetConfigBool(smc::ConfigItem::DisableProgramVerification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControl::InitializePhase1() {
|
||||
/* Enable KTargetSystem. */
|
||||
KTargetSystem::SetInitialized();
|
||||
|
||||
/* Check KTargetSystem was configured correctly. */
|
||||
{
|
||||
/* Check IsDebugMode. */
|
||||
{
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugMode() == GetConfigBool(smc::ConfigItem::IsDebugMode));
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugLoggingEnabled() == GetConfigBool(smc::ConfigItem::IsDebugMode));
|
||||
}
|
||||
|
||||
/* Check Kernel Configuration. */
|
||||
{
|
||||
const auto kernel_config = util::BitPack32{GetConfigU32(smc::ConfigItem::KernelConfiguration)};
|
||||
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugMemoryFillEnabled() == kernel_config.Get<smc::KernelConfiguration::DebugFillMemory>());
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsUserExceptionHandlersEnabled() == kernel_config.Get<smc::KernelConfiguration::EnableUserExceptionHandlers>());
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled() == !kernel_config.Get<smc::KernelConfiguration::DisableDynamicResourceLimits>());
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsUserPmuAccessEnabled() == kernel_config.Get<smc::KernelConfiguration::EnableUserPmuAccess>());
|
||||
|
||||
MESOSPHERE_ABORT_UNLESS(g_call_smc_on_panic == kernel_config.Get<smc::KernelConfiguration::UseSecureMonitorPanicCall>());
|
||||
}
|
||||
|
||||
/* Check Kernel Debugging. */
|
||||
{
|
||||
MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsKernelDebuggingEnabled() == GetConfigBool(smc::ConfigItem::DisableProgramVerification));
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize random and resource limit. */
|
||||
{
|
||||
u64 seed;
|
||||
smc::GenerateRandomBytes(std::addressof(seed), sizeof(seed));
|
||||
KSystemControlBase::InitializePhase1Base(seed);
|
||||
}
|
||||
|
||||
/* Configure the Kernel Carveout region. */
|
||||
{
|
||||
const auto carveout = KMemoryLayout::GetCarveoutRegionExtents();
|
||||
MESOSPHERE_ABORT_UNLESS(carveout.GetEndAddress() != 0);
|
||||
|
||||
smc::ConfigureCarveout(0, carveout.GetAddress(), carveout.GetSize());
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControl::InitializePhase2() {
|
||||
/* Initialize the sleep manager. */
|
||||
KSleepManager::Initialize();
|
||||
|
||||
/* Get the secure applet memory. */
|
||||
const auto &secure_applet_memory = KMemoryLayout::GetSecureAppletMemoryRegion();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(secure_applet_memory.GetSize() == SecureAppletMemorySize);
|
||||
|
||||
g_secure_applet_memory_address = secure_applet_memory.GetAddress();
|
||||
|
||||
/* Initialize KTrace (and potentially other init). */
|
||||
KSystemControlBase::InitializePhase2();
|
||||
}
|
||||
|
||||
u32 KSystemControl::GetCreateProcessMemoryPool() {
|
||||
return KMemoryManager::Pool_Unsafe;
|
||||
}
|
||||
|
||||
/* Privileged Access. */
|
||||
void KSystemControl::ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) {
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(address, sizeof(u32)));
|
||||
MESOSPHERE_ABORT_UNLESS(IsRegisterAccessibleToPrivileged(address));
|
||||
MESOSPHERE_ABORT_UNLESS(smc::ReadWriteRegister(out, address, mask, value));
|
||||
}
|
||||
|
||||
Result KSystemControl::ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) {
|
||||
R_UNLESS(AMS_LIKELY(util::IsAligned(address, sizeof(u32))), svc::ResultInvalidAddress());
|
||||
R_UNLESS(AMS_LIKELY(IsRegisterAccessibleToUser(address)), svc::ResultInvalidAddress());
|
||||
R_UNLESS(AMS_LIKELY(smc::ReadWriteRegister(out, address, mask, value)), svc::ResultInvalidAddress());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Randomness. */
|
||||
void KSystemControl::GenerateRandom(u64 *dst, size_t count) {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(count <= 7);
|
||||
smc::GenerateRandomBytes(dst, count * sizeof(u64));
|
||||
}
|
||||
|
||||
u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) {
|
||||
KScopedInterruptDisable intr_disable;
|
||||
KScopedSpinLock lk(s_random_lock);
|
||||
|
||||
|
||||
if (AMS_LIKELY(!s_uninitialized_random_generator)) {
|
||||
return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); });
|
||||
} else {
|
||||
return KSystemControlBase::GenerateUniformRange(min, max, GenerateRandomU64FromSmc);
|
||||
}
|
||||
}
|
||||
|
||||
u64 KSystemControl::GenerateRandomU64() {
|
||||
KScopedInterruptDisable intr_disable;
|
||||
KScopedSpinLock lk(s_random_lock);
|
||||
|
||||
if (AMS_LIKELY(!s_uninitialized_random_generator)) {
|
||||
return s_random_generator.GenerateRandomU64();
|
||||
} else {
|
||||
return GenerateRandomU64FromSmc();
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControl::SleepSystem() {
|
||||
MESOSPHERE_LOG("SleepSystem() was called\n");
|
||||
KSleepManager::SleepSystem();
|
||||
}
|
||||
|
||||
void KSystemControl::StopSystem(void *arg) {
|
||||
if (arg != nullptr) {
|
||||
/* Get the address of the legacy IRAM region. */
|
||||
const KVirtualAddress iram_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsIram) + 64_KB;
|
||||
constexpr size_t RebootPayloadSize = 0x24000;
|
||||
|
||||
/* NOTE: Atmosphere extension; if we received an exception context from Panic(), */
|
||||
/* generate a fatal error report using it. */
|
||||
const KExceptionContext *e_ctx = static_cast<const KExceptionContext *>(arg);
|
||||
auto *f_ctx = GetPointer<::ams::impl::FatalErrorContext>(iram_address + 0x2E000);
|
||||
|
||||
/* Clear the fatal context. */
|
||||
std::memset(f_ctx, 0xCC, sizeof(*f_ctx));
|
||||
|
||||
/* Set metadata. */
|
||||
f_ctx->magic = ::ams::impl::FatalErrorContext::Magic;
|
||||
f_ctx->error_desc = ::ams::impl::FatalErrorContext::KernelPanicDesc;
|
||||
f_ctx->program_id = (static_cast<u64>(util::FourCC<'M', 'E', 'S', 'O'>::Code) << 0) | (static_cast<u64>(util::FourCC<'S', 'P', 'H', 'R'>::Code) << 32);
|
||||
|
||||
/* Set identifier. */
|
||||
f_ctx->report_identifier = KHardwareTimer::GetTick();
|
||||
|
||||
/* Set module base. */
|
||||
f_ctx->module_base = KMemoryLayout::GetKernelCodeRegionExtents().GetAddress();
|
||||
|
||||
/* Set afsr1. */
|
||||
f_ctx->afsr0 = GetVersionIdentifier();
|
||||
f_ctx->afsr1 = static_cast<u32>(kern::GetTargetFirmware());
|
||||
|
||||
/* Set efsr/far. */
|
||||
f_ctx->far = cpu::GetFarEl1();
|
||||
f_ctx->esr = cpu::GetEsrEl1();
|
||||
|
||||
/* Copy registers. */
|
||||
for (size_t i = 0; i < util::size(e_ctx->x); ++i) {
|
||||
f_ctx->gprs[i] = e_ctx->x[i];
|
||||
}
|
||||
f_ctx->sp = e_ctx->sp;
|
||||
f_ctx->pc = cpu::GetElrEl1();
|
||||
|
||||
/* Dump stack trace. */
|
||||
{
|
||||
uintptr_t fp = e_ctx->x[29];
|
||||
for (f_ctx->stack_trace_size = 0; f_ctx->stack_trace_size < ::ams::impl::FatalErrorContext::MaxStackTrace && fp != 0 && util::IsAligned(fp, 0x10) && cpu::GetPhysicalAddressWritable(nullptr, fp, true); ++(f_ctx->stack_trace_size)) {
|
||||
struct {
|
||||
uintptr_t fp;
|
||||
uintptr_t lr;
|
||||
} *stack_frame = reinterpret_cast<decltype(stack_frame)>(fp);
|
||||
|
||||
f_ctx->stack_trace[f_ctx->stack_trace_size] = stack_frame->lr;
|
||||
fp = stack_frame->fp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump stack. */
|
||||
{
|
||||
uintptr_t sp = e_ctx->sp;
|
||||
for (f_ctx->stack_dump_size = 0; f_ctx->stack_dump_size < ::ams::impl::FatalErrorContext::MaxStackDumpSize && cpu::GetPhysicalAddressWritable(nullptr, sp + f_ctx->stack_dump_size, true); f_ctx->stack_dump_size += sizeof(u64)) {
|
||||
*reinterpret_cast<u64 *>(f_ctx->stack_dump + f_ctx->stack_dump_size) = *reinterpret_cast<u64 *>(sp + f_ctx->stack_dump_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get a payload address. */
|
||||
const KMemoryRegion *cached_region = nullptr;
|
||||
u64 reboot_payload_paddr = 0;
|
||||
if (smc::TryGetConfig(std::addressof(reboot_payload_paddr), 1, smc::ConfigItem::ExospherePayloadAddress) && KMemoryLayout::IsLinearMappedPhysicalAddress(cached_region, reboot_payload_paddr, RebootPayloadSize)) {
|
||||
/* If we have a payload, reboot to it. */
|
||||
const KVirtualAddress reboot_payload = KMemoryLayout::GetLinearVirtualAddress(KPhysicalAddress(reboot_payload_paddr));
|
||||
|
||||
/* Clear IRAM. */
|
||||
std::memset(GetVoidPointer(iram_address), 0xCC, RebootPayloadSize);
|
||||
|
||||
/* Copy the payload to iram. */
|
||||
for (size_t i = 0; i < RebootPayloadSize / sizeof(u32); ++i) {
|
||||
GetPointer<volatile u32>(iram_address)[i] = GetPointer<volatile u32>(reboot_payload)[i];
|
||||
}
|
||||
}
|
||||
smc::SetConfig(smc::ConfigItem::ExosphereNeedsReboot, smc::UserRebootType_ToFatalError);
|
||||
}
|
||||
|
||||
if (g_call_smc_on_panic) {
|
||||
/* If we should, instruct the secure monitor to display a panic screen. */
|
||||
smc::ShowError(0xF00);
|
||||
}
|
||||
|
||||
AMS_INFINITE_LOOP();
|
||||
}
|
||||
|
||||
/* User access. */
|
||||
void KSystemControl::CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* Invoke the secure monitor. */
|
||||
return smc::CallSecureMonitorFromUser(args);
|
||||
}
|
||||
|
||||
/* Secure Memory. */
|
||||
size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) {
|
||||
if (pool == KMemoryManager::Pool_Applet) {
|
||||
return 0;
|
||||
} else {
|
||||
return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool);
|
||||
}
|
||||
}
|
||||
|
||||
Result KSystemControl::AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool) {
|
||||
/* Applet secure memory is handled separately. */
|
||||
if (pool == KMemoryManager::Pool_Applet) {
|
||||
R_RETURN(AllocateSecureMemoryForApplet(out, size));
|
||||
}
|
||||
|
||||
/* Ensure the size is aligned. */
|
||||
const size_t alignment = (pool == KMemoryManager::Pool_System ? PageSize : SecureAlignment);
|
||||
R_UNLESS(util::IsAligned(size, alignment), svc::ResultInvalidSize());
|
||||
|
||||
/* Allocate the memory. */
|
||||
const size_t num_pages = size / PageSize;
|
||||
const KPhysicalAddress paddr = Kernel::GetMemoryManager().AllocateAndOpenContinuous(num_pages, alignment / PageSize, KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool), KMemoryManager::Direction_FromFront));
|
||||
R_UNLESS(paddr != Null<KPhysicalAddress>, svc::ResultOutOfMemory());
|
||||
|
||||
/* Ensure we don't leak references to the memory on error. */
|
||||
ON_RESULT_FAILURE { Kernel::GetMemoryManager().Close(paddr, num_pages); };
|
||||
|
||||
/* If the memory isn't already secure, set it as secure. */
|
||||
if (pool != KMemoryManager::Pool_System) {
|
||||
/* Set the secure region. */
|
||||
R_UNLESS(SetSecureRegion(paddr, size), svc::ResultOutOfMemory());
|
||||
}
|
||||
|
||||
/* We succeeded. */
|
||||
*out = KPageTable::GetHeapVirtualAddress(paddr);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KSystemControl::FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool) {
|
||||
/* Applet secure memory is handled separately. */
|
||||
if (pool == KMemoryManager::Pool_Applet) {
|
||||
return FreeSecureMemoryForApplet(address, size);
|
||||
}
|
||||
|
||||
/* Ensure the size is aligned. */
|
||||
const size_t alignment = (pool == KMemoryManager::Pool_System ? PageSize : SecureAlignment);
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(address), alignment));
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, alignment));
|
||||
|
||||
/* If the memory isn't secure system, reset the secure region. */
|
||||
if (pool != KMemoryManager::Pool_System) {
|
||||
/* Check that the size being freed is the current secure region size. */
|
||||
MESOSPHERE_ABORT_UNLESS(g_secure_region_size == size);
|
||||
|
||||
/* Get the physical address. */
|
||||
const KPhysicalAddress paddr = KPageTable::GetHeapPhysicalAddress(address);
|
||||
MESOSPHERE_ABORT_UNLESS(paddr != Null<KPhysicalAddress>);
|
||||
|
||||
/* Check that the memory being freed is the current secure region. */
|
||||
MESOSPHERE_ABORT_UNLESS(paddr == g_secure_region_phys_addr);
|
||||
|
||||
/* Free the secure region. */
|
||||
MESOSPHERE_ABORT_UNLESS(SetSecureRegion(paddr, 0));
|
||||
}
|
||||
|
||||
/* Close the secure region's pages. */
|
||||
Kernel::GetMemoryManager().Close(KPageTable::GetHeapPhysicalAddress(address), size / PageSize);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_lps_driver.hpp"
|
||||
#include "kern_k_sleep_manager.hpp"
|
||||
|
||||
#include "kern_bpmp_api.hpp"
|
||||
#include "kern_atomics_registers.hpp"
|
||||
#include "kern_ictlr_registers.hpp"
|
||||
#include "kern_sema_registers.hpp"
|
||||
|
||||
namespace ams::kern::board::nintendo::nx::lps {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline int ChannelCount = 12;
|
||||
|
||||
constexpr inline TimeSpan ChannelTimeout = TimeSpan::FromSeconds(1);
|
||||
|
||||
constinit bool g_lps_init_done = false;
|
||||
constinit bool g_bpmp_connected = false;
|
||||
constinit bool g_bpmp_mail_initialized = false;
|
||||
|
||||
constinit KSpinLock g_bpmp_mrq_lock;
|
||||
|
||||
constinit KVirtualAddress g_evp_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_flow_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_prictlr_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_sema_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_atomics_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_clkrst_address = Null<KVirtualAddress>;
|
||||
constinit KVirtualAddress g_pmc_address = Null<KVirtualAddress>;
|
||||
|
||||
constinit ChannelData g_channel_area[ChannelCount] = {};
|
||||
|
||||
constinit u32 g_csite_clk_source = 0;
|
||||
|
||||
ALWAYS_INLINE u32 Read(KVirtualAddress address) {
|
||||
return *GetPointer<volatile u32>(address);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Write(KVirtualAddress address, u32 value) {
|
||||
*GetPointer<volatile u32>(address) = value;
|
||||
}
|
||||
|
||||
void InitializeDeviceVirtualAddresses() {
|
||||
/* Retrieve randomized mappings. */
|
||||
g_evp_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsExceptionVectors);
|
||||
g_flow_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsFlowController);
|
||||
g_prictlr_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsPrimaryICtlr);
|
||||
g_sema_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsSemaphore);
|
||||
g_atomics_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsAtomics);
|
||||
g_clkrst_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsClkRst);
|
||||
g_pmc_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_PowerManagementController);
|
||||
}
|
||||
|
||||
/* NOTE: linux "do_cc4_init" */
|
||||
void ConfigureCc3AndCc4() {
|
||||
/* Configure CC4/CC3 as enabled with time threshold as 2 microseconds. */
|
||||
Write(g_flow_address + FLOW_CTLR_CC4_HVC_CONTROL, (0x2 << 3) | 0x1);
|
||||
|
||||
/* Configure Retention with threshold 2 microseconds. */
|
||||
Write(g_flow_address + FLOW_CTLR_CC4_RETENTION_CONTROL, (0x2 << 3));
|
||||
|
||||
/* Configure CC3/CC3 retry threshold as 2 microseconds. */
|
||||
Write(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY, (0x2 << 3));
|
||||
|
||||
/* Read the retry register to ensure writes take. */
|
||||
Read(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY);
|
||||
}
|
||||
|
||||
constexpr bool IsValidMessageDataSize(int size) {
|
||||
return 0 <= size && size < MessageDataSizeMax;
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_valid_txfer" */
|
||||
constexpr bool IsTransferValid(const void *ob, int ob_size, void *ib, int ib_size) {
|
||||
return IsValidMessageDataSize(ob_size) && IsValidMessageDataSize(ib_size) && (ob_size == 0 || ob != nullptr) && (ib_size == 0 || ib != nullptr);
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_ob_channel" */
|
||||
int BpmpGetOutboundChannel() {
|
||||
return GetCurrentCoreId();
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_ch_sta" */
|
||||
u32 BpmpGetChannelState(int channel) {
|
||||
cpu::DataSynchronizationBarrier();
|
||||
return Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) & CH_MASK(channel);
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_master_free" */
|
||||
bool BpmpIsMasterFree(int channel) {
|
||||
return BpmpGetChannelState(channel) == MA_FREE(channel);
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_master_acked" */
|
||||
bool BpmpIsMasterAcked(int channel) {
|
||||
return BpmpGetChannelState(channel) == MA_ACKD(channel);
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_signal_slave" */
|
||||
void BpmpSignalSlave(int channel) {
|
||||
Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, CH_MASK(channel));
|
||||
cpu::DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_free_master" */
|
||||
void BpmpFreeMaster(int channel) {
|
||||
/* Transition state from ack'd to free. */
|
||||
Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, ((MA_ACKD(channel)) ^ (MA_FREE(channel))));
|
||||
cpu::DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_ring_doorbell" */
|
||||
void BpmpRingDoorbell() {
|
||||
Write(g_prictlr_address + ICTLR_FIR_SET(INT_SHR_SEM_OUTBOX_IBF), FIR_BIT(INT_SHR_SEM_OUTBOX_IBF));
|
||||
cpu::DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_wait_master_free" */
|
||||
int BpmpWaitMasterFree(int channel) {
|
||||
/* Check if the master is already freed. */
|
||||
if (BpmpIsMasterFree(channel)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Spin-poll for the master to be freed until timeout occurs. */
|
||||
const auto start_tick = KHardwareTimer::GetTick();
|
||||
const auto timeout = ams::svc::Tick(ChannelTimeout);
|
||||
do {
|
||||
if (BpmpIsMasterFree(channel)) {
|
||||
return 0;
|
||||
}
|
||||
} while ((KHardwareTimer::GetTick() - start_tick) < timeout);
|
||||
|
||||
/* The master didn't become free. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_wait_ack" */
|
||||
int BpmpWaitAck(int channel) {
|
||||
/* Check if the master is already ACK'd. */
|
||||
if (BpmpIsMasterAcked(channel)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Spin-poll for the master to be ACK'd until timeout occurs. */
|
||||
const auto start_tick = KHardwareTimer::GetTick();
|
||||
const auto timeout = ams::svc::Tick(ChannelTimeout);
|
||||
do {
|
||||
if (BpmpIsMasterAcked(channel)) {
|
||||
return 0;
|
||||
}
|
||||
} while ((KHardwareTimer::GetTick() - start_tick) < timeout);
|
||||
|
||||
/* The master didn't get ACK'd. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* NOTE: linux "bpmp_write_ch" */
|
||||
int BpmpWriteChannel(int channel, int mrq, int flags, const void *data, size_t data_size) {
|
||||
/* Wait to be able to master the mailbox. */
|
||||
if (int res = BpmpWaitMasterFree(channel); res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Prepare the message. */
|
||||
MailboxData *mb = g_channel_area[channel].ob;
|
||||
mb->code = mrq;
|
||||
mb->flags = flags;
|
||||
if (data != nullptr) {
|
||||
std::memcpy(mb->data, data, data_size);
|
||||
}
|
||||
|
||||
/* Signal to slave that message is available. */
|
||||
BpmpSignalSlave(channel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NOTE: linux "__bpmp_read_ch" */
|
||||
int BpmpReadChannel(int channel, void *data, size_t data_size) {
|
||||
/* Get the message. */
|
||||
MailboxData *mb = g_channel_area[channel].ib;
|
||||
|
||||
/* Copy any return data. */
|
||||
if (data != nullptr) {
|
||||
std::memcpy(data, mb->data, data_size);
|
||||
}
|
||||
|
||||
/* Free the channel. */
|
||||
BpmpFreeMaster(channel);
|
||||
|
||||
/* Return result. */
|
||||
return mb->code;
|
||||
}
|
||||
|
||||
/* NOTE: linux "tegra_bpmp_send_receive_atomic" or "tegra_bpmp_send_receive". */
|
||||
int BpmpSendAndReceive(int mrq, const void *ob, int ob_size, void *ib, int ib_size) {
|
||||
/* Validate that the data transfer is valid. */
|
||||
if (!IsTransferValid(ob, ob_size, ib, ib_size)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate that the bpmp is connected. */
|
||||
if (!g_bpmp_connected) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Acquire exclusive access to send mrqs. */
|
||||
KScopedSpinLock lk(g_bpmp_mrq_lock);
|
||||
|
||||
/* Send the message. */
|
||||
int channel = BpmpGetOutboundChannel();
|
||||
if (int res = BpmpWriteChannel(channel, mrq, BPMP_MSG_DO_ACK, ob, ob_size); res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Send "doorbell" irq to the bpmp firmware. */
|
||||
BpmpRingDoorbell();
|
||||
|
||||
/* Wait for the bpmp firmware to acknowledge our request. */
|
||||
if (int res = BpmpWaitAck(channel); res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Read the data the bpmp sent back. */
|
||||
return BpmpReadChannel(channel, ib, ib_size);
|
||||
}
|
||||
|
||||
/* NOTE: linux "tegra_bpmp_send" */
|
||||
int BpmpSend(int mrq, const void *ob, int ob_size) {
|
||||
/* Validate that the data transfer is valid. */
|
||||
if (!IsTransferValid(ob, ob_size, nullptr, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate that the bpmp is connected. */
|
||||
if (!g_bpmp_connected) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Acquire exclusive access to send mrqs. */
|
||||
KScopedSpinLock lk(g_bpmp_mrq_lock);
|
||||
|
||||
/* Send the message. */
|
||||
int channel = BpmpGetOutboundChannel();
|
||||
if (int res = BpmpWriteChannel(channel, mrq, 0, ob, ob_size); res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Send "doorbell" irq to the bpmp firmware. */
|
||||
BpmpRingDoorbell();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NOTE: modified linux "tegra_bpmp_enable_suspend" */
|
||||
int BpmpEnableSuspend(int mode, int flags) {
|
||||
/* Prepare data for bpmp. */
|
||||
const s32 data[] = { mode, flags };
|
||||
|
||||
/* Send the data. */
|
||||
return BpmpSend(MRQ_ENABLE_SUSPEND, data, sizeof(data));
|
||||
}
|
||||
|
||||
/* NOTE: linux "__bpmp_connect" */
|
||||
int ConnectToBpmp() {
|
||||
/* Check if we've already connected. */
|
||||
if (g_bpmp_connected) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Verify that the resource semaphore state is set. */
|
||||
if (Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the channels, which the bpmp firmware has configured in advance. */
|
||||
{
|
||||
const KVirtualAddress iram_virt_addr = KMemoryLayout::GetDeviceVirtualAddress (KMemoryRegionType_LegacyLpsIram);
|
||||
const KPhysicalAddress iram_phys_addr = KMemoryLayout::GetDevicePhysicalAddress(KMemoryRegionType_LegacyLpsIram);
|
||||
|
||||
for (auto i = 0; i < ChannelCount; ++i) {
|
||||
/* Trigger a get command for the desired channel. */
|
||||
Write(g_atomics_address + ATOMICS_AP0_TRIGGER, TRIGGER_CMD_GET | (i << 16));
|
||||
|
||||
/* Retrieve the channel phys-addr-in-iram, and convert it to a kernel address. */
|
||||
auto *ch = GetPointer<MailboxData>(iram_virt_addr + (Read(g_atomics_address + ATOMICS_AP0_RESULT(i)) - GetInteger(iram_phys_addr)));
|
||||
|
||||
/* Verify the channel isn't null. */
|
||||
/* NOTE: This is an utterly nonsense check, as this would require the bpmp firmware to specify */
|
||||
/* a phys-to-virt diff as an address. On 1.0.0, which had no ASLR, this was 0x8028C000. */
|
||||
/* However, Nintendo has the check, and we'll preserve it to be faithful. */
|
||||
if (ch == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set the channel in the channel area. */
|
||||
g_channel_area[i].ib = ch;
|
||||
g_channel_area[i].ob = ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark driver as connected to bpmp. */
|
||||
g_bpmp_connected = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NOTE: Modified linux "bpmp_mail_init" */
|
||||
int InitializeBpmpMail() {
|
||||
/* Check if we've already initialized. */
|
||||
if (g_bpmp_mail_initialized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark function as having been called. */
|
||||
g_bpmp_mail_initialized = true;
|
||||
|
||||
/* Forward declare result/reply variables. */
|
||||
int res, request = 0, reply = 0;
|
||||
|
||||
/* Try to connect to the bpmp. */
|
||||
if (res = ConnectToBpmp(); res != 0) {
|
||||
MESOSPHERE_LOG("bpmp: connect error returns %d\n", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Ensure that we can successfully ping the bpmp. */
|
||||
request = 1;
|
||||
if (res = BpmpSendAndReceive(MRQ_PING, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) {
|
||||
MESOSPHERE_LOG("bpmp: MRQ_PING error returns %d with reply %d\n", res, reply);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Configure the PMIC. */
|
||||
request = 1;
|
||||
if (res = BpmpSendAndReceive(MRQ_CPU_PMIC_SELECT, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) {
|
||||
MESOSPHERE_LOG("bpmp: MRQ_CPU_PMIC_SELECT for MAX77621 error returns %d with reply %d\n", res, reply);
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
if (!g_lps_init_done) {
|
||||
/* Get the addresses of the devices the driver needs. */
|
||||
InitializeDeviceVirtualAddresses();
|
||||
|
||||
/* Configure CC3/CC4. */
|
||||
ConfigureCc3AndCc4();
|
||||
|
||||
/* Initialize ccplex <-> bpmp mail. */
|
||||
/* NOTE: Nintendo does not check that this call succeeds. */
|
||||
InitializeBpmpMail();
|
||||
|
||||
g_lps_init_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
Result EnableSuspend(bool enable) {
|
||||
/* If we're not on core 0, there's nothing to do. */
|
||||
R_SUCCEED_IF(GetCurrentCoreId() != 0);
|
||||
|
||||
/* If we're not enabling suspend, there's nothing to do. */
|
||||
R_SUCCEED_IF(!enable);
|
||||
|
||||
/* Instruct BPMP to enable suspend-to-sc7. */
|
||||
R_UNLESS(BpmpEnableSuspend(TEGRA_BPMP_PM_SC7, 0) == 0, svc::ResultInvalidState());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg) {
|
||||
/* Verify that we're allowed to perform suspension. */
|
||||
MESOSPHERE_ABORT_UNLESS(g_lps_init_done);
|
||||
MESOSPHERE_ABORT_UNLESS(GetCurrentCoreId() == 0);
|
||||
|
||||
/* Save the CSITE clock source. */
|
||||
g_csite_clk_source = Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE);
|
||||
|
||||
/* Configure CSITE clock source as CLK_M. */
|
||||
Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, (0x6 << 29));
|
||||
|
||||
/* Clear the top bit of PMC_SCRATCH4. */
|
||||
Write(g_pmc_address + APBDEV_PMC_SCRATCH4, Read(g_pmc_address + APBDEV_PMC_SCRATCH4) & 0x7FFFFFFF);
|
||||
|
||||
/* Write 1 to PMC_SCRATCH0. This will cause the bootrom to use the warmboot code-path. */
|
||||
Write(g_pmc_address + APBDEV_PMC_SCRATCH0, 1);
|
||||
|
||||
/* Read PMC_SCRATCH0 to be sure our write takes. */
|
||||
Read(g_pmc_address + APBDEV_PMC_SCRATCH0);
|
||||
|
||||
/* Invoke the sleep hander. */
|
||||
KSleepManager::CpuSleepHandler(arg, entry, entry_arg);
|
||||
|
||||
/* Disable deep power down. */
|
||||
Write(g_pmc_address + APBDEV_PMC_DPD_ENABLE, 0);
|
||||
|
||||
/* Restore the saved CSITE clock source. */
|
||||
Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, g_csite_clk_source);
|
||||
|
||||
/* Read the CSITE clock source to ensure our configuration takes. */
|
||||
Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE);
|
||||
|
||||
/* Configure CC3/CC4. */
|
||||
ConfigureCc3AndCc4();
|
||||
}
|
||||
|
||||
void ResumeBpmpFirmware() {
|
||||
/* Halt the bpmp. */
|
||||
Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x2 << 29));
|
||||
|
||||
/* Hold the bpmp in reset. */
|
||||
Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, 0x2);
|
||||
|
||||
/* Read the saved bpmp entrypoint, and write it to the relevant exception vector. */
|
||||
const u32 bpmp_entry = Read(g_pmc_address + APBDEV_PMC_SCRATCH39);
|
||||
Write(g_evp_address + EVP_COP_RESET_VECTOR, bpmp_entry);
|
||||
|
||||
/* Verify that we can read back the address we wrote. */
|
||||
while (Read(g_evp_address + EVP_COP_RESET_VECTOR) != bpmp_entry) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* Spin for 40 ticks, to give enough time for the bpmp to be reset. */
|
||||
const auto start_tick = KHardwareTimer::GetTick();
|
||||
do {
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
} while ((KHardwareTimer::GetTick() - start_tick) < 40);
|
||||
|
||||
/* Take the bpmp out of reset. */
|
||||
Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, 0x2);
|
||||
|
||||
/* Resume the bpmp. */
|
||||
Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x0 << 29));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::board::nintendo::nx {
|
||||
|
||||
namespace lps {
|
||||
|
||||
void Initialize();
|
||||
Result EnableSuspend(bool enable);
|
||||
void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg);
|
||||
void ResumeBpmpFirmware();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_secure_monitor.hpp"
|
||||
|
||||
namespace ams::kern::board::nintendo::nx::smc {
|
||||
|
||||
namespace {
|
||||
|
||||
enum UserFunctionId : u32 {
|
||||
UserFunctionId_SetConfig = 0xC3000401,
|
||||
UserFunctionId_GetConfigUser = 0xC3000002,
|
||||
UserFunctionId_GetResult = 0xC3000003,
|
||||
UserFunctionId_GetResultData = 0xC3000404,
|
||||
UserFunctionId_ModularExponentiate = 0xC3000E05,
|
||||
UserFunctionId_GenerateRandomBytes = 0xC3000006,
|
||||
UserFunctionId_GenerateAesKek = 0xC3000007,
|
||||
UserFunctionId_LoadAesKey = 0xC3000008,
|
||||
UserFunctionId_ComputeAes = 0xC3000009,
|
||||
UserFunctionId_GenerateSpecificAesKey = 0xC300000A,
|
||||
UserFunctionId_ComputeCmac = 0xC300040B,
|
||||
UserFunctionId_ReencryptDeviceUniqueData = 0xC300D60C,
|
||||
UserFunctionId_DecryptDeviceUniqueData = 0xC300100D,
|
||||
UserFunctionId_ModularExponentiateByStorageKey = 0xC300060F,
|
||||
UserFunctionId_PrepareEsDeviceUniqueKey = 0xC3000610,
|
||||
UserFunctionId_LoadPreparedAesKey = 0xC3000011,
|
||||
UserFunctionId_PrepareEsCommonTitleKey = 0xC3000012,
|
||||
};
|
||||
|
||||
enum FunctionId : u32 {
|
||||
FunctionId_GetConfig = 0xC3000004,
|
||||
FunctionId_GenerateRandomBytes = 0xC3000005,
|
||||
FunctionId_ShowError = 0xC3000006,
|
||||
FunctionId_ConfigureCarveout = 0xC3000007,
|
||||
FunctionId_ReadWriteRegister = 0xC3000008,
|
||||
|
||||
/* NOTE: Atmosphere extension for mesosphere. This ID is subject to change at any time. */
|
||||
FunctionId_SetConfig = 0xC3000409,
|
||||
};
|
||||
|
||||
constexpr size_t GenerateRandomBytesSizeMax = sizeof(::ams::svc::lp64::SecureMonitorArguments) - sizeof(::ams::svc::lp64::SecureMonitorArguments{}.r[0]);
|
||||
|
||||
/* Global lock for generate random bytes. */
|
||||
constinit KSpinLock g_generate_random_lock;
|
||||
|
||||
bool TryGetConfigImpl(u64 *out, size_t num_qwords, ConfigItem config_item) {
|
||||
/* Create the arguments .*/
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_GetConfig, static_cast<u32>(config_item) } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* If successful, copy the output. */
|
||||
const bool success = static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
if (AMS_LIKELY(success)) {
|
||||
for (size_t i = 0; i < num_qwords && i < 7; i++) {
|
||||
out[i] = args.r[1 + i];
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SetConfigImpl(ConfigItem config_item, u64 value) {
|
||||
/* Create the arguments .*/
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_SetConfig, static_cast<u32>(config_item), 0, value } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* Return whether the call was successful. */
|
||||
return static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
}
|
||||
|
||||
bool ReadWriteRegisterImpl(u32 *out, u64 address, u32 mask, u32 value) {
|
||||
/* Create the arguments .*/
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ReadWriteRegister, address, mask, value } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* Unconditionally write the output. */
|
||||
*out = static_cast<u32>(args.r[1]);
|
||||
|
||||
/* Return whether the call was successful. */
|
||||
return static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
}
|
||||
|
||||
bool GenerateRandomBytesImpl(void *dst, size_t size) {
|
||||
/* Create the arguments. */
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_GenerateRandomBytes, size } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* If successful, copy the output. */
|
||||
const bool success = static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
if (AMS_LIKELY(success)) {
|
||||
std::memcpy(dst, std::addressof(args.r[1]), size);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ConfigureCarveoutImpl(size_t which, uintptr_t address, size_t size) {
|
||||
/* Create the arguments .*/
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ConfigureCarveout, static_cast<u64>(which), static_cast<u64>(address), static_cast<u64>(size) } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* Return whether the call was successful. */
|
||||
return static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
}
|
||||
|
||||
bool ShowErrorImpl(u32 color) {
|
||||
/* Create the arguments .*/
|
||||
ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ShowError, color } };
|
||||
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r);
|
||||
|
||||
/* Return whether the call was successful. */
|
||||
return static_cast<SmcResult>(args.r[0]) == SmcResult::Success;
|
||||
}
|
||||
|
||||
void CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* Call into the secure monitor. */
|
||||
::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_User>(args->r);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* SMC functionality needed for init. */
|
||||
namespace init {
|
||||
|
||||
void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) {
|
||||
/* Ensure we successfully get the config. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(TryGetConfigImpl(out, num_qwords, config_item));
|
||||
}
|
||||
|
||||
void GenerateRandomBytes(void *dst, size_t size) {
|
||||
/* Check that the size is valid. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(0 < size && size <= GenerateRandomBytesSizeMax);
|
||||
|
||||
/* Ensure we successfully generate the random bytes. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(GenerateRandomBytesImpl(dst, size));
|
||||
}
|
||||
|
||||
void ReadWriteRegister(u32 *out, u64 address, u32 mask, u32 value) {
|
||||
/* Ensure we successfully access the register. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(ReadWriteRegisterImpl(out, address, mask, value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TryGetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Get the config. */
|
||||
return TryGetConfigImpl(out, num_qwords, config_item);
|
||||
}
|
||||
|
||||
void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) {
|
||||
/* Ensure we successfully get the config. */
|
||||
MESOSPHERE_ABORT_UNLESS(TryGetConfig(out, num_qwords, config_item));
|
||||
}
|
||||
|
||||
bool SetConfig(ConfigItem config_item, u64 value) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Set the config. */
|
||||
return SetConfigImpl(config_item, value);
|
||||
}
|
||||
|
||||
bool ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Access the register. */
|
||||
return ReadWriteRegisterImpl(out, address, mask, value);
|
||||
}
|
||||
|
||||
void ConfigureCarveout(size_t which, uintptr_t address, size_t size) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Ensure that we successfully configure the carveout. */
|
||||
MESOSPHERE_ABORT_UNLESS(ConfigureCarveoutImpl(which, address, size));
|
||||
}
|
||||
|
||||
void GenerateRandomBytes(void *dst, size_t size) {
|
||||
/* Check that the size is valid. */
|
||||
MESOSPHERE_ABORT_UNLESS(0 < size && size <= GenerateRandomBytesSizeMax);
|
||||
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Acquire the exclusive right to generate random bytes. */
|
||||
KScopedSpinLock lk(g_generate_random_lock);
|
||||
|
||||
/* Ensure we successfully generate the random bytes. */
|
||||
MESOSPHERE_ABORT_UNLESS(GenerateRandomBytesImpl(dst, size));
|
||||
}
|
||||
|
||||
void ShowError(u32 color) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Ensure we successfully show the error. */
|
||||
MESOSPHERE_ABORT_UNLESS(ShowErrorImpl(color));
|
||||
}
|
||||
|
||||
void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* Disable interrupts. */
|
||||
KScopedInterruptDisable di;
|
||||
|
||||
/* Perform the call. */
|
||||
CallSecureMonitorFromUserImpl(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
#include <mesosphere/arch/arm64/kern_secure_monitor_base.hpp>
|
||||
|
||||
namespace ams::kern::board::nintendo::nx::smc {
|
||||
|
||||
/* Types. */
|
||||
enum SmcId {
|
||||
SmcId_User = 0,
|
||||
SmcId_Supervisor = 1,
|
||||
};
|
||||
|
||||
enum MemorySize {
|
||||
MemorySize_4GB = 0,
|
||||
MemorySize_6GB = 1,
|
||||
MemorySize_8GB = 2,
|
||||
};
|
||||
|
||||
enum MemoryArrangement {
|
||||
MemoryArrangement_4GB = 0,
|
||||
MemoryArrangement_4GBForAppletDev = 1,
|
||||
MemoryArrangement_4GBForSystemDev = 2,
|
||||
MemoryArrangement_6GB = 3,
|
||||
MemoryArrangement_6GBForAppletDev = 4,
|
||||
MemoryArrangement_8GB = 5,
|
||||
};
|
||||
|
||||
enum class ConfigItem : u32 {
|
||||
/* Standard config items. */
|
||||
DisableProgramVerification = 1,
|
||||
DramId = 2,
|
||||
SecurityEngineIrqNumber = 3,
|
||||
Version = 4,
|
||||
HardwareType = 5,
|
||||
IsRetail = 6,
|
||||
IsRecoveryBoot = 7,
|
||||
DeviceId = 8,
|
||||
BootReason = 9,
|
||||
MemoryMode = 10,
|
||||
IsDebugMode = 11,
|
||||
KernelConfiguration = 12,
|
||||
IsChargerHiZModeEnabled = 13,
|
||||
IsQuest = 14,
|
||||
RegulatorType = 15,
|
||||
DeviceUniqueKeyGeneration = 16,
|
||||
Package2Hash = 17,
|
||||
|
||||
/* Extension config items for exosphere. */
|
||||
ExosphereApiVersion = 65000,
|
||||
ExosphereNeedsReboot = 65001,
|
||||
ExosphereNeedsShutdown = 65002,
|
||||
ExosphereGitCommitHash = 65003,
|
||||
ExosphereHasRcmBugPatch = 65004,
|
||||
ExosphereBlankProdInfo = 65005,
|
||||
ExosphereAllowCalWrites = 65006,
|
||||
ExosphereEmummcType = 65007,
|
||||
ExospherePayloadAddress = 65008,
|
||||
ExosphereLogConfiguration = 65009,
|
||||
ExosphereForceEnableUsb30 = 65010,
|
||||
ExosphereSupportedHosVersion = 65011,
|
||||
};
|
||||
|
||||
enum class SmcResult {
|
||||
Success = 0,
|
||||
NotImplemented = 1,
|
||||
InvalidArgument = 2,
|
||||
InProgress = 3,
|
||||
NoAsyncOperation = 4,
|
||||
InvalidAsyncOperation = 5,
|
||||
NotPermitted = 6,
|
||||
};
|
||||
|
||||
struct KernelConfiguration {
|
||||
using DebugFillMemory = util::BitPack32::Field<0, 1, bool>;
|
||||
using EnableUserExceptionHandlers = util::BitPack32::Field<DebugFillMemory::Next, 1, bool>;
|
||||
using EnableUserPmuAccess = util::BitPack32::Field<EnableUserExceptionHandlers::Next, 1, bool>;
|
||||
using IncreaseThreadResourceLimit = util::BitPack32::Field<EnableUserPmuAccess::Next, 1, bool>;
|
||||
using DisableDynamicResourceLimits = util::BitPack32::Field<IncreaseThreadResourceLimit::Next, 1, bool>;
|
||||
using Reserved5 = util::BitPack32::Field<DisableDynamicResourceLimits::Next, 3, u32>;
|
||||
using UseSecureMonitorPanicCall = util::BitPack32::Field<Reserved5::Next, 1, bool>;
|
||||
using Reserved9 = util::BitPack32::Field<UseSecureMonitorPanicCall::Next, 7, u32>;
|
||||
using MemorySize = util::BitPack32::Field<Reserved9::Next, 2, smc::MemorySize>;
|
||||
};
|
||||
|
||||
enum UserRebootType {
|
||||
UserRebootType_None = 0,
|
||||
UserRebootType_ToRcm = 1,
|
||||
UserRebootType_ToPayload = 2,
|
||||
UserRebootType_ToFatalError = 3,
|
||||
};
|
||||
|
||||
void GenerateRandomBytes(void *dst, size_t size);
|
||||
bool TryGetConfig(u64 *out, size_t num_qwords, ConfigItem config_item);
|
||||
void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item);
|
||||
bool ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value);
|
||||
void ConfigureCarveout(size_t which, uintptr_t address, size_t size);
|
||||
|
||||
bool SetConfig(ConfigItem config_item, u64 value);
|
||||
|
||||
void ShowError(u32 color);
|
||||
|
||||
void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args);
|
||||
|
||||
namespace init {
|
||||
|
||||
void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item);
|
||||
void GenerateRandomBytes(void *dst, size_t size);
|
||||
void ReadWriteRegister(u32 *out, u64 address, u32 mask, u32 value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define RES_SEMA_SHRD_SMP_STA 0x000
|
||||
#define RES_SEMA_SHRD_SMP_SET 0x004
|
||||
#define RES_SEMA_SHRD_SMP_CLR 0x008
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_secure_monitor.hpp"
|
||||
|
||||
namespace ams::kern::board::qemu::virt {
|
||||
|
||||
/* User access. */
|
||||
void KSystemControl::CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* Invoke the secure monitor. */
|
||||
return smc::CallSecureMonitorFromUser(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_secure_monitor.hpp"
|
||||
|
||||
namespace ams::kern::board::qemu::virt::smc {
|
||||
|
||||
namespace {
|
||||
|
||||
enum UserFunctionId : u32 {
|
||||
UserFunctionId_SetConfig = 0xC3000401,
|
||||
UserFunctionId_GetConfig = 0xC3000002,
|
||||
UserFunctionId_GetResult = 0xC3000003,
|
||||
UserFunctionId_GetResultData = 0xC3000404,
|
||||
UserFunctionId_ModularExponentiate = 0xC3000E05,
|
||||
UserFunctionId_GenerateRandomBytes = 0xC3000006,
|
||||
UserFunctionId_GenerateAesKek = 0xC3000007,
|
||||
UserFunctionId_LoadAesKey = 0xC3000008,
|
||||
UserFunctionId_ComputeAes = 0xC3000009,
|
||||
UserFunctionId_GenerateSpecificAesKey = 0xC300000A,
|
||||
UserFunctionId_ComputeCmac = 0xC300040B,
|
||||
UserFunctionId_ReencryptDeviceUniqueData = 0xC300D60C,
|
||||
UserFunctionId_DecryptDeviceUniqueData = 0xC300100D,
|
||||
UserFunctionId_ModularExponentiateByStorageKey = 0xC300060F,
|
||||
UserFunctionId_PrepareEsDeviceUniqueKey = 0xC3000610,
|
||||
UserFunctionId_LoadPreparedAesKey = 0xC3000011,
|
||||
UserFunctionId_PrepareEsCommonTitleKey = 0xC3000012,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
MESOSPHERE_LOG("Received SMC [%p %p %p %p %p %p %p %p] from %s\n", reinterpret_cast<void *>(args->r[0]), reinterpret_cast<void *>(args->r[1]), reinterpret_cast<void *>(args->r[2]), reinterpret_cast<void *>(args->r[3]), reinterpret_cast<void *>(args->r[4]), reinterpret_cast<void *>(args->r[5]), reinterpret_cast<void *>(args->r[6]), reinterpret_cast<void *>(args->r[7]), GetCurrentProcess().GetName());
|
||||
|
||||
switch (args->r[0]) {
|
||||
case UserFunctionId_GetConfig:
|
||||
{
|
||||
switch (static_cast<ConfigItem>(args->r[1])) {
|
||||
case ConfigItem::ExosphereApiVersion:
|
||||
args->r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) |
|
||||
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) |
|
||||
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) |
|
||||
(static_cast<u64>(13) << 32) |
|
||||
(static_cast<u64>(GetTargetFirmware()) << 0);
|
||||
break;
|
||||
default:
|
||||
MESOSPHERE_PANIC("Unhandled GetConfig\n");
|
||||
}
|
||||
|
||||
args->r[0] = static_cast<u64>(SmcResult::Success);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MESOSPHERE_PANIC("Unhandled SMC [%p %p %p %p %p %p %p %p]", reinterpret_cast<void *>(args->r[0]), reinterpret_cast<void *>(args->r[1]), reinterpret_cast<void *>(args->r[2]), reinterpret_cast<void *>(args->r[3]), reinterpret_cast<void *>(args->r[4]), reinterpret_cast<void *>(args->r[5]), reinterpret_cast<void *>(args->r[6]), reinterpret_cast<void *>(args->r[7]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::board::qemu::virt::smc {
|
||||
|
||||
enum class ConfigItem : u32 {
|
||||
/* Standard config items. */
|
||||
DisableProgramVerification = 1,
|
||||
DramId = 2,
|
||||
SecurityEngineIrqNumber = 3,
|
||||
Version = 4,
|
||||
HardwareType = 5,
|
||||
IsRetail = 6,
|
||||
IsRecoveryBoot = 7,
|
||||
DeviceId = 8,
|
||||
BootReason = 9,
|
||||
MemoryMode = 10,
|
||||
IsDebugMode = 11,
|
||||
KernelConfiguration = 12,
|
||||
IsChargerHiZModeEnabled = 13,
|
||||
IsQuest = 14,
|
||||
RegulatorType = 15,
|
||||
DeviceUniqueKeyGeneration = 16,
|
||||
Package2Hash = 17,
|
||||
|
||||
/* Extension config items for exosphere. */
|
||||
ExosphereApiVersion = 65000,
|
||||
ExosphereNeedsReboot = 65001,
|
||||
ExosphereNeedsShutdown = 65002,
|
||||
ExosphereGitCommitHash = 65003,
|
||||
ExosphereHasRcmBugPatch = 65004,
|
||||
ExosphereBlankProdInfo = 65005,
|
||||
ExosphereAllowCalWrites = 65006,
|
||||
ExosphereEmummcType = 65007,
|
||||
ExospherePayloadAddress = 65008,
|
||||
ExosphereLogConfiguration = 65009,
|
||||
ExosphereForceEnableUsb30 = 65010,
|
||||
ExosphereSupportedHosVersion = 65011,
|
||||
};
|
||||
|
||||
enum class SmcResult {
|
||||
Success = 0,
|
||||
NotImplemented = 1,
|
||||
InvalidArgument = 2,
|
||||
InProgress = 3,
|
||||
NoAsyncOperation = 4,
|
||||
InvalidAsyncOperation = 5,
|
||||
NotPermitted = 6,
|
||||
};
|
||||
|
||||
void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::init::Elf {
|
||||
|
||||
/* API to apply relocations or call init array. */
|
||||
void ApplyRelocations(uintptr_t base_address, const Dyn *dynamic) {
|
||||
uintptr_t dyn_rel = 0;
|
||||
uintptr_t dyn_rela = 0;
|
||||
uintptr_t dyn_relr = 0;
|
||||
uintptr_t rel_count = 0;
|
||||
uintptr_t rela_count = 0;
|
||||
uintptr_t relr_sz = 0;
|
||||
uintptr_t rel_ent = 0;
|
||||
uintptr_t rela_ent = 0;
|
||||
uintptr_t relr_ent = 0;
|
||||
|
||||
/* Iterate over all tags, identifying important extents. */
|
||||
for (const Dyn *cur_entry = dynamic; cur_entry->GetTag() != DT_NULL; cur_entry++) {
|
||||
switch (cur_entry->GetTag()) {
|
||||
case DT_REL:
|
||||
dyn_rel = base_address + cur_entry->GetPtr();
|
||||
break;
|
||||
case DT_RELA:
|
||||
dyn_rela = base_address + cur_entry->GetPtr();
|
||||
break;
|
||||
case DT_RELR:
|
||||
dyn_relr = base_address + cur_entry->GetPtr();
|
||||
break;
|
||||
case DT_RELENT:
|
||||
rel_ent = cur_entry->GetValue();
|
||||
break;
|
||||
case DT_RELAENT:
|
||||
rela_ent = cur_entry->GetValue();
|
||||
break;
|
||||
case DT_RELRENT:
|
||||
relr_ent = cur_entry->GetValue();
|
||||
break;
|
||||
case DT_RELCOUNT:
|
||||
rel_count = cur_entry->GetValue();
|
||||
break;
|
||||
case DT_RELACOUNT:
|
||||
rela_count = cur_entry->GetValue();
|
||||
break;
|
||||
case DT_RELRSZ:
|
||||
relr_sz = cur_entry->GetValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply all Rel relocations */
|
||||
if (rel_count > 0) {
|
||||
/* Check that the rel relocations are applyable. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(dyn_rel != 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(rel_ent == sizeof(Elf::Rel));
|
||||
|
||||
for (size_t i = 0; i < rel_count; ++i) {
|
||||
const auto &rel = reinterpret_cast<const Elf::Rel *>(dyn_rel)[i];
|
||||
|
||||
/* Only allow architecture-specific relocations. */
|
||||
while (rel.GetType() != R_ARCHITECTURE_RELATIVE) { /* ... */ }
|
||||
|
||||
/* Apply the relocation. */
|
||||
Elf::Addr *target_address = reinterpret_cast<Elf::Addr *>(base_address + rel.GetOffset());
|
||||
*target_address += base_address;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply all Rela relocations. */
|
||||
if (rela_count > 0) {
|
||||
/* Check that the rela relocations are applyable. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(dyn_rela != 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(rela_ent == sizeof(Elf::Rela));
|
||||
|
||||
for (size_t i = 0; i < rela_count; ++i) {
|
||||
const auto &rela = reinterpret_cast<const Elf::Rela *>(dyn_rela)[i];
|
||||
|
||||
/* Only allow architecture-specific relocations. */
|
||||
while (rela.GetType() != R_ARCHITECTURE_RELATIVE) { /* ... */ }
|
||||
|
||||
/* Apply the relocation. */
|
||||
Elf::Addr *target_address = reinterpret_cast<Elf::Addr *>(base_address + rela.GetOffset());
|
||||
*target_address = base_address + rela.GetAddend();
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply all Relr relocations. */
|
||||
if (relr_sz >= sizeof(Elf::Relr)) {
|
||||
/* Check that the relr relocations are applyable. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(dyn_relr != 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(relr_ent == sizeof(Elf::Relr));
|
||||
|
||||
const size_t relr_count = relr_sz / sizeof(Elf::Relr);
|
||||
|
||||
Elf::Addr *where = nullptr;
|
||||
for (size_t i = 0; i < relr_count; ++i) {
|
||||
const auto &relr = reinterpret_cast<const Elf::Relr *>(dyn_relr)[i];
|
||||
|
||||
if (relr.IsLocation()) {
|
||||
/* Update location. */
|
||||
where = reinterpret_cast<Elf::Addr *>(base_address + relr.GetLocation());
|
||||
|
||||
/* Apply the relocation. */
|
||||
*(where++) += base_address;
|
||||
} else {
|
||||
/* Get the bitmap. */
|
||||
u64 bitmap = relr.GetBitmap();
|
||||
|
||||
/* Apply all relocations. */
|
||||
while (bitmap != 0) {
|
||||
const u64 next = util::CountTrailingZeros(bitmap);
|
||||
bitmap &= ~(static_cast<u64>(1) << next);
|
||||
where[next] += base_address;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
where += BITSIZEOF(bitmap) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CallInitArrayFuncs(uintptr_t init_array_start, uintptr_t init_array_end) {
|
||||
for (uintptr_t cur_entry = init_array_start; cur_entry < init_array_end; cur_entry += sizeof(void *)) {
|
||||
(*(void (**)())(cur_entry))();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::init {
|
||||
|
||||
/* For macro convenience. */
|
||||
using KSessionRequestMappings = KSessionRequest::SessionMappings::DynamicMappings;
|
||||
using KThreadLockInfo = KThread::LockWithPriorityInheritanceInfo;
|
||||
|
||||
#define SLAB_COUNT(CLASS) g_slab_resource_counts.num_##CLASS
|
||||
|
||||
#define FOREACH_SLAB_TYPE(HANDLER, ...) \
|
||||
HANDLER(KProcess, (SLAB_COUNT(KProcess)), ## __VA_ARGS__) \
|
||||
HANDLER(KThread, (SLAB_COUNT(KThread)), ## __VA_ARGS__) \
|
||||
HANDLER(KEvent, (SLAB_COUNT(KEvent)), ## __VA_ARGS__) \
|
||||
HANDLER(KInterruptEvent, (SLAB_COUNT(KInterruptEvent)), ## __VA_ARGS__) \
|
||||
HANDLER(KPort, (SLAB_COUNT(KPort)), ## __VA_ARGS__) \
|
||||
HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ## __VA_ARGS__) \
|
||||
HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ## __VA_ARGS__) \
|
||||
HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ## __VA_ARGS__) \
|
||||
HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ## __VA_ARGS__) \
|
||||
HANDLER(KDeviceAddressSpace, (SLAB_COUNT(KDeviceAddressSpace)), ## __VA_ARGS__) \
|
||||
HANDLER(KSession, (SLAB_COUNT(KSession)), ## __VA_ARGS__) \
|
||||
HANDLER(KSessionRequest, (SLAB_COUNT(KSession) * 2), ## __VA_ARGS__) \
|
||||
HANDLER(KLightSession, (SLAB_COUNT(KLightSession)), ## __VA_ARGS__) \
|
||||
HANDLER(KThreadLocalPage, (SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), ## __VA_ARGS__) \
|
||||
HANDLER(KObjectName, (SLAB_COUNT(KObjectName)), ## __VA_ARGS__) \
|
||||
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ## __VA_ARGS__) \
|
||||
HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ## __VA_ARGS__) \
|
||||
HANDLER(KDebug, (SLAB_COUNT(KDebug)), ## __VA_ARGS__) \
|
||||
HANDLER(KIoPool, (SLAB_COUNT(KIoPool)), ## __VA_ARGS__) \
|
||||
HANDLER(KIoRegion, (SLAB_COUNT(KIoRegion)), ## __VA_ARGS__) \
|
||||
HANDLER(KSessionRequestMappings, (SLAB_COUNT(KSessionRequestMappings)), ## __VA_ARGS__) \
|
||||
HANDLER(KSecureSystemResource, (SLAB_COUNT(KProcess)), ## __VA_ARGS__) \
|
||||
HANDLER(KThreadLockInfo, (SLAB_COUNT(KThread)), ## __VA_ARGS__)
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
#define DEFINE_SLAB_TYPE_ENUM_MEMBER(NAME, COUNT, ...) KSlabType_##NAME,
|
||||
|
||||
enum KSlabType : u32 {
|
||||
FOREACH_SLAB_TYPE(DEFINE_SLAB_TYPE_ENUM_MEMBER)
|
||||
KSlabType_Count,
|
||||
};
|
||||
|
||||
#undef DEFINE_SLAB_TYPE_ENUM_MEMBER
|
||||
|
||||
/* Constexpr counts. */
|
||||
constexpr size_t SlabCountKProcess = 80;
|
||||
constexpr size_t SlabCountKThread = 800;
|
||||
constexpr size_t SlabCountKEvent = 900;
|
||||
constexpr size_t SlabCountKInterruptEvent = 100;
|
||||
constexpr size_t SlabCountKPort = 384;
|
||||
constexpr size_t SlabCountKSharedMemory = 80;
|
||||
constexpr size_t SlabCountKTransferMemory = 200;
|
||||
constexpr size_t SlabCountKCodeMemory = 10;
|
||||
constexpr size_t SlabCountKDeviceAddressSpace = 300;
|
||||
constexpr size_t SlabCountKSession = 1133;
|
||||
constexpr size_t SlabCountKLightSession = 100;
|
||||
constexpr size_t SlabCountKObjectName = 7;
|
||||
constexpr size_t SlabCountKResourceLimit = 5;
|
||||
constexpr size_t SlabCountKDebug = cpu::NumCores;
|
||||
constexpr size_t SlabCountKIoPool = 1;
|
||||
constexpr size_t SlabCountKIoRegion = 6;
|
||||
constexpr size_t SlabcountKSessionRequestMappings = 40;
|
||||
|
||||
constexpr size_t SlabCountExtraKThread = (1024 + 256 + 256) - SlabCountKThread;
|
||||
|
||||
namespace test {
|
||||
|
||||
constexpr size_t RequiredSizeForExtraThreadCount = SlabCountExtraKThread * (sizeof(KThread) + (sizeof(KThreadLocalPage) / 8) + sizeof(KEventInfo));
|
||||
static_assert(RequiredSizeForExtraThreadCount <= KernelSlabHeapAdditionalSize);
|
||||
|
||||
static_assert(KernelPageBufferHeapSize == 2 * PageSize + (SlabCountKProcess + SlabCountKThread + (SlabCountKProcess + SlabCountKThread) / 8) * PageSize);
|
||||
static_assert(KernelPageBufferAdditionalSize == (SlabCountExtraKThread + (SlabCountExtraKThread / 8)) * PageSize);
|
||||
|
||||
}
|
||||
|
||||
/* Global to hold our resource counts. */
|
||||
constinit KSlabResourceCounts g_slab_resource_counts = {
|
||||
.num_KProcess = SlabCountKProcess,
|
||||
.num_KThread = SlabCountKThread,
|
||||
.num_KEvent = SlabCountKEvent,
|
||||
.num_KInterruptEvent = SlabCountKInterruptEvent,
|
||||
.num_KPort = SlabCountKPort,
|
||||
.num_KSharedMemory = SlabCountKSharedMemory,
|
||||
.num_KTransferMemory = SlabCountKTransferMemory,
|
||||
.num_KCodeMemory = SlabCountKCodeMemory,
|
||||
.num_KDeviceAddressSpace = SlabCountKDeviceAddressSpace,
|
||||
.num_KSession = SlabCountKSession,
|
||||
.num_KLightSession = SlabCountKLightSession,
|
||||
.num_KObjectName = SlabCountKObjectName,
|
||||
.num_KResourceLimit = SlabCountKResourceLimit,
|
||||
.num_KDebug = SlabCountKDebug,
|
||||
.num_KIoPool = SlabCountKIoPool,
|
||||
.num_KIoRegion = SlabCountKIoRegion,
|
||||
.num_KSessionRequestMappings = SlabcountKSessionRequestMappings,
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
NOINLINE KVirtualAddress InitializeSlabHeap(KVirtualAddress address, size_t num_objects) {
|
||||
const size_t size = util::AlignUp(sizeof(T) * num_objects, alignof(void *));
|
||||
KVirtualAddress start = util::AlignUp(GetInteger(address), alignof(T));
|
||||
|
||||
if (size > 0) {
|
||||
const KMemoryRegion *region = KMemoryLayout::Find(start + size - 1);
|
||||
MESOSPHERE_ABORT_UNLESS(region != nullptr);
|
||||
MESOSPHERE_ABORT_UNLESS(region->IsDerivedFrom(KMemoryRegionType_KernelSlab));
|
||||
T::InitializeSlabHeap(GetVoidPointer(start), size);
|
||||
}
|
||||
|
||||
return start + size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const KSlabResourceCounts &GetSlabResourceCounts() {
|
||||
return g_slab_resource_counts;
|
||||
}
|
||||
|
||||
void InitializeSlabResourceCounts() {
|
||||
/* Note: Nintendo initializes all fields here, but we initialize all constants at compile-time. */
|
||||
if (KSystemControl::Init::ShouldIncreaseThreadResourceLimit()) {
|
||||
g_slab_resource_counts.num_KThread += SlabCountExtraKThread;
|
||||
}
|
||||
}
|
||||
|
||||
size_t CalculateSlabHeapGapSize() {
|
||||
constexpr size_t KernelSlabHeapGapSize = 2_MB - 356_KB;
|
||||
static_assert(KernelSlabHeapGapSize <= KernelSlabHeapGapsSizeMax);
|
||||
return KernelSlabHeapGapSize;
|
||||
}
|
||||
|
||||
size_t CalculateTotalSlabHeapSize() {
|
||||
size_t size = 0;
|
||||
|
||||
#define ADD_SLAB_SIZE(NAME, COUNT, ...) ({ \
|
||||
size += alignof(NAME); \
|
||||
size += util::AlignUp(sizeof(NAME) * (COUNT), alignof(void *)); \
|
||||
});
|
||||
|
||||
/* Add the size required for each slab. */
|
||||
FOREACH_SLAB_TYPE(ADD_SLAB_SIZE)
|
||||
|
||||
#undef ADD_SLAB_SIZE
|
||||
|
||||
/* Add the reserved size. */
|
||||
size += CalculateSlabHeapGapSize();
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void InitializeSlabHeaps() {
|
||||
/* Get the slab region, since that's where we'll be working. */
|
||||
const KMemoryRegion &slab_region = KMemoryLayout::GetSlabRegion();
|
||||
KVirtualAddress address = slab_region.GetAddress();
|
||||
|
||||
/* Clear the slab region. */
|
||||
std::memset(GetVoidPointer(address), 0, slab_region.GetSize());
|
||||
|
||||
/* Initialize slab type array to be in sorted order. */
|
||||
KSlabType slab_types[KSlabType_Count];
|
||||
for (size_t i = 0; i < util::size(slab_types); i++) { slab_types[i] = static_cast<KSlabType>(i); }
|
||||
|
||||
/* N shuffles the slab type array with the following simple algorithm. */
|
||||
for (size_t i = 0; i < util::size(slab_types); i++) {
|
||||
const size_t rnd = KSystemControl::GenerateRandomRange(0, util::size(slab_types) - 1);
|
||||
std::swap(slab_types[i], slab_types[rnd]);
|
||||
}
|
||||
|
||||
/* Create an array to represent the gaps between the slabs. */
|
||||
const size_t total_gap_size = CalculateSlabHeapGapSize();
|
||||
size_t slab_gaps[util::size(slab_types)];
|
||||
for (size_t i = 0; i < util::size(slab_gaps); i++) {
|
||||
/* Note: This is an off-by-one error from Nintendo's intention, because GenerateRandomRange is inclusive. */
|
||||
/* However, Nintendo also has the off-by-one error, and it's "harmless", so we will include it ourselves. */
|
||||
slab_gaps[i] = KSystemControl::GenerateRandomRange(0, total_gap_size);
|
||||
}
|
||||
|
||||
/* Sort the array, so that we can treat differences between values as offsets to the starts of slabs. */
|
||||
for (size_t i = 1; i < util::size(slab_gaps); i++) {
|
||||
for (size_t j = i; j > 0 && slab_gaps[j-1] > slab_gaps[j]; j--) {
|
||||
std::swap(slab_gaps[j], slab_gaps[j-1]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Track the gaps, so that we can free them to the unused slab tree. */
|
||||
KVirtualAddress gap_start = address;
|
||||
size_t gap_size = 0;
|
||||
|
||||
for (size_t i = 0; i < util::size(slab_types); i++) {
|
||||
/* Add the random gap to the address. */
|
||||
const auto cur_gap = (i == 0) ? slab_gaps[0] : slab_gaps[i] - slab_gaps[i - 1];
|
||||
address += cur_gap;
|
||||
gap_size += cur_gap;
|
||||
|
||||
#define INITIALIZE_SLAB_HEAP(NAME, COUNT, ...) \
|
||||
case KSlabType_##NAME: \
|
||||
if (COUNT > 0) { \
|
||||
address = InitializeSlabHeap<NAME>(address, COUNT); \
|
||||
} \
|
||||
break;
|
||||
|
||||
/* Initialize the slabheap. */
|
||||
switch (slab_types[i]) {
|
||||
/* For each of the slab types, we want to initialize that heap. */
|
||||
FOREACH_SLAB_TYPE(INITIALIZE_SLAB_HEAP)
|
||||
/* If we somehow get an invalid type, abort. */
|
||||
MESOSPHERE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* If we've hit the end of a gap, free it. */
|
||||
if (gap_start + gap_size != address) {
|
||||
FreeUnusedSlabMemory(gap_start, gap_size);
|
||||
gap_start = address;
|
||||
gap_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the end of the slab region. */
|
||||
FreeUnusedSlabMemory(gap_start, gap_size + (slab_region.GetEndAddress() - GetInteger(address)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KPageBufferSlabHeap::Initialize(KDynamicPageManager &allocator) {
|
||||
/* Get slab resource counts. */
|
||||
const auto &counts = init::GetSlabResourceCounts();
|
||||
|
||||
/* If size is correct, account for thread local pages. */
|
||||
if (BufferSize == PageSize) {
|
||||
s_buffer_count += counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
|
||||
}
|
||||
|
||||
/* Set our object size. */
|
||||
m_obj_size = BufferSize;
|
||||
|
||||
/* Initialize the base allocator. */
|
||||
KSlabHeapImpl::Initialize();
|
||||
|
||||
/* Allocate the desired page count. */
|
||||
for (size_t i = 0; i < s_buffer_count; ++i) {
|
||||
/* Allocate an appropriate buffer. */
|
||||
auto * const pb = (BufferSize <= PageSize) ? allocator.Allocate() : allocator.Allocate(BufferSize / PageSize);
|
||||
MESOSPHERE_ABORT_UNLESS(pb != nullptr);
|
||||
|
||||
/* Free to our slab. */
|
||||
KSlabHeapImpl::Free(pb);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_debug_log_impl.hpp"
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit KSpinLock g_debug_log_lock;
|
||||
constinit bool g_initialized_impl;
|
||||
|
||||
/* NOTE: Nintendo's print buffer is size 0x100. */
|
||||
constinit char g_print_buffer[0x400];
|
||||
|
||||
void PutString(const char *str) {
|
||||
/* Only print if the implementation is initialized. */
|
||||
if (AMS_UNLIKELY(!g_initialized_impl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING)
|
||||
KDebugLogImpl::PutStringBySemihosting(str);
|
||||
#else
|
||||
while (*str) {
|
||||
/* Get a character. */
|
||||
const char c = *(str++);
|
||||
|
||||
/* Print the character. */
|
||||
if (c == '\n') {
|
||||
KDebugLogImpl::PutChar('\r');
|
||||
}
|
||||
KDebugLogImpl::PutChar(c);
|
||||
}
|
||||
|
||||
KDebugLogImpl::Flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_DEBUG_PRINT)
|
||||
|
||||
Result PutUserString(ams::kern::svc::KUserPointer<const char *> user_str, size_t len) {
|
||||
/* Only print if the implementation is initialized. */
|
||||
if (!g_initialized_impl) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
#if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING)
|
||||
/* TODO: should we do this properly? */
|
||||
KDebugLogImpl::PutStringBySemihosting(user_str.GetUnsafePointer());
|
||||
MESOSPHERE_UNUSED(len);
|
||||
#else
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
/* Get a character. */
|
||||
char c;
|
||||
R_TRY(user_str.CopyArrayElementTo(std::addressof(c), i));
|
||||
|
||||
/* Print the character. */
|
||||
if (c == '\n') {
|
||||
KDebugLogImpl::PutChar('\r');
|
||||
}
|
||||
KDebugLogImpl::PutChar(c);
|
||||
}
|
||||
|
||||
KDebugLogImpl::Flush();
|
||||
#endif
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ALWAYS_INLINE void FormatU64(char * const dst, u64 value) {
|
||||
/* Adjust, so that we can print the value backwards. */
|
||||
char *cur = dst + 2 * sizeof(value);
|
||||
|
||||
/* Format the value in (as %016lx) */
|
||||
while (cur > dst) {
|
||||
/* Extract the digit. */
|
||||
const auto digit = value & 0xF;
|
||||
value >>= 4;
|
||||
|
||||
*(--cur) = (digit <= 9) ? ('0' + digit) : ('a' + digit - 10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KDebugLog::Initialize() {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
if (!g_initialized_impl) {
|
||||
g_initialized_impl = KDebugLogImpl::Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLog::Printf(const char *format, ...) {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
::std::va_list vl;
|
||||
va_start(vl, format);
|
||||
VPrintf(format, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLog::VPrintf(const char *format, ::std::va_list vl) {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
VSNPrintf(g_print_buffer, util::size(g_print_buffer), format, vl);
|
||||
PutString(g_print_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLog::VSNPrintf(char *dst, const size_t dst_size, const char *format, ::std::va_list vl) {
|
||||
::ams::util::TVSNPrintf(dst, dst_size, format, vl);
|
||||
}
|
||||
|
||||
void KDebugLog::LogException(const char *str) {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
/* Get the current program ID. */
|
||||
/* NOTE: Nintendo does this after printing the string, */
|
||||
/* but it seems wise to avoid holding the lock/disabling interrupts */
|
||||
/* for longer than is strictly necessary. */
|
||||
char suffix[18];
|
||||
if (const auto *cur_process = GetCurrentProcessPointer(); AMS_LIKELY(cur_process != nullptr)) {
|
||||
FormatU64(suffix, cur_process->GetProgramId());
|
||||
suffix[16] = '\n';
|
||||
suffix[17] = '\x00';
|
||||
} else {
|
||||
suffix[0] = '\n';
|
||||
suffix[1] = '\x00';
|
||||
}
|
||||
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
/* Log the string. */
|
||||
PutString(str);
|
||||
|
||||
/* Log the program id (and newline) suffix. */
|
||||
PutString(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
Result KDebugLog::PrintUserString(ams::kern::svc::KUserPointer<const char *> user_str, size_t len) {
|
||||
/* If printing is enabled, print the user string. */
|
||||
#if defined(MESOSPHERE_ENABLE_DEBUG_PRINT)
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
R_TRY(PutUserString(user_str, len));
|
||||
}
|
||||
#else
|
||||
MESOSPHERE_UNUSED(user_str, len);
|
||||
#endif
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KDebugLog::Save() {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
KDebugLogImpl::Save();
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLog::Restore() {
|
||||
if (KTargetSystem::IsDebugLoggingEnabled()) {
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_debug_log_lock);
|
||||
|
||||
KDebugLogImpl::Restore();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
/* ams::kern::KDebugLogImpl::PutStringBySemihosting(const char *str) */
|
||||
.section .text._ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc, "ax", %progbits
|
||||
.global _ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc
|
||||
.type _ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc, %function
|
||||
.balign 0x10
|
||||
_ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc:
|
||||
mov x1, x0
|
||||
mov x0, #0x4
|
||||
hlt #0xF000
|
||||
ret
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_debug_log_impl.hpp"
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
#if defined(MESOSPHERE_DEBUG_LOG_USE_UART)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool DoSaveAndRestore = false;
|
||||
|
||||
enum UartRegister {
|
||||
UartRegister_THR = 0,
|
||||
UartRegister_IER = 1,
|
||||
UartRegister_FCR = 2,
|
||||
UartRegister_LCR = 3,
|
||||
|
||||
UartRegister_LSR = 5,
|
||||
|
||||
UartRegister_IRDA_CSR = 8,
|
||||
|
||||
UartRegister_DLL = 0,
|
||||
UartRegister_DLH = 1,
|
||||
};
|
||||
|
||||
KVirtualAddress g_uart_address = 0;
|
||||
|
||||
[[maybe_unused]] constinit u32 g_saved_registers[5];
|
||||
|
||||
ALWAYS_INLINE u32 ReadUartRegister(UartRegister which) {
|
||||
return GetPointer<volatile u32>(g_uart_address)[which];
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void WriteUartRegister(UartRegister which, u32 value) {
|
||||
GetPointer<volatile u32>(g_uart_address)[which] = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool KDebugLogImpl::Initialize() {
|
||||
/* Get the uart memory region. */
|
||||
const KMemoryRegion *uart_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_Uart);
|
||||
if (uart_region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the uart register base address. */
|
||||
g_uart_address = uart_region->GetPairAddress();
|
||||
if (g_uart_address == Null<KVirtualAddress>) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* NOTE: We assume here that UART init/config has been done by the Secure Monitor. */
|
||||
/* As such, we only need to disable interrupts. */
|
||||
WriteUartRegister(UartRegister_IER, 0x00);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KDebugLogImpl::PutChar(char c) {
|
||||
while (ReadUartRegister(UartRegister_LSR) & 0x100) {
|
||||
/* While the FIFO is full, yield. */
|
||||
cpu::Yield();
|
||||
}
|
||||
WriteUartRegister(UartRegister_THR, c);
|
||||
cpu::DataSynchronizationBarrier();
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Flush() {
|
||||
while ((ReadUartRegister(UartRegister_LSR) & 0x40) == 0) {
|
||||
/* Wait for the TMTY bit to be one (transmit empty). */
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Save() {
|
||||
if constexpr (DoSaveAndRestore) {
|
||||
/* Save LCR, IER, FCR. */
|
||||
g_saved_registers[0] = ReadUartRegister(UartRegister_LCR);
|
||||
g_saved_registers[1] = ReadUartRegister(UartRegister_IER);
|
||||
g_saved_registers[2] = ReadUartRegister(UartRegister_FCR);
|
||||
|
||||
/* Set Divisor Latch Access bit, to allow access to DLL/DLH */
|
||||
WriteUartRegister(UartRegister_LCR, 0x80);
|
||||
ReadUartRegister(UartRegister_LCR);
|
||||
|
||||
/* Save DLL/DLH. */
|
||||
g_saved_registers[3] = ReadUartRegister(UartRegister_DLL);
|
||||
g_saved_registers[4] = ReadUartRegister(UartRegister_DLH);
|
||||
|
||||
/* Restore Divisor Latch Access bit. */
|
||||
WriteUartRegister(UartRegister_LCR, g_saved_registers[0]);
|
||||
ReadUartRegister(UartRegister_LCR);
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Restore() {
|
||||
if constexpr (DoSaveAndRestore) {
|
||||
/* Set Divisor Latch Access bit, to allow access to DLL/DLH */
|
||||
WriteUartRegister(UartRegister_LCR, 0x80);
|
||||
ReadUartRegister(UartRegister_LCR);
|
||||
|
||||
/* Restore DLL/DLH. */
|
||||
WriteUartRegister(UartRegister_DLL, g_saved_registers[3]);
|
||||
WriteUartRegister(UartRegister_DLH, g_saved_registers[4]);
|
||||
ReadUartRegister(UartRegister_DLH);
|
||||
|
||||
/* Restore Divisor Latch Access bit. */
|
||||
WriteUartRegister(UartRegister_LCR, g_saved_registers[0]);
|
||||
ReadUartRegister(UartRegister_LCR);
|
||||
|
||||
/* Restore IER and FCR. */
|
||||
WriteUartRegister(UartRegister_IER, g_saved_registers[1]);
|
||||
WriteUartRegister(UartRegister_FCR, g_saved_registers[2] | 2);
|
||||
WriteUartRegister(UartRegister_IRDA_CSR, 0x02);
|
||||
ReadUartRegister(UartRegister_FCR);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(MESOSPHERE_DEBUG_LOG_USE_IRAM_RINGBUFFER)
|
||||
|
||||
namespace {
|
||||
|
||||
constinit KVirtualAddress g_debug_iram_address = 0;
|
||||
|
||||
constexpr size_t RingBufferSize = 0x5000;
|
||||
constinit uintptr_t g_offset = 0;
|
||||
|
||||
constinit u8 g_saved_buffer[RingBufferSize];
|
||||
|
||||
}
|
||||
|
||||
bool KDebugLogImpl::Initialize() {
|
||||
/* Set the base address. */
|
||||
g_debug_iram_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsIram) + 0x38000;
|
||||
|
||||
std::memset(GetVoidPointer(g_debug_iram_address), 0xFF, RingBufferSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KDebugLogImpl::PutChar(char c) {
|
||||
GetPointer<char>(g_debug_iram_address)[g_offset++] = c;
|
||||
|
||||
if (g_offset == RingBufferSize) {
|
||||
g_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Flush() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Save() {
|
||||
std::memcpy(g_saved_buffer, GetVoidPointer(g_debug_iram_address), RingBufferSize);
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Restore() {
|
||||
std::memcpy(GetVoidPointer(g_debug_iram_address), g_saved_buffer, RingBufferSize);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#error "Unknown Debug UART device!"
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#include "kern_debug_log_impl.hpp"
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
#if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING)
|
||||
|
||||
bool KDebugLogImpl::Initialize() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void KDebugLogImpl::PutChar(char c) {
|
||||
/* TODO */
|
||||
AMS_UNUSED(c);
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Flush() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Save() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void KDebugLogImpl::Restore() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#error "Unknown Debug device!"
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
class KDebugLogImpl {
|
||||
public:
|
||||
static NOINLINE bool Initialize();
|
||||
static NOINLINE void PutStringBySemihosting(const char *s);
|
||||
static NOINLINE void PutChar(char c);
|
||||
static NOINLINE void Flush();
|
||||
|
||||
/* Functionality for preserving across sleep. */
|
||||
static NOINLINE void Save();
|
||||
static NOINLINE void Restore();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
struct InitialProcessInfo {
|
||||
KProcess *process;
|
||||
size_t stack_size;
|
||||
s32 priority;
|
||||
};
|
||||
|
||||
constinit KPhysicalAddress g_initial_process_binary_phys_addr = Null<KPhysicalAddress>;
|
||||
constinit KVirtualAddress g_initial_process_binary_address = Null<KVirtualAddress>;
|
||||
constinit size_t g_initial_process_binary_size = 0;
|
||||
constinit InitialProcessBinaryHeader g_initial_process_binary_header = {};
|
||||
constinit size_t g_initial_process_secure_memory_size = 0;
|
||||
constinit u64 g_initial_process_id_min = std::numeric_limits<u64>::max();
|
||||
constinit u64 g_initial_process_id_max = std::numeric_limits<u64>::min();
|
||||
|
||||
void LoadInitialProcessBinaryHeader() {
|
||||
if (g_initial_process_binary_header.magic != InitialProcessBinaryMagic) {
|
||||
/* Get the virtual address. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>);
|
||||
const KVirtualAddress virt_addr = KMemoryLayout::GetLinearVirtualAddress(g_initial_process_binary_phys_addr);
|
||||
|
||||
/* Copy and validate the header. */
|
||||
g_initial_process_binary_header = *GetPointer<InitialProcessBinaryHeader>(virt_addr);
|
||||
MESOSPHERE_ABORT_UNLESS(g_initial_process_binary_header.magic == InitialProcessBinaryMagic);
|
||||
MESOSPHERE_ABORT_UNLESS(g_initial_process_binary_header.num_processes <= init::GetSlabResourceCounts().num_KProcess);
|
||||
|
||||
/* Set the image address. */
|
||||
g_initial_process_binary_address = virt_addr;
|
||||
|
||||
/* Process/calculate the secure memory size. */
|
||||
KVirtualAddress current = g_initial_process_binary_address + sizeof(InitialProcessBinaryHeader);
|
||||
const KVirtualAddress end = g_initial_process_binary_address + g_initial_process_binary_header.size;
|
||||
const size_t num_processes = g_initial_process_binary_header.num_processes;
|
||||
for (size_t i = 0; i < num_processes; ++i) {
|
||||
/* Validate that we can read the current KIP. */
|
||||
MESOSPHERE_ABORT_UNLESS(current <= end - sizeof(KInitialProcessHeader));
|
||||
|
||||
/* Attach to the current KIP. */
|
||||
KInitialProcessReader reader;
|
||||
KVirtualAddress data = reader.Attach(current);
|
||||
MESOSPHERE_ABORT_UNLESS(data != Null<KVirtualAddress>);
|
||||
|
||||
/* If the process uses secure memory, account for that. */
|
||||
if (reader.UsesSecureMemory()) {
|
||||
g_initial_process_secure_memory_size += reader.GetSize() + util::AlignUp(reader.GetStackSize(), PageSize);
|
||||
}
|
||||
|
||||
/* Advance to the next KIP. */
|
||||
current = data + reader.GetBinarySize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProcesses(InitialProcessInfo *infos) {
|
||||
/* Determine process image extents. */
|
||||
KVirtualAddress current = g_initial_process_binary_address + sizeof(InitialProcessBinaryHeader);
|
||||
KVirtualAddress end = g_initial_process_binary_address + g_initial_process_binary_header.size;
|
||||
|
||||
/* Decide on pools to use. */
|
||||
const auto unsafe_pool = static_cast<KMemoryManager::Pool>(KSystemControl::GetCreateProcessMemoryPool());
|
||||
const auto secure_pool = (GetTargetFirmware() >= TargetFirmware_2_0_0) ? KMemoryManager::Pool_Secure : unsafe_pool;
|
||||
|
||||
const size_t num_processes = g_initial_process_binary_header.num_processes;
|
||||
for (size_t i = 0; i < num_processes; ++i) {
|
||||
/* Validate that we can read the current KIP header. */
|
||||
MESOSPHERE_ABORT_UNLESS(current <= end - sizeof(KInitialProcessHeader));
|
||||
|
||||
/* Attach to the current kip. */
|
||||
KInitialProcessReader reader;
|
||||
KVirtualAddress data = reader.Attach(current);
|
||||
MESOSPHERE_ABORT_UNLESS(data != Null<KVirtualAddress>);
|
||||
|
||||
/* Ensure that the remainder of our parse is page aligned. */
|
||||
if (!util::IsAligned(GetInteger(data), PageSize)) {
|
||||
const KVirtualAddress aligned_data = util::AlignDown(GetInteger(data), PageSize);
|
||||
std::memmove(GetVoidPointer(aligned_data), GetVoidPointer(data), end - data);
|
||||
|
||||
data = aligned_data;
|
||||
end -= (data - aligned_data);
|
||||
}
|
||||
|
||||
/* If we crossed a page boundary, free the pages we're done using. */
|
||||
if (KVirtualAddress aligned_current = util::AlignDown(GetInteger(current), PageSize); aligned_current != data) {
|
||||
const size_t freed_size = data - aligned_current;
|
||||
Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(aligned_current), freed_size / PageSize);
|
||||
Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, freed_size);
|
||||
}
|
||||
|
||||
/* Parse process parameters. */
|
||||
ams::svc::CreateProcessParameter params;
|
||||
MESOSPHERE_R_ABORT_UNLESS(reader.MakeCreateProcessParameter(std::addressof(params), true));
|
||||
|
||||
/* Get the binary size for the kip. */
|
||||
const size_t binary_size = reader.GetBinarySize();
|
||||
const size_t binary_pages = binary_size / PageSize;
|
||||
|
||||
/* Get the pool for both the current (compressed) image, and the decompressed process. */
|
||||
const auto src_pool = Kernel::GetMemoryManager().GetPool(KMemoryLayout::GetLinearPhysicalAddress(data));
|
||||
const auto dst_pool = reader.UsesSecureMemory() ? secure_pool : unsafe_pool;
|
||||
|
||||
/* Determine the process size, and how much memory isn't already reserved. */
|
||||
const size_t process_size = params.code_num_pages * PageSize;
|
||||
const size_t unreserved_size = process_size - (src_pool == dst_pool ? util::AlignDown(binary_size, PageSize) : 0);
|
||||
|
||||
/* Reserve however much memory we need to reserve. */
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, unreserved_size));
|
||||
|
||||
/* Create the process. */
|
||||
KProcess *new_process = nullptr;
|
||||
{
|
||||
/* Make page groups to represent the data. */
|
||||
KPageGroup pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer());
|
||||
KPageGroup workaround_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer());
|
||||
|
||||
/* Populate the page group to represent the data. */
|
||||
{
|
||||
/* Allocate the previously unreserved pages. */
|
||||
KPageGroup unreserve_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer());
|
||||
MESOSPHERE_R_ABORT_UNLESS(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(unreserve_pg), unreserved_size / PageSize, 1, KMemoryManager::EncodeOption(dst_pool, KMemoryManager::Direction_FromFront)));
|
||||
|
||||
/* Add the previously reserved pages. */
|
||||
if (src_pool == dst_pool && binary_pages != 0) {
|
||||
/* NOTE: Nintendo does not check the result of this operation. */
|
||||
pg.AddBlock(KMemoryLayout::GetLinearPhysicalAddress(data), binary_pages);
|
||||
}
|
||||
|
||||
/* Add the previously unreserved pages. */
|
||||
for (const auto &block : unreserve_pg) {
|
||||
/* NOTE: Nintendo does not check the result of this operation. */
|
||||
pg.AddBlock(block.GetAddress(), block.GetNumPages());
|
||||
}
|
||||
}
|
||||
MESOSPHERE_ABORT_UNLESS(pg.GetNumPages() == static_cast<size_t>(params.code_num_pages));
|
||||
|
||||
/* Ensure that we do not leak pages. */
|
||||
KPageGroup *process_pg = std::addressof(pg);
|
||||
ON_SCOPE_EXIT { process_pg->Close(); };
|
||||
|
||||
/* Load the process. */
|
||||
reader.Load(pg, data);
|
||||
|
||||
/* If necessary, close/release the aligned part of the data we just loaded. */
|
||||
if (const size_t aligned_bin_size = util::AlignDown(binary_size, PageSize); aligned_bin_size != 0 && src_pool != dst_pool) {
|
||||
Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(data), aligned_bin_size / PageSize);
|
||||
Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, aligned_bin_size);
|
||||
}
|
||||
|
||||
/* Create a KProcess object. */
|
||||
new_process = KProcess::Create();
|
||||
MESOSPHERE_ABORT_UNLESS(new_process != nullptr);
|
||||
|
||||
/* Ensure the page group is usable for the process. */
|
||||
/* If the pool is the same, we need to use the workaround page group. */
|
||||
if (src_pool == dst_pool) {
|
||||
/* Allocate a new, usable group for the process. */
|
||||
MESOSPHERE_R_ABORT_UNLESS(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(workaround_pg), static_cast<size_t>(params.code_num_pages), 1, KMemoryManager::EncodeOption(dst_pool, KMemoryManager::Direction_FromFront)));
|
||||
|
||||
/* Copy data from the working page group to the usable one. */
|
||||
auto work_it = pg.begin();
|
||||
MESOSPHERE_ABORT_UNLESS(work_it != pg.end());
|
||||
{
|
||||
auto work_address = work_it->GetAddress();
|
||||
auto work_remaining = work_it->GetNumPages();
|
||||
for (const auto &block : workaround_pg) {
|
||||
auto block_address = block.GetAddress();
|
||||
auto block_remaining = block.GetNumPages();
|
||||
while (block_remaining > 0) {
|
||||
if (work_remaining == 0) {
|
||||
++work_it;
|
||||
work_address = work_it->GetAddress();
|
||||
work_remaining = work_it->GetNumPages();
|
||||
}
|
||||
|
||||
const size_t cur_pages = std::min(block_remaining, work_remaining);
|
||||
const size_t cur_size = cur_pages * PageSize;
|
||||
std::memcpy(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_address)), GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(work_address)), cur_size);
|
||||
|
||||
block_address += cur_size;
|
||||
work_address += cur_size;
|
||||
|
||||
block_remaining -= cur_pages;
|
||||
work_remaining -= cur_pages;
|
||||
}
|
||||
}
|
||||
|
||||
++work_it;
|
||||
}
|
||||
MESOSPHERE_ABORT_UNLESS(work_it == pg.end());
|
||||
|
||||
/* We want to use the new page group. */
|
||||
process_pg = std::addressof(workaround_pg);
|
||||
pg.Close();
|
||||
}
|
||||
|
||||
/* Initialize the process. */
|
||||
MESOSPHERE_R_ABORT_UNLESS(new_process->Initialize(params, *process_pg, reader.GetCapabilities(), reader.GetNumCapabilities(), std::addressof(Kernel::GetSystemResourceLimit()), dst_pool, reader.IsImmortal()));
|
||||
}
|
||||
|
||||
/* Set the process's memory permissions. */
|
||||
MESOSPHERE_R_ABORT_UNLESS(reader.SetMemoryPermissions(new_process->GetPageTable(), params));
|
||||
|
||||
/* Register the process. */
|
||||
KProcess::Register(new_process);
|
||||
|
||||
/* Set the ideal core id. */
|
||||
new_process->SetIdealCoreId(reader.GetIdealCoreId());
|
||||
|
||||
/* Save the process info. */
|
||||
infos[i].process = new_process;
|
||||
infos[i].stack_size = reader.GetStackSize();
|
||||
infos[i].priority = reader.GetPriority();
|
||||
|
||||
/* Advance the reader. */
|
||||
current = data + binary_size;
|
||||
}
|
||||
|
||||
/* Release remaining memory used by the image. */
|
||||
{
|
||||
const size_t remaining_size = util::AlignUp(GetInteger(g_initial_process_binary_address) + g_initial_process_binary_header.size, PageSize) - util::AlignDown(GetInteger(current), PageSize);
|
||||
const size_t remaining_pages = remaining_size / PageSize;
|
||||
Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(util::AlignDown(GetInteger(current), PageSize)), remaining_pages);
|
||||
Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, remaining_size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SetInitialProcessBinaryPhysicalAddress(KPhysicalAddress phys_addr, size_t size) {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr == Null<KPhysicalAddress>);
|
||||
|
||||
g_initial_process_binary_phys_addr = phys_addr;
|
||||
g_initial_process_binary_size = size;
|
||||
}
|
||||
|
||||
KPhysicalAddress GetInitialProcessBinaryPhysicalAddress() {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>);
|
||||
|
||||
return g_initial_process_binary_phys_addr;
|
||||
}
|
||||
|
||||
size_t GetInitialProcessBinarySize() {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>);
|
||||
|
||||
return g_initial_process_binary_size;
|
||||
}
|
||||
|
||||
u64 GetInitialProcessIdMin() {
|
||||
return g_initial_process_id_min;
|
||||
}
|
||||
|
||||
u64 GetInitialProcessIdMax() {
|
||||
return g_initial_process_id_max;
|
||||
}
|
||||
|
||||
size_t GetInitialProcessesSecureMemorySize() {
|
||||
LoadInitialProcessBinaryHeader();
|
||||
|
||||
return g_initial_process_secure_memory_size;
|
||||
}
|
||||
|
||||
size_t CopyInitialProcessBinaryToKernelMemory() {
|
||||
LoadInitialProcessBinaryHeader();
|
||||
|
||||
if (g_initial_process_binary_header.num_processes > 0) {
|
||||
/* Ensure that we have a non-zero size. */
|
||||
const size_t expected_size = g_initial_process_binary_size;
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(expected_size != 0);
|
||||
|
||||
/* Ensure that the size we need to reserve is as we expect it to be. */
|
||||
const size_t total_size = util::AlignUp(g_initial_process_binary_header.size, PageSize);
|
||||
MESOSPHERE_ABORT_UNLESS(total_size == expected_size);
|
||||
MESOSPHERE_ABORT_UNLESS(total_size <= InitialProcessBinarySizeMax);
|
||||
|
||||
/* Reserve pages for the initial process binary from the system resource limit. */
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, total_size));
|
||||
|
||||
return total_size;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAndRunInitialProcesses() {
|
||||
/* Allocate space for the processes. */
|
||||
InitialProcessInfo *infos = static_cast<InitialProcessInfo *>(__builtin_alloca(sizeof(InitialProcessInfo) * g_initial_process_binary_header.num_processes));
|
||||
|
||||
/* Create the processes. */
|
||||
CreateProcesses(infos);
|
||||
|
||||
/* Determine the initial process id range. */
|
||||
for (size_t i = 0; i < g_initial_process_binary_header.num_processes; i++) {
|
||||
const auto pid = infos[i].process->GetId();
|
||||
g_initial_process_id_min = std::min(g_initial_process_id_min, pid);
|
||||
g_initial_process_id_max = std::max(g_initial_process_id_max, pid);
|
||||
}
|
||||
|
||||
/* Run the processes. */
|
||||
for (size_t i = 0; i < g_initial_process_binary_header.num_processes; i++) {
|
||||
MESOSPHERE_R_ABORT_UNLESS(infos[i].process->Run(infos[i].priority, infos[i].stack_size));
|
||||
infos[i].process->Close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE bool ReadFromUser(s32 *out, KProcessAddress address) {
|
||||
return UserspaceAccess::CopyMemoryFromUserSize32Bit(out, GetVoidPointer(address));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool ReadFromUser(s64 *out, KProcessAddress address) {
|
||||
return UserspaceAccess::CopyMemoryFromUserSize64Bit(out, GetVoidPointer(address));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool DecrementIfLessThan(s32 *out, KProcessAddress address, s32 value) {
|
||||
/* NOTE: If scheduler lock is not held here, interrupt disable is required. */
|
||||
/* KScopedInterruptDisable di; */
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
if (!cpu::CanAccessAtomic(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UserspaceAccess::DecrementIfLessThanAtomic(out, GetPointer<s32>(address), value);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool UpdateIfEqual(s32 *out, KProcessAddress address, s32 value, s32 new_value) {
|
||||
/* NOTE: If scheduler lock is not held here, interrupt disable is required. */
|
||||
/* KScopedInterruptDisable di; */
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
if (!cpu::CanAccessAtomic(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UserspaceAccess::UpdateIfEqualAtomic(out, GetPointer<s32>(address), value, new_value);
|
||||
}
|
||||
|
||||
class ThreadQueueImplForKAddressArbiter final : public KThreadQueue {
|
||||
private:
|
||||
KAddressArbiter::ThreadTree *m_tree;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKAddressArbiter(KAddressArbiter::ThreadTree *t) : KThreadQueue(), m_tree(t) { /* ... */ }
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* If the thread is waiting on an address arbiter, remove it from the tree. */
|
||||
if (waiting_thread->IsWaitingForAddressArbiter()) {
|
||||
m_tree->erase(m_tree->iterator_to(*waiting_thread));
|
||||
waiting_thread->ClearAddressArbiter();
|
||||
}
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Result KAddressArbiter::Signal(uintptr_t addr, s32 count) {
|
||||
/* Perform signaling. */
|
||||
s32 num_waiters = 0;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto it = m_tree.nfind_key({ addr, -1 });
|
||||
while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) {
|
||||
/* End the thread's wait. */
|
||||
KThread *target_thread = std::addressof(*it);
|
||||
target_thread->EndWait(ResultSuccess());
|
||||
|
||||
MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||
target_thread->ClearAddressArbiter();
|
||||
|
||||
it = m_tree.erase(it);
|
||||
++num_waiters;
|
||||
}
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KAddressArbiter::SignalAndIncrementIfEqual(uintptr_t addr, s32 value, s32 count) {
|
||||
/* Perform signaling. */
|
||||
s32 num_waiters = 0;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Check the userspace value. */
|
||||
s32 user_value;
|
||||
R_UNLESS(UpdateIfEqual(std::addressof(user_value), addr, value, value + 1), svc::ResultInvalidCurrentMemory());
|
||||
R_UNLESS(user_value == value, svc::ResultInvalidState());
|
||||
|
||||
auto it = m_tree.nfind_key({ addr, -1 });
|
||||
while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) {
|
||||
/* End the thread's wait. */
|
||||
KThread *target_thread = std::addressof(*it);
|
||||
target_thread->EndWait(ResultSuccess());
|
||||
|
||||
MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||
target_thread->ClearAddressArbiter();
|
||||
|
||||
it = m_tree.erase(it);
|
||||
++num_waiters;
|
||||
}
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uintptr_t addr, s32 value, s32 count) {
|
||||
/* Perform signaling. */
|
||||
s32 num_waiters = 0;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto it = m_tree.nfind_key({ addr, -1 });
|
||||
/* Determine the updated value. */
|
||||
s32 new_value;
|
||||
if (count <= 0) {
|
||||
if ((it != m_tree.end()) && (it->GetAddressArbiterKey() == addr)) {
|
||||
new_value = value - 1;
|
||||
} else {
|
||||
new_value = value + 1;
|
||||
}
|
||||
} else {
|
||||
if ((it != m_tree.end()) && (it->GetAddressArbiterKey() == addr)) {
|
||||
auto tmp_it = it;
|
||||
s32 tmp_num_waiters = 0;
|
||||
while ((++tmp_it != m_tree.end()) && (tmp_it->GetAddressArbiterKey() == addr)) {
|
||||
if ((++tmp_num_waiters) >= count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tmp_num_waiters < count) {
|
||||
new_value = value - 1;
|
||||
} else {
|
||||
new_value = value;
|
||||
}
|
||||
} else {
|
||||
new_value = value + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the userspace value. */
|
||||
s32 user_value;
|
||||
bool succeeded;
|
||||
if (value != new_value) {
|
||||
succeeded = UpdateIfEqual(std::addressof(user_value), addr, value, new_value);
|
||||
} else {
|
||||
succeeded = ReadFromUser(std::addressof(user_value), addr);
|
||||
}
|
||||
|
||||
R_UNLESS(succeeded, svc::ResultInvalidCurrentMemory());
|
||||
R_UNLESS(user_value == value, svc::ResultInvalidState());
|
||||
|
||||
while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) {
|
||||
/* End the thread's wait. */
|
||||
KThread *target_thread = std::addressof(*it);
|
||||
target_thread->EndWait(ResultSuccess());
|
||||
|
||||
MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||
target_thread->ClearAddressArbiter();
|
||||
|
||||
it = m_tree.erase(it);
|
||||
++num_waiters;
|
||||
}
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KAddressArbiter::WaitIfLessThan(uintptr_t addr, s32 value, bool decrement, s64 timeout) {
|
||||
/* Prepare to wait. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree));
|
||||
|
||||
{
|
||||
KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout);
|
||||
|
||||
/* Check that the thread isn't terminating. */
|
||||
if (cur_thread->IsTerminationRequested()) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTerminationRequested());
|
||||
}
|
||||
|
||||
/* Read the value from userspace. */
|
||||
s32 user_value;
|
||||
bool succeeded;
|
||||
if (decrement) {
|
||||
succeeded = DecrementIfLessThan(std::addressof(user_value), addr, value);
|
||||
} else {
|
||||
succeeded = ReadFromUser(std::addressof(user_value), addr);
|
||||
}
|
||||
|
||||
if (!succeeded) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidCurrentMemory());
|
||||
}
|
||||
|
||||
/* Check that the value is less than the specified one. */
|
||||
if (user_value >= value) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidState());
|
||||
}
|
||||
|
||||
/* Check that the timeout is non-zero. */
|
||||
if (timeout == 0) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTimedOut());
|
||||
}
|
||||
|
||||
/* Set the arbiter. */
|
||||
cur_thread->SetAddressArbiter(std::addressof(m_tree), addr);
|
||||
m_tree.insert(*cur_thread);
|
||||
|
||||
/* Wait for the thread to finish. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(cur_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
Result KAddressArbiter::WaitIfEqual(uintptr_t addr, s32 value, s64 timeout) {
|
||||
/* Prepare to wait. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree));
|
||||
|
||||
{
|
||||
KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout);
|
||||
|
||||
/* Check that the thread isn't terminating. */
|
||||
if (cur_thread->IsTerminationRequested()) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTerminationRequested());
|
||||
}
|
||||
|
||||
/* Read the value from userspace. */
|
||||
s32 user_value;
|
||||
if (!ReadFromUser(std::addressof(user_value), addr)) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidCurrentMemory());
|
||||
}
|
||||
|
||||
/* Check that the value is equal. */
|
||||
if (value != user_value) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidState());
|
||||
}
|
||||
|
||||
/* Check that the timeout is non-zero. */
|
||||
if (timeout == 0) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTimedOut());
|
||||
}
|
||||
|
||||
/* Set the arbiter. */
|
||||
cur_thread->SetAddressArbiter(std::addressof(m_tree), addr);
|
||||
m_tree.insert(*cur_thread);
|
||||
|
||||
/* Wait for the thread to finish. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(cur_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
Result KAddressArbiter::WaitIfEqual64(uintptr_t addr, s64 value, s64 timeout) {
|
||||
/* Prepare to wait. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree));
|
||||
|
||||
{
|
||||
KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout);
|
||||
|
||||
/* Check that the thread isn't terminating. */
|
||||
if (cur_thread->IsTerminationRequested()) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTerminationRequested());
|
||||
}
|
||||
|
||||
/* Read the value from userspace. */
|
||||
s64 user_value;
|
||||
if (!ReadFromUser(std::addressof(user_value), addr)) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidCurrentMemory());
|
||||
}
|
||||
|
||||
/* Check that the value is equal. */
|
||||
if (value != user_value) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidState());
|
||||
}
|
||||
|
||||
/* Check that the timeout is non-zero. */
|
||||
if (timeout == 0) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTimedOut());
|
||||
}
|
||||
|
||||
/* Set the arbiter. */
|
||||
cur_thread->SetAddressArbiter(std::addressof(m_tree), addr);
|
||||
m_tree.insert(*cur_thread);
|
||||
|
||||
/* Wait for the thread to finish. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(cur_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uintptr_t Invalid = std::numeric_limits<uintptr_t>::max();
|
||||
|
||||
constinit KAddressSpaceInfo AddressSpaceInfos[] = {
|
||||
{ 32, ams::svc::AddressSmallMap32Start, ams::svc::AddressSmallMap32Size, KAddressSpaceInfo::Type_MapSmall, },
|
||||
{ 32, ams::svc::AddressLargeMap32Start, ams::svc::AddressLargeMap32Size, KAddressSpaceInfo::Type_MapLarge, },
|
||||
{ 32, Invalid, ams::svc::AddressMemoryRegionHeap32Size, KAddressSpaceInfo::Type_Heap, },
|
||||
{ 32, Invalid, ams::svc::AddressMemoryRegionAlias32Size, KAddressSpaceInfo::Type_Alias, },
|
||||
{ 36, ams::svc::AddressSmallMap36Start, ams::svc::AddressSmallMap36Size, KAddressSpaceInfo::Type_MapSmall, },
|
||||
{ 36, ams::svc::AddressLargeMap36Start, ams::svc::AddressLargeMap36Size, KAddressSpaceInfo::Type_MapLarge, },
|
||||
{ 36, Invalid, ams::svc::AddressMemoryRegionHeap36Size, KAddressSpaceInfo::Type_Heap, },
|
||||
{ 36, Invalid, ams::svc::AddressMemoryRegionAlias36Size, KAddressSpaceInfo::Type_Alias, },
|
||||
{ 39, ams::svc::AddressMap39Start, ams::svc::AddressMap39Size, KAddressSpaceInfo::Type_Map39Bit, },
|
||||
{ 39, Invalid, ams::svc::AddressMemoryRegionSmall39Size, KAddressSpaceInfo::Type_MapSmall, },
|
||||
{ 39, Invalid, ams::svc::AddressMemoryRegionHeap39Size, KAddressSpaceInfo::Type_Heap, },
|
||||
{ 39, Invalid, ams::svc::AddressMemoryRegionAlias39Size, KAddressSpaceInfo::Type_Alias, },
|
||||
{ 39, Invalid, ams::svc::AddressMemoryRegionStack39Size, KAddressSpaceInfo::Type_Stack, },
|
||||
};
|
||||
|
||||
constexpr u8 FlagsToAddressSpaceWidthTable[4] = {
|
||||
32, 36, 32, 39
|
||||
};
|
||||
|
||||
constexpr size_t GetAddressSpaceWidth(ams::svc::CreateProcessFlag flags) {
|
||||
/* Convert the input flags to an array index. */
|
||||
const size_t idx = (flags & ams::svc::CreateProcessFlag_AddressSpaceMask) >> ams::svc::CreateProcessFlag_AddressSpaceShift;
|
||||
MESOSPHERE_ABORT_UNLESS(idx < sizeof(FlagsToAddressSpaceWidthTable));
|
||||
|
||||
/* Return the width. */
|
||||
return FlagsToAddressSpaceWidthTable[idx];
|
||||
}
|
||||
|
||||
static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace32Bit) == 32);
|
||||
static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated) == 36);
|
||||
static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias) == 32);
|
||||
static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace64Bit) == 39);
|
||||
|
||||
KAddressSpaceInfo &GetAddressSpaceInfo(size_t width, KAddressSpaceInfo::Type type) {
|
||||
for (auto &info : AddressSpaceInfos) {
|
||||
if (info.GetWidth() == width && info.GetType() == type) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
MESOSPHERE_PANIC("Could not find AddressSpaceInfo");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uintptr_t KAddressSpaceInfo::GetAddressSpaceStart(ams::svc::CreateProcessFlag flags, KAddressSpaceInfo::Type type, size_t code_size) {
|
||||
MESOSPHERE_UNUSED(code_size);
|
||||
return GetAddressSpaceInfo(GetAddressSpaceWidth(flags), type).GetAddress();
|
||||
}
|
||||
|
||||
size_t KAddressSpaceInfo::GetAddressSpaceSize(ams::svc::CreateProcessFlag flags, KAddressSpaceInfo::Type type) {
|
||||
/* Extract the address space from the create process flags. */
|
||||
const auto as_flags = (flags & ams::svc::CreateProcessFlag_AddressSpaceMask);
|
||||
|
||||
/* Get the address space width. */
|
||||
const auto as_width = GetAddressSpaceWidth(flags);
|
||||
|
||||
/* Get the size. */
|
||||
size_t as_size = GetAddressSpaceInfo(as_width, type).GetSize();
|
||||
|
||||
/* If we're getting size for 32-bit without alias, adjust the sizes accordingly. */
|
||||
if (as_flags == ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias) {
|
||||
switch (type) {
|
||||
/* The heap space receives space that would otherwise go to the alias space. */
|
||||
case KAddressSpaceInfo::Type_Heap:
|
||||
as_size += GetAddressSpaceInfo(as_width, KAddressSpaceInfo::Type_Alias).GetSize();
|
||||
break;
|
||||
/* The alias space doesn't exist. */
|
||||
case KAddressSpaceInfo::Type_Alias:
|
||||
as_size = 0;
|
||||
break;
|
||||
/* Nothing to do by default. */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return as_size;
|
||||
}
|
||||
|
||||
void KAddressSpaceInfo::SetAddressSpaceSize(size_t width, Type type, size_t size) {
|
||||
GetAddressSpaceInfo(width, type).SetSize(size);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KCapabilities::Initialize(const u32 *caps, s32 num_caps, KProcessPageTable *page_table) {
|
||||
/* We're initializing an initial process. */
|
||||
m_svc_access_flags.Reset();
|
||||
m_irq_access_flags.Reset();
|
||||
m_debug_capabilities = {0};
|
||||
m_handle_table_size = 0;
|
||||
m_intended_kernel_version = {0};
|
||||
m_program_type = 0;
|
||||
|
||||
/* Initial processes may run on all cores. */
|
||||
constexpr u64 VirtMask = cpu::VirtualCoreMask;
|
||||
constexpr u64 PhysMask = cpu::ConvertVirtualCoreMaskToPhysical(VirtMask);
|
||||
|
||||
m_core_mask = VirtMask;
|
||||
m_phys_core_mask = PhysMask;
|
||||
|
||||
/* Initial processes may use any user priority they like. */
|
||||
m_priority_mask = ~0xFul;
|
||||
|
||||
/* Here, Nintendo sets the kernel version to the current kernel version. */
|
||||
/* We will follow suit and set the version to the highest supported kernel version. */
|
||||
m_intended_kernel_version.Set<KernelVersion::MajorVersion>(ams::svc::SupportedKernelMajorVersion);
|
||||
m_intended_kernel_version.Set<KernelVersion::MinorVersion>(ams::svc::SupportedKernelMinorVersion);
|
||||
|
||||
/* Parse the capabilities array. */
|
||||
R_RETURN(this->SetCapabilities(caps, num_caps, page_table));
|
||||
}
|
||||
|
||||
Result KCapabilities::Initialize(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table) {
|
||||
/* We're initializing a user process. */
|
||||
m_svc_access_flags.Reset();
|
||||
m_irq_access_flags.Reset();
|
||||
m_debug_capabilities = {0};
|
||||
m_handle_table_size = 0;
|
||||
m_intended_kernel_version = {0};
|
||||
m_program_type = 0;
|
||||
|
||||
/* User processes must specify what cores/priorities they can use. */
|
||||
m_core_mask = 0;
|
||||
m_priority_mask = 0;
|
||||
|
||||
/* Parse the user capabilities array. */
|
||||
R_RETURN(this->SetCapabilities(user_caps, num_caps, page_table));
|
||||
}
|
||||
|
||||
Result KCapabilities::SetCorePriorityCapability(const util::BitPack32 cap) {
|
||||
/* We can't set core/priority if we've already set them. */
|
||||
R_UNLESS(m_core_mask == 0, svc::ResultInvalidArgument());
|
||||
R_UNLESS(m_priority_mask == 0, svc::ResultInvalidArgument());
|
||||
|
||||
/* Validate the core/priority. */
|
||||
const auto min_core = cap.Get<CorePriority::MinimumCoreId>();
|
||||
const auto max_core = cap.Get<CorePriority::MaximumCoreId>();
|
||||
const auto max_prio = cap.Get<CorePriority::LowestThreadPriority>();
|
||||
const auto min_prio = cap.Get<CorePriority::HighestThreadPriority>();
|
||||
|
||||
R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination());
|
||||
R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination());
|
||||
R_UNLESS(max_core < cpu::NumVirtualCores, svc::ResultInvalidCoreId());
|
||||
|
||||
MESOSPHERE_ASSERT(max_prio < BITSIZEOF(u64));
|
||||
|
||||
/* Set core mask. */
|
||||
for (auto core_id = min_core; core_id <= max_core; core_id++) {
|
||||
m_core_mask |= (1ul << core_id);
|
||||
}
|
||||
MESOSPHERE_ASSERT((m_core_mask & cpu::VirtualCoreMask) == m_core_mask);
|
||||
|
||||
/* Set physical core mask. */
|
||||
m_phys_core_mask = cpu::ConvertVirtualCoreMaskToPhysical(m_core_mask);
|
||||
|
||||
/* Set priority mask. */
|
||||
for (auto prio = min_prio; prio <= max_prio; prio++) {
|
||||
m_priority_mask |= (1ul << prio);
|
||||
}
|
||||
|
||||
/* We must have some core/priority we can use. */
|
||||
R_UNLESS(m_core_mask != 0, svc::ResultInvalidArgument());
|
||||
R_UNLESS(m_priority_mask != 0, svc::ResultInvalidArgument());
|
||||
|
||||
/* Processes must not have access to kernel thread priorities. */
|
||||
R_UNLESS((m_priority_mask & 0xF) == 0, svc::ResultInvalidArgument());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetSyscallMaskCapability(const util::BitPack32 cap, u32 &set_svc) {
|
||||
/* Validate the index. */
|
||||
const auto mask = cap.Get<SyscallMask::Mask>();
|
||||
const auto index = cap.Get<SyscallMask::Index>();
|
||||
|
||||
const u32 index_flag = (1u << index);
|
||||
R_UNLESS((set_svc & index_flag) == 0, svc::ResultInvalidCombination());
|
||||
set_svc |= index_flag;
|
||||
|
||||
/* Set SVCs. */
|
||||
for (size_t i = 0; i < SyscallMask::Mask::Count; i++) {
|
||||
const u32 svc_id = SyscallMask::Mask::Count * index + i;
|
||||
if (mask & (1u << i)) {
|
||||
R_UNLESS(this->SetSvcAllowed(svc_id), svc::ResultOutOfRange());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::MapRange(const util::BitPack32 cap, const util::BitPack32 size_cap, KProcessPageTable *page_table) {
|
||||
/* Get/validate address/size */
|
||||
#if defined(MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES)
|
||||
const u64 phys_addr = static_cast<u64>(cap.Get<MapRange::Address>() | (size_cap.Get<MapRangeSize::AddressHigh>() << MapRange::Address::Count)) * PageSize;
|
||||
#else
|
||||
const u64 phys_addr = static_cast<u64>(cap.Get<MapRange::Address>()) * PageSize;
|
||||
|
||||
/* Validate reserved bits are unused. */
|
||||
R_UNLESS(size_cap.Get<MapRangeSize::Reserved>() == 0, svc::ResultOutOfRange());
|
||||
#endif
|
||||
const size_t num_pages = size_cap.Get<MapRangeSize::Pages>();
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(phys_addr == GetInteger(KPhysicalAddress(phys_addr)), svc::ResultInvalidAddress());
|
||||
R_UNLESS(num_pages != 0, svc::ResultInvalidSize());
|
||||
R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress());
|
||||
R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, svc::ResultInvalidAddress());
|
||||
|
||||
/* Do the mapping. */
|
||||
const KMemoryPermission perm = cap.Get<MapRange::ReadOnly>() ? KMemoryPermission_UserRead : KMemoryPermission_UserReadWrite;
|
||||
if (size_cap.Get<MapRangeSize::Normal>()) {
|
||||
R_RETURN(page_table->MapStatic(phys_addr, size, perm));
|
||||
} else {
|
||||
R_RETURN(page_table->MapIo(phys_addr, size, perm));
|
||||
}
|
||||
}
|
||||
|
||||
Result KCapabilities::MapIoPage(const util::BitPack32 cap, KProcessPageTable *page_table) {
|
||||
/* Get/validate address/size */
|
||||
const u64 phys_addr = cap.Get<MapIoPage::Address>() * PageSize;
|
||||
const size_t num_pages = 1;
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(phys_addr == GetInteger(KPhysicalAddress(phys_addr)), svc::ResultInvalidAddress());
|
||||
R_UNLESS(num_pages != 0, svc::ResultInvalidSize());
|
||||
R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress());
|
||||
R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, svc::ResultInvalidAddress());
|
||||
|
||||
/* Do the mapping. */
|
||||
R_RETURN(page_table->MapIo(phys_addr, size, KMemoryPermission_UserReadWrite));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
ALWAYS_INLINE Result KCapabilities::ProcessMapRegionCapability(const util::BitPack32 cap, F f) {
|
||||
/* Define the allowed memory regions. */
|
||||
constexpr const KMemoryRegionType MemoryRegions[] = {
|
||||
KMemoryRegionType_None,
|
||||
KMemoryRegionType_KernelTraceBuffer,
|
||||
KMemoryRegionType_OnMemoryBootImage,
|
||||
KMemoryRegionType_DTB,
|
||||
};
|
||||
|
||||
/* Extract regions/read only. */
|
||||
const RegionType types[3] = { cap.Get<MapRegion::Region0>(), cap.Get<MapRegion::Region1>(), cap.Get<MapRegion::Region2>(), };
|
||||
const bool ro[3] = { cap.Get<MapRegion::ReadOnly0>(), cap.Get<MapRegion::ReadOnly1>(), cap.Get<MapRegion::ReadOnly2>(), };
|
||||
|
||||
for (size_t i = 0; i < util::size(types); i++) {
|
||||
const auto type = types[i];
|
||||
const auto perm = ro[i] ? KMemoryPermission_UserRead : KMemoryPermission_UserReadWrite;
|
||||
switch (type) {
|
||||
case RegionType::NoMapping:
|
||||
break;
|
||||
case RegionType::KernelTraceBuffer:
|
||||
/* NOTE: This does not match official, but is used to make pre-processing hbl capabilities in userland unnecessary. */
|
||||
/* If ktrace isn't enabled, allow ktrace to succeed without mapping anything. */
|
||||
if constexpr (!ams::kern::IsKTraceEnabled) {
|
||||
break;
|
||||
}
|
||||
case RegionType::OnMemoryBootImage:
|
||||
case RegionType::DTB:
|
||||
R_TRY(f(MemoryRegions[static_cast<u32>(type)], perm));
|
||||
break;
|
||||
default:
|
||||
R_THROW(svc::ResultNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::MapRegion(const util::BitPack32 cap, KProcessPageTable *page_table) {
|
||||
/* Map each region into the process's page table. */
|
||||
R_RETURN(ProcessMapRegionCapability(cap, [page_table] ALWAYS_INLINE_LAMBDA (KMemoryRegionType region_type, KMemoryPermission perm) -> Result {
|
||||
R_RETURN(page_table->MapRegion(region_type, perm));
|
||||
}));
|
||||
}
|
||||
|
||||
Result KCapabilities::CheckMapRegion(const util::BitPack32 cap) {
|
||||
/* Check that each region has a physical backing store. */
|
||||
R_RETURN(ProcessMapRegionCapability(cap, [] ALWAYS_INLINE_LAMBDA (KMemoryRegionType region_type, KMemoryPermission perm) -> Result {
|
||||
MESOSPHERE_UNUSED(perm);
|
||||
R_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(region_type) != nullptr, svc::ResultOutOfRange());
|
||||
R_SUCCEED();
|
||||
}));
|
||||
}
|
||||
|
||||
Result KCapabilities::SetInterruptPairCapability(const util::BitPack32 cap) {
|
||||
/* Extract interrupts. */
|
||||
const u32 ids[2] = { cap.Get<InterruptPair::InterruptId0>(), cap.Get<InterruptPair::InterruptId1>(), };
|
||||
|
||||
for (size_t i = 0; i < util::size(ids); i++) {
|
||||
if (ids[i] != PaddingInterruptId) {
|
||||
R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(ids[i]), svc::ResultOutOfRange());
|
||||
R_UNLESS(this->SetInterruptPermitted(ids[i]), svc::ResultOutOfRange());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetProgramTypeCapability(const util::BitPack32 cap) {
|
||||
/* Validate. */
|
||||
R_UNLESS(cap.Get<ProgramType::Reserved>() == 0, svc::ResultReservedUsed());
|
||||
|
||||
m_program_type = cap.Get<ProgramType::Type>();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetKernelVersionCapability(const util::BitPack32 cap) {
|
||||
/* Ensure we haven't set our version before. */
|
||||
R_UNLESS(m_intended_kernel_version.Get<KernelVersion::MajorVersion>() == 0, svc::ResultInvalidArgument());
|
||||
|
||||
/* Set, ensure that we set a valid version. */
|
||||
m_intended_kernel_version = cap;
|
||||
R_UNLESS(m_intended_kernel_version.Get<KernelVersion::MajorVersion>() != 0, svc::ResultInvalidArgument());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetHandleTableCapability(const util::BitPack32 cap) {
|
||||
/* Validate. */
|
||||
R_UNLESS(cap.Get<HandleTable::Reserved>() == 0, svc::ResultReservedUsed());
|
||||
|
||||
m_handle_table_size = cap.Get<HandleTable::Size>();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetDebugFlagsCapability(const util::BitPack32 cap) {
|
||||
/* Validate. */
|
||||
R_UNLESS(cap.Get<DebugFlags::Reserved>() == 0, svc::ResultReservedUsed());
|
||||
|
||||
u32 total = 0;
|
||||
if (cap.Get<DebugFlags::AllowDebug>()) { ++total; }
|
||||
if (cap.Get<DebugFlags::ForceDebugProd>()) { ++total; }
|
||||
if (cap.Get<DebugFlags::ForceDebug>()) { ++total; }
|
||||
R_UNLESS(total <= 1, svc::ResultInvalidCombination());
|
||||
|
||||
m_debug_capabilities.Set<DebugFlags::AllowDebug>(cap.Get<DebugFlags::AllowDebug>());
|
||||
m_debug_capabilities.Set<DebugFlags::ForceDebugProd>(cap.Get<DebugFlags::ForceDebugProd>());
|
||||
m_debug_capabilities.Set<DebugFlags::ForceDebug>(cap.Get<DebugFlags::ForceDebug>());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetCapability(const util::BitPack32 cap, u32 &set_flags, u32 &set_svc, KProcessPageTable *page_table) {
|
||||
/* Validate this is a capability we can act on. */
|
||||
const auto type = GetCapabilityType(cap);
|
||||
R_UNLESS(type != CapabilityType::Invalid, svc::ResultInvalidArgument());
|
||||
|
||||
/* If the type is padding, we have no work to do. */
|
||||
R_SUCCEED_IF(type == CapabilityType::Padding);
|
||||
|
||||
/* Check that we haven't already processed this capability. */
|
||||
const auto flag = GetCapabilityFlag(type);
|
||||
R_UNLESS(((set_flags & InitializeOnceFlags) & flag) == 0, svc::ResultInvalidCombination());
|
||||
set_flags |= flag;
|
||||
|
||||
/* Process the capability. */
|
||||
switch (type) {
|
||||
case CapabilityType::CorePriority: R_RETURN(this->SetCorePriorityCapability(cap));
|
||||
case CapabilityType::SyscallMask: R_RETURN(this->SetSyscallMaskCapability(cap, set_svc));
|
||||
case CapabilityType::MapIoPage: R_RETURN(this->MapIoPage(cap, page_table));
|
||||
case CapabilityType::MapRegion: R_RETURN(this->MapRegion(cap, page_table));
|
||||
case CapabilityType::InterruptPair: R_RETURN(this->SetInterruptPairCapability(cap));
|
||||
case CapabilityType::ProgramType: R_RETURN(this->SetProgramTypeCapability(cap));
|
||||
case CapabilityType::KernelVersion: R_RETURN(this->SetKernelVersionCapability(cap));
|
||||
case CapabilityType::HandleTable: R_RETURN(this->SetHandleTableCapability(cap));
|
||||
case CapabilityType::DebugFlags: R_RETURN(this->SetDebugFlagsCapability(cap));
|
||||
default: R_THROW(svc::ResultInvalidArgument());
|
||||
}
|
||||
}
|
||||
|
||||
Result KCapabilities::SetCapabilities(const u32 *caps, s32 num_caps, KProcessPageTable *page_table) {
|
||||
u32 set_flags = 0, set_svc = 0;
|
||||
|
||||
for (s32 i = 0; i < num_caps; i++) {
|
||||
const util::BitPack32 cap = { caps[i] };
|
||||
if (GetCapabilityType(cap) == CapabilityType::MapRange) {
|
||||
/* Check that the pair cap exists. */
|
||||
R_UNLESS((++i) < num_caps, svc::ResultInvalidCombination());
|
||||
|
||||
/* Check the pair cap is a map range cap. */
|
||||
const util::BitPack32 size_cap = { caps[i] };
|
||||
R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, svc::ResultInvalidCombination());
|
||||
|
||||
/* Map the range. */
|
||||
R_TRY(this->MapRange(cap, size_cap, page_table));
|
||||
} else {
|
||||
R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table));
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::SetCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table) {
|
||||
u32 set_flags = 0, set_svc = 0;
|
||||
|
||||
for (s32 i = 0; i < num_caps; i++) {
|
||||
/* Read the cap from userspace. */
|
||||
u32 cap0;
|
||||
R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap0), i));
|
||||
|
||||
const util::BitPack32 cap = { cap0 };
|
||||
if (GetCapabilityType(cap) == CapabilityType::MapRange) {
|
||||
/* Check that the pair cap exists. */
|
||||
R_UNLESS((++i) < num_caps, svc::ResultInvalidCombination());
|
||||
|
||||
/* Read the second cap from userspace. */
|
||||
u32 cap1;
|
||||
R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap1), i));
|
||||
|
||||
/* Check the pair cap is a map range cap. */
|
||||
const util::BitPack32 size_cap = { cap1 };
|
||||
R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, svc::ResultInvalidCombination());
|
||||
|
||||
/* Map the range. */
|
||||
R_TRY(this->MapRange(cap, size_cap, page_table));
|
||||
} else {
|
||||
R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table));
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCapabilities::CheckCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps) {
|
||||
for (s32 i = 0; i < num_caps; ++i) {
|
||||
/* Read the cap from userspace. */
|
||||
u32 cap0;
|
||||
R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap0), i));
|
||||
|
||||
/* Check the capability refers to a valid region. */
|
||||
|
||||
const util::BitPack32 cap = { cap0 };
|
||||
if (GetCapabilityType(cap) == CapabilityType::MapRegion) {
|
||||
R_TRY(CheckMapRegion(cap));
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
/* Ensure that we generate correct class tokens for all types. */
|
||||
|
||||
/* Ensure that the absolute token values are correct. */
|
||||
static_assert(ClassToken<KAutoObject> == 0b00000000'00000000);
|
||||
static_assert(ClassToken<KSynchronizationObject> == 0b00000000'00000001);
|
||||
static_assert(ClassToken<KReadableEvent> == 0b00000000'00000011);
|
||||
static_assert(ClassToken<KInterruptEvent> == 0b00000111'00000011);
|
||||
static_assert(ClassToken<KDebug> == 0b00001011'00000001);
|
||||
static_assert(ClassToken<KThread> == 0b00010011'00000001);
|
||||
static_assert(ClassToken<KServerPort> == 0b00100011'00000001);
|
||||
static_assert(ClassToken<KServerSession> == 0b01000011'00000001);
|
||||
static_assert(ClassToken<KClientPort> == 0b10000011'00000001);
|
||||
static_assert(ClassToken<KClientSession> == 0b00001101'00000000);
|
||||
static_assert(ClassToken<KProcess> == 0b00010101'00000001);
|
||||
static_assert(ClassToken<KResourceLimit> == 0b00100101'00000000);
|
||||
static_assert(ClassToken<KLightSession> == 0b01000101'00000000);
|
||||
static_assert(ClassToken<KPort> == 0b10000101'00000000);
|
||||
static_assert(ClassToken<KSession> == 0b00011001'00000000);
|
||||
static_assert(ClassToken<KSharedMemory> == 0b00101001'00000000);
|
||||
static_assert(ClassToken<KEvent> == 0b01001001'00000000);
|
||||
static_assert(ClassToken<KLightClientSession> == 0b10001001'00000000);
|
||||
static_assert(ClassToken<KLightServerSession> == 0b00110001'00000000);
|
||||
static_assert(ClassToken<KTransferMemory> == 0b01010001'00000000);
|
||||
static_assert(ClassToken<KDeviceAddressSpace> == 0b10010001'00000000);
|
||||
static_assert(ClassToken<KSessionRequest> == 0b01100001'00000000);
|
||||
static_assert(ClassToken<KCodeMemory> == 0b10100001'00000000);
|
||||
static_assert(ClassToken<KIoPool> == 0b11000001'00000000);
|
||||
static_assert(ClassToken<KIoRegion> == 0b00001110'00000000);
|
||||
/* 0b00010110'00000000 */
|
||||
/* 0b00100110'00000000 */
|
||||
static_assert(ClassToken<KSystemResource> == 0b01000110'00000000);
|
||||
|
||||
|
||||
/* Ensure that the token hierarchy is correct. */
|
||||
|
||||
/* Base classes */
|
||||
static_assert(ClassToken<KAutoObject> == (0b00000000));
|
||||
static_assert(ClassToken<KSynchronizationObject> == (0b00000001 | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KReadableEvent> == (0b00000010 | ClassToken<KSynchronizationObject>));
|
||||
|
||||
/* Final classes */
|
||||
static_assert(ClassToken<KInterruptEvent> == ((0b00000111 << 8) | ClassToken<KReadableEvent>));
|
||||
static_assert(ClassToken<KDebug> == ((0b00001011 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KThread> == ((0b00010011 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KServerPort> == ((0b00100011 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KServerSession> == ((0b01000011 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KClientPort> == ((0b10000011 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KClientSession> == ((0b00001101 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KProcess> == ((0b00010101 << 8) | ClassToken<KSynchronizationObject>));
|
||||
static_assert(ClassToken<KResourceLimit> == ((0b00100101 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KLightSession> == ((0b01000101 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KPort> == ((0b10000101 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KSession> == ((0b00011001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KSharedMemory> == ((0b00101001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KEvent> == ((0b01001001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KLightClientSession> == ((0b10001001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KLightServerSession> == ((0b00110001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KTransferMemory> == ((0b01010001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KDeviceAddressSpace> == ((0b10010001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KSessionRequest> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KCodeMemory> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KIoPool> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KIoRegion> == ((0b00001110 << 8) | ClassToken<KAutoObject>));
|
||||
|
||||
static_assert(ClassToken<KSystemResource> == ((0b01000110 << 8) | ClassToken<KAutoObject>));
|
||||
|
||||
/* Ensure that the token hierarchy reflects the class hierarchy. */
|
||||
|
||||
/* Base classes. */
|
||||
static_assert(!std::is_final<KSynchronizationObject>::value && std::is_base_of<KAutoObject, KSynchronizationObject>::value);
|
||||
static_assert(!std::is_final<KReadableEvent>::value && std::is_base_of<KSynchronizationObject, KReadableEvent>::value);
|
||||
|
||||
/* Final classes */
|
||||
static_assert(std::is_final<KInterruptEvent>::value && std::is_base_of<KReadableEvent, KInterruptEvent>::value);
|
||||
static_assert(std::is_final<KDebug>::value && std::is_base_of<KSynchronizationObject, KDebug>::value);
|
||||
static_assert(std::is_final<KThread>::value && std::is_base_of<KSynchronizationObject, KThread>::value);
|
||||
static_assert(std::is_final<KServerPort>::value && std::is_base_of<KSynchronizationObject, KServerPort>::value);
|
||||
static_assert(std::is_final<KServerSession>::value && std::is_base_of<KSynchronizationObject, KServerSession>::value);
|
||||
static_assert(std::is_final<KClientPort>::value && std::is_base_of<KSynchronizationObject, KClientPort>::value);
|
||||
static_assert(std::is_final<KClientSession>::value && std::is_base_of<KAutoObject, KClientSession>::value);
|
||||
static_assert(std::is_final<KProcess>::value && std::is_base_of<KSynchronizationObject, KProcess>::value);
|
||||
static_assert(std::is_final<KResourceLimit>::value && std::is_base_of<KAutoObject, KResourceLimit>::value);
|
||||
static_assert(std::is_final<KLightSession>::value && std::is_base_of<KAutoObject, KLightSession>::value);
|
||||
static_assert(std::is_final<KPort>::value && std::is_base_of<KAutoObject, KPort>::value);
|
||||
static_assert(std::is_final<KSession>::value && std::is_base_of<KAutoObject, KSession>::value);
|
||||
static_assert(std::is_final<KSharedMemory>::value && std::is_base_of<KAutoObject, KSharedMemory>::value);
|
||||
static_assert(std::is_final<KEvent>::value && std::is_base_of<KAutoObject, KEvent>::value);
|
||||
static_assert(std::is_final<KLightClientSession>::value && std::is_base_of<KAutoObject, KLightClientSession>::value);
|
||||
static_assert(std::is_final<KLightServerSession>::value && std::is_base_of<KAutoObject, KLightServerSession>::value);
|
||||
static_assert(std::is_final<KTransferMemory>::value && std::is_base_of<KAutoObject, KTransferMemory>::value);
|
||||
static_assert(std::is_final<KDeviceAddressSpace>::value && std::is_base_of<KAutoObject, KDeviceAddressSpace>::value);
|
||||
static_assert(std::is_final<KSessionRequest>::value && std::is_base_of<KAutoObject, KSessionRequest>::value);
|
||||
static_assert(std::is_final<KCodeMemory>::value && std::is_base_of<KAutoObject, KCodeMemory>::value);
|
||||
static_assert(std::is_final<KIoPool>::value && std::is_base_of<KAutoObject, KIoPool>::value);
|
||||
static_assert(std::is_final<KIoRegion>::value && std::is_base_of<KAutoObject, KIoRegion>::value);
|
||||
|
||||
static_assert(std::is_base_of<KAutoObject, KSystemResource>::value);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KClientPort::Initialize(KPort *parent, s32 max_sessions) {
|
||||
/* Set member variables. */
|
||||
m_num_sessions = 0;
|
||||
m_peak_sessions = 0;
|
||||
m_parent = parent;
|
||||
m_max_sessions = max_sessions;
|
||||
}
|
||||
|
||||
void KClientPort::OnSessionFinalized() {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
if (const auto prev = m_num_sessions--; prev == m_max_sessions) {
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void KClientPort::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
}
|
||||
|
||||
bool KClientPort::IsLight() const {
|
||||
return this->GetParent()->IsLight();
|
||||
}
|
||||
|
||||
bool KClientPort::IsServerClosed() const {
|
||||
return this->GetParent()->IsServerClosed();
|
||||
}
|
||||
|
||||
void KClientPort::Destroy() {
|
||||
/* Note with our parent that we're closed. */
|
||||
m_parent->OnClientClosed();
|
||||
|
||||
/* Close our reference to our parent. */
|
||||
m_parent->Close();
|
||||
}
|
||||
|
||||
bool KClientPort::IsSignaled() const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
return m_num_sessions.Load() < m_max_sessions;
|
||||
}
|
||||
|
||||
Result KClientPort::CreateSession(KClientSession **out) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Declare the session we're going to allocate. */
|
||||
KSession *session;
|
||||
|
||||
/* Reserve a new session from the resource limit. */
|
||||
KScopedResourceReservation session_reservation(GetCurrentProcessPointer(), ams::svc::LimitableResource_SessionCountMax);
|
||||
if (session_reservation.Succeeded()) {
|
||||
/* Allocate a session normally. */
|
||||
session = KSession::Create();
|
||||
} else {
|
||||
/* We couldn't reserve a session. Check that we support dynamically expanding the resource limit. */
|
||||
R_UNLESS(GetCurrentProcess().GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached());
|
||||
R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached());
|
||||
|
||||
/* Try to allocate a session from unused slab memory. */
|
||||
session = KSession::CreateFromUnusedSlabMemory();
|
||||
R_UNLESS(session != nullptr, svc::ResultLimitReached());
|
||||
ON_RESULT_FAILURE { session->Close(); };
|
||||
|
||||
/* We want to add two KSessionRequests to the heap, to prevent request exhaustion. */
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
KSessionRequest *request = KSessionRequest::CreateFromUnusedSlabMemory();
|
||||
R_UNLESS(request != nullptr, svc::ResultLimitReached());
|
||||
|
||||
request->Close();
|
||||
}
|
||||
|
||||
/* We successfully allocated a session, so add the object we allocated to the resource limit. */
|
||||
Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_SessionCountMax, 1);
|
||||
}
|
||||
|
||||
/* Check that we successfully created a session. */
|
||||
R_UNLESS(session != nullptr, svc::ResultOutOfResource());
|
||||
|
||||
/* Update the session counts. */
|
||||
{
|
||||
ON_RESULT_FAILURE { session->Close(); };
|
||||
|
||||
/* Atomically increment the number of sessions. */
|
||||
s32 new_sessions;
|
||||
{
|
||||
const auto max = m_max_sessions;
|
||||
auto cur_sessions = m_num_sessions.Load();
|
||||
do {
|
||||
R_UNLESS(cur_sessions < max, svc::ResultOutOfSessions());
|
||||
new_sessions = cur_sessions + 1;
|
||||
} while (!m_num_sessions.CompareExchangeWeak<std::memory_order_relaxed>(cur_sessions, new_sessions));
|
||||
}
|
||||
|
||||
/* Atomically update the peak session tracking. */
|
||||
{
|
||||
auto peak = m_peak_sessions.Load();
|
||||
do {
|
||||
if (peak >= new_sessions) {
|
||||
break;
|
||||
}
|
||||
} while (!m_peak_sessions.CompareExchangeWeak<std::memory_order_relaxed>(peak, new_sessions));
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize the session. */
|
||||
session->Initialize(this, m_parent->GetName());
|
||||
|
||||
/* Commit the session reservation. */
|
||||
session_reservation.Commit();
|
||||
|
||||
/* Register the session. */
|
||||
KSession::Register(session);
|
||||
ON_RESULT_FAILURE {
|
||||
session->GetClientSession().Close();
|
||||
session->GetServerSession().Close();
|
||||
};
|
||||
|
||||
/* Enqueue the session with our parent. */
|
||||
R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession())));
|
||||
|
||||
/* We succeeded, so set the output. */
|
||||
*out = std::addressof(session->GetClientSession());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KClientPort::CreateLightSession(KLightClientSession **out) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Declare the session we're going to allocate. */
|
||||
KLightSession *session;
|
||||
|
||||
/* Reserve a new session from the resource limit. */
|
||||
KScopedResourceReservation session_reservation(GetCurrentProcessPointer(), ams::svc::LimitableResource_SessionCountMax);
|
||||
if (session_reservation.Succeeded()) {
|
||||
/* Allocate a session normally. */
|
||||
session = KLightSession::Create();
|
||||
} else {
|
||||
/* We couldn't reserve a session. Check that we support dynamically expanding the resource limit. */
|
||||
R_UNLESS(GetCurrentProcess().GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached());
|
||||
R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached());
|
||||
|
||||
/* Try to allocate a session from unused slab memory. */
|
||||
session = KLightSession::CreateFromUnusedSlabMemory();
|
||||
R_UNLESS(session != nullptr, svc::ResultLimitReached());
|
||||
|
||||
/* We successfully allocated a session, so add the object we allocated to the resource limit. */
|
||||
Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_SessionCountMax, 1);
|
||||
}
|
||||
|
||||
/* Check that we successfully created a session. */
|
||||
R_UNLESS(session != nullptr, svc::ResultOutOfResource());
|
||||
|
||||
/* Update the session counts. */
|
||||
{
|
||||
ON_RESULT_FAILURE { session->Close(); };
|
||||
|
||||
/* Atomically increment the number of sessions. */
|
||||
s32 new_sessions;
|
||||
{
|
||||
const auto max = m_max_sessions;
|
||||
auto cur_sessions = m_num_sessions.Load();
|
||||
do {
|
||||
R_UNLESS(cur_sessions < max, svc::ResultOutOfSessions());
|
||||
new_sessions = cur_sessions + 1;
|
||||
} while (!m_num_sessions.CompareExchangeWeak<std::memory_order_relaxed>(cur_sessions, new_sessions));
|
||||
}
|
||||
|
||||
/* Atomically update the peak session tracking. */
|
||||
{
|
||||
auto peak = m_peak_sessions.Load();
|
||||
do {
|
||||
if (peak >= new_sessions) {
|
||||
break;
|
||||
}
|
||||
} while (!m_peak_sessions.CompareExchangeWeak<std::memory_order_relaxed>(peak, new_sessions));
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize the session. */
|
||||
session->Initialize(this, m_parent->GetName());
|
||||
|
||||
/* Commit the session reservation. */
|
||||
session_reservation.Commit();
|
||||
|
||||
/* Register the session. */
|
||||
KLightSession::Register(session);
|
||||
ON_RESULT_FAILURE {
|
||||
session->GetClientSession().Close();
|
||||
session->GetServerSession().Close();
|
||||
};
|
||||
|
||||
/* Enqueue the session with our parent. */
|
||||
R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession())));
|
||||
|
||||
/* We succeeded, so set the output. */
|
||||
*out = std::addressof(session->GetClientSession());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KClientSession::Destroy() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
m_parent->OnClientClosed();
|
||||
m_parent->Close();
|
||||
}
|
||||
|
||||
void KClientSession::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
}
|
||||
|
||||
Result KClientSession::SendSyncRequest(uintptr_t address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Create a session request. */
|
||||
KSessionRequest *request = KSessionRequest::Create();
|
||||
R_UNLESS(request != nullptr, svc::ResultOutOfResource());
|
||||
ON_SCOPE_EXIT { request->Close(); };
|
||||
|
||||
/* Initialize the request. */
|
||||
request->Initialize(nullptr, address, size);
|
||||
|
||||
/* Send the request. */
|
||||
R_RETURN(m_parent->OnRequest(request));
|
||||
}
|
||||
|
||||
Result KClientSession::SendAsyncRequest(KEvent *event, uintptr_t address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Create a session request. */
|
||||
KSessionRequest *request = KSessionRequest::Create();
|
||||
R_UNLESS(request != nullptr, svc::ResultOutOfResource());
|
||||
ON_SCOPE_EXIT { request->Close(); };
|
||||
|
||||
/* Initialize the request. */
|
||||
request->Initialize(event, address, size);
|
||||
|
||||
/* Send the request. */
|
||||
R_RETURN(m_parent->OnRequest(request));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KCodeMemory::Initialize(KProcessAddress addr, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set members. */
|
||||
m_owner = GetCurrentProcessPointer();
|
||||
|
||||
/* Get the owner page table. */
|
||||
auto &page_table = m_owner->GetPageTable();
|
||||
|
||||
/* Construct the page group, guarding to make sure our state is valid on exit. */
|
||||
auto pg_guard = util::ConstructAtGuarded(m_page_group, page_table.GetBlockInfoManager());
|
||||
|
||||
/* Lock the memory. */
|
||||
R_TRY(page_table.LockForCodeMemory(GetPointer(m_page_group), addr, size));
|
||||
|
||||
/* Clear the memory. */
|
||||
for (const auto &block : GetReference(m_page_group)) {
|
||||
/* Clear and store cache. */
|
||||
void * const block_address = GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress()));
|
||||
std::memset(block_address, 0xFF, block.GetSize());
|
||||
cpu::StoreDataCache(block_address, block.GetSize());
|
||||
}
|
||||
|
||||
/* Set remaining tracking members. */
|
||||
m_owner->Open();
|
||||
m_address = addr;
|
||||
m_is_initialized = true;
|
||||
m_is_owner_mapped = false;
|
||||
m_is_mapped = false;
|
||||
|
||||
/* We succeeded. */
|
||||
pg_guard.Cancel();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KCodeMemory::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Unlock. */
|
||||
if (!m_is_mapped && !m_is_owner_mapped) {
|
||||
const size_t size = GetReference(m_page_group).GetNumPages() * PageSize;
|
||||
MESOSPHERE_R_ABORT_UNLESS(m_owner->GetPageTable().UnlockForCodeMemory(m_address, size, GetReference(m_page_group)));
|
||||
}
|
||||
|
||||
/* Close the page group. */
|
||||
GetReference(m_page_group).Close();
|
||||
GetReference(m_page_group).Finalize();
|
||||
|
||||
/* Close our reference to our owner. */
|
||||
m_owner->Close();
|
||||
}
|
||||
|
||||
Result KCodeMemory::Map(KProcessAddress address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Ensure we're not already mapped. */
|
||||
R_UNLESS(!m_is_mapped, svc::ResultInvalidState());
|
||||
|
||||
/* Map the memory. */
|
||||
R_TRY(GetCurrentProcess().GetPageTable().MapPageGroup(address, GetReference(m_page_group), KMemoryState_CodeOut, KMemoryPermission_UserReadWrite));
|
||||
|
||||
/* Mark ourselves as mapped. */
|
||||
m_is_mapped = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCodeMemory::Unmap(KProcessAddress address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Unmap the memory. */
|
||||
R_TRY(GetCurrentProcess().GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), KMemoryState_CodeOut));
|
||||
|
||||
/* Mark ourselves as unmapped. */
|
||||
MESOSPHERE_ASSERT(m_is_mapped);
|
||||
m_is_mapped = false;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCodeMemory::MapToOwner(KProcessAddress address, size_t size, ams::svc::MemoryPermission perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Ensure we're not already mapped. */
|
||||
R_UNLESS(!m_is_owner_mapped, svc::ResultInvalidState());
|
||||
|
||||
/* Convert the memory permission. */
|
||||
KMemoryPermission k_perm;
|
||||
switch (perm) {
|
||||
case ams::svc::MemoryPermission_Read: k_perm = KMemoryPermission_UserRead; break;
|
||||
case ams::svc::MemoryPermission_ReadExecute: k_perm = KMemoryPermission_UserReadExecute; break;
|
||||
MESOSPHERE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Map the memory. */
|
||||
R_TRY(m_owner->GetPageTable().MapPageGroup(address, GetReference(m_page_group), KMemoryState_GeneratedCode, k_perm));
|
||||
|
||||
/* Mark ourselves as mapped. */
|
||||
m_is_owner_mapped = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KCodeMemory::UnmapFromOwner(KProcessAddress address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Unmap the memory. */
|
||||
R_TRY(m_owner->GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), KMemoryState_GeneratedCode));
|
||||
|
||||
/* Mark ourselves as unmapped. */
|
||||
MESOSPHERE_ASSERT(m_is_owner_mapped);
|
||||
m_is_owner_mapped = false;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE bool ReadFromUser(u32 *out, KProcessAddress address) {
|
||||
return UserspaceAccess::CopyMemoryFromUserSize32Bit(out, GetVoidPointer(address));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool WriteToUser(KProcessAddress address, const u32 *p) {
|
||||
return UserspaceAccess::CopyMemoryToUserSize32Bit(GetVoidPointer(address), p);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool UpdateLockAtomic(u32 *out, KProcessAddress address, u32 if_zero, u32 new_orr_mask) {
|
||||
return UserspaceAccess::UpdateLockAtomic(out, GetPointer<u32>(address), if_zero, new_orr_mask);
|
||||
}
|
||||
|
||||
class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue {
|
||||
public:
|
||||
constexpr ThreadQueueImplForKConditionVariableWaitForAddress() : KThreadQueue() { /* ... */ }
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Remove the thread as a waiter from its owner. */
|
||||
waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
class ThreadQueueImplForKConditionVariableWaitConditionVariable final : public KThreadQueue {
|
||||
private:
|
||||
KConditionVariable::ThreadTree *m_tree;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKConditionVariableWaitConditionVariable(KConditionVariable::ThreadTree *t) : KThreadQueue(), m_tree(t) { /* ... */ }
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Remove the thread as a waiter from its owner. */
|
||||
if (KThread *owner = waiting_thread->GetLockOwner(); owner != nullptr) {
|
||||
owner->RemoveWaiter(waiting_thread);
|
||||
}
|
||||
|
||||
/* If the thread is waiting on a condvar, remove it from the tree. */
|
||||
if (waiting_thread->IsWaitingForConditionVariable()) {
|
||||
m_tree->erase(m_tree->iterator_to(*waiting_thread));
|
||||
waiting_thread->ClearConditionVariable();
|
||||
}
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
|
||||
KThread *owner_thread = GetCurrentThreadPointer();
|
||||
|
||||
/* Signal the address. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Remove waiter thread. */
|
||||
bool has_waiters;
|
||||
KThread * const next_owner_thread = owner_thread->RemoveWaiterByKey(std::addressof(has_waiters), addr);
|
||||
|
||||
/* Determine the next tag. */
|
||||
u32 next_value = 0;
|
||||
if (next_owner_thread != nullptr) {
|
||||
next_value = next_owner_thread->GetAddressKeyValue();
|
||||
if (has_waiters) {
|
||||
next_value |= ams::svc::HandleWaitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/* Synchronize memory before proceeding. */
|
||||
cpu::DataMemoryBarrierInnerShareable();
|
||||
|
||||
/* Write the value to userspace. */
|
||||
Result result;
|
||||
if (AMS_LIKELY(WriteToUser(addr, std::addressof(next_value)))) {
|
||||
result = ResultSuccess();
|
||||
} else {
|
||||
result = svc::ResultInvalidCurrentMemory();
|
||||
}
|
||||
|
||||
/* If necessary, signal the next owner thread. */
|
||||
if (next_owner_thread != nullptr) {
|
||||
next_owner_thread->EndWait(result);
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
}
|
||||
|
||||
Result KConditionVariable::WaitForAddress(ams::svc::Handle handle, KProcessAddress addr, u32 value) {
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
ThreadQueueImplForKConditionVariableWaitForAddress wait_queue;
|
||||
|
||||
/* Wait for the address. */
|
||||
KThread *owner_thread;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Check if the thread should terminate. */
|
||||
R_UNLESS(!cur_thread->IsTerminationRequested(), svc::ResultTerminationRequested());
|
||||
|
||||
/* Read the tag from userspace. */
|
||||
u32 test_tag;
|
||||
R_UNLESS(ReadFromUser(std::addressof(test_tag), addr), svc::ResultInvalidCurrentMemory());
|
||||
|
||||
/* If the tag isn't the handle (with wait mask), we're done. */
|
||||
R_SUCCEED_IF(test_tag != (handle | ams::svc::HandleWaitMask));
|
||||
|
||||
/* Get the lock owner thread. */
|
||||
owner_thread = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(handle).ReleasePointerUnsafe();
|
||||
R_UNLESS(owner_thread != nullptr, svc::ResultInvalidHandle());
|
||||
|
||||
/* Update the lock. */
|
||||
cur_thread->SetAddressKey(addr, value);
|
||||
owner_thread->AddWaiter(cur_thread);
|
||||
|
||||
/* Begin waiting. */
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Close our reference to the owner thread, now that the wait is over. */
|
||||
owner_thread->Close();
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(cur_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
void KConditionVariable::SignalImpl(KThread *thread) {
|
||||
/* Check pre-conditions. */
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* Update the tag. */
|
||||
KProcessAddress address = thread->GetAddressKey();
|
||||
u32 own_tag = thread->GetAddressKeyValue();
|
||||
|
||||
u32 prev_tag;
|
||||
bool can_access;
|
||||
{
|
||||
/* NOTE: If scheduler lock is not held here, interrupt disable is required. */
|
||||
/* KScopedInterruptDisable di; */
|
||||
|
||||
can_access = cpu::CanAccessAtomic(address);
|
||||
if (AMS_LIKELY(can_access)) {
|
||||
can_access = UpdateLockAtomic(std::addressof(prev_tag), address, own_tag, ams::svc::HandleWaitMask);
|
||||
}
|
||||
}
|
||||
|
||||
if (AMS_LIKELY(can_access)) {
|
||||
if (prev_tag == ams::svc::InvalidHandle) {
|
||||
/* If nobody held the lock previously, we're all good. */
|
||||
thread->EndWait(ResultSuccess());
|
||||
} else {
|
||||
/* Get the previous owner. */
|
||||
KThread *owner_thread = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(static_cast<ams::svc::Handle>(prev_tag & ~ams::svc::HandleWaitMask))
|
||||
.ReleasePointerUnsafe();
|
||||
if (AMS_LIKELY(owner_thread != nullptr)) {
|
||||
/* Add the thread as a waiter on the owner. */
|
||||
owner_thread->AddWaiter(thread);
|
||||
owner_thread->Close();
|
||||
} else {
|
||||
/* The lock was tagged with a thread that doesn't exist. */
|
||||
thread->EndWait(svc::ResultInvalidState());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* If the address wasn't accessible, note so. */
|
||||
thread->EndWait(svc::ResultInvalidCurrentMemory());
|
||||
}
|
||||
}
|
||||
|
||||
void KConditionVariable::Signal(uintptr_t cv_key, s32 count) {
|
||||
/* Perform signaling. */
|
||||
int num_waiters = 0;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
auto it = m_tree.nfind_key({ cv_key, -1 });
|
||||
while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetConditionVariableKey() == cv_key)) {
|
||||
KThread *target_thread = std::addressof(*it);
|
||||
|
||||
it = m_tree.erase(it);
|
||||
target_thread->ClearConditionVariable();
|
||||
|
||||
this->SignalImpl(target_thread);
|
||||
|
||||
++num_waiters;
|
||||
}
|
||||
|
||||
/* If we have no waiters, clear the has waiter flag. */
|
||||
if (it == m_tree.end() || it->GetConditionVariableKey() != cv_key) {
|
||||
const u32 has_waiter_flag = 0;
|
||||
WriteToUser(cv_key, std::addressof(has_waiter_flag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result KConditionVariable::Wait(KProcessAddress addr, uintptr_t key, u32 value, s64 timeout) {
|
||||
/* Prepare to wait. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(std::addressof(m_tree));
|
||||
|
||||
{
|
||||
KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout);
|
||||
|
||||
/* Check that the thread isn't terminating. */
|
||||
if (cur_thread->IsTerminationRequested()) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTerminationRequested());
|
||||
}
|
||||
|
||||
/* Update the value and process for the next owner. */
|
||||
{
|
||||
/* Remove waiter thread. */
|
||||
bool has_waiters;
|
||||
KThread *next_owner_thread = cur_thread->RemoveWaiterByKey(std::addressof(has_waiters), GetInteger(addr));
|
||||
|
||||
/* Update for the next owner thread. */
|
||||
u32 next_value = 0;
|
||||
if (next_owner_thread != nullptr) {
|
||||
/* Get the next tag value. */
|
||||
next_value = next_owner_thread->GetAddressKeyValue();
|
||||
if (has_waiters) {
|
||||
next_value |= ams::svc::HandleWaitMask;
|
||||
}
|
||||
|
||||
/* Wake up the next owner. */
|
||||
next_owner_thread->EndWait(ResultSuccess());
|
||||
}
|
||||
|
||||
/* Write to the cv key. */
|
||||
{
|
||||
const u32 has_waiter_flag = 1;
|
||||
WriteToUser(key, std::addressof(has_waiter_flag));
|
||||
cpu::DataMemoryBarrierInnerShareable();
|
||||
}
|
||||
|
||||
/* Write the value to userspace. */
|
||||
if (!WriteToUser(addr, std::addressof(next_value))) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultInvalidCurrentMemory());
|
||||
}
|
||||
}
|
||||
|
||||
/* If timeout is zero, time out. */
|
||||
R_UNLESS(timeout != 0, svc::ResultTimedOut());
|
||||
|
||||
/* Update condition variable tracking. */
|
||||
cur_thread->SetConditionVariable(std::addressof(m_tree), addr, key, value);
|
||||
m_tree.insert(*cur_thread);
|
||||
|
||||
/* Begin waiting. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(cur_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
/* Static initializer. */
|
||||
void KDeviceAddressSpace::Initialize() {
|
||||
/* This just forwards to the device page table manager. */
|
||||
KDevicePageTable::Initialize();
|
||||
}
|
||||
|
||||
/* Member functions. */
|
||||
Result KDeviceAddressSpace::Initialize(u64 address, u64 size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Initialize the device page table. */
|
||||
R_TRY(m_table.Initialize(address, size));
|
||||
|
||||
/* Set member variables. */
|
||||
m_space_address = address;
|
||||
m_space_size = size;
|
||||
m_is_initialized = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KDeviceAddressSpace::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Finalize the table. */
|
||||
m_table.Finalize();
|
||||
}
|
||||
|
||||
Result KDeviceAddressSpace::Attach(ams::svc::DeviceName device_name) {
|
||||
/* Lock the address space. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Attach. */
|
||||
R_RETURN(m_table.Attach(device_name, m_space_address, m_space_size));
|
||||
}
|
||||
|
||||
Result KDeviceAddressSpace::Detach(ams::svc::DeviceName device_name) {
|
||||
/* Lock the address space. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Detach. */
|
||||
R_RETURN(m_table.Detach(device_name));
|
||||
}
|
||||
|
||||
Result KDeviceAddressSpace::Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address, u32 option, bool is_aligned) {
|
||||
/* Check that the address falls within the space. */
|
||||
R_UNLESS((m_space_address <= device_address && device_address + size - 1 <= m_space_address + m_space_size - 1), svc::ResultInvalidCurrentMemory());
|
||||
|
||||
/* Decode the option. */
|
||||
const util::BitPack32 option_pack = { option };
|
||||
const auto device_perm = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Permission>();
|
||||
const auto flags = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Flags>();
|
||||
const auto reserved = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Reserved>();
|
||||
|
||||
/* Validate the option. */
|
||||
/* TODO: It is likely that this check for flags == none is only on NX board. */
|
||||
R_UNLESS(flags == ams::svc::MapDeviceAddressSpaceFlag_None, svc::ResultInvalidEnumValue());
|
||||
R_UNLESS(reserved == 0, svc::ResultInvalidEnumValue());
|
||||
|
||||
/* Lock the address space. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Lock the page table to prevent concurrent device mapping operations. */
|
||||
KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock();
|
||||
|
||||
/* Lock the pages. */
|
||||
bool is_io{};
|
||||
R_TRY(page_table->LockForMapDeviceAddressSpace(std::addressof(is_io), process_address, size, ConvertToKMemoryPermission(device_perm), is_aligned, true));
|
||||
|
||||
/* Ensure that if we fail, we don't keep unmapped pages locked. */
|
||||
ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpace(process_address, size)); };
|
||||
|
||||
/* Check that the io status is allowable. */
|
||||
if (is_io) {
|
||||
R_UNLESS((flags & ams::svc::MapDeviceAddressSpaceFlag_NotIoRegister) == 0, svc::ResultInvalidCombination());
|
||||
}
|
||||
|
||||
/* Map the pages. */
|
||||
{
|
||||
/* Perform the mapping. */
|
||||
R_TRY(m_table.Map(page_table, process_address, size, device_address, device_perm, is_aligned, is_io));
|
||||
|
||||
/* Ensure that we unmap the pages if we fail to update the protections. */
|
||||
/* NOTE: Nintendo does not check the result of this unmap call. */
|
||||
ON_RESULT_FAILURE { m_table.Unmap(device_address, size); };
|
||||
|
||||
/* Update the protections in accordance with how much we mapped. */
|
||||
R_TRY(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size));
|
||||
}
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KDeviceAddressSpace::Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address) {
|
||||
/* Check that the address falls within the space. */
|
||||
R_UNLESS((m_space_address <= device_address && device_address + size - 1 <= m_space_address + m_space_size - 1), svc::ResultInvalidCurrentMemory());
|
||||
|
||||
/* Lock the address space. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Lock the page table to prevent concurrent device mapping operations. */
|
||||
KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock();
|
||||
|
||||
/* Lock the pages. */
|
||||
R_TRY(page_table->LockForUnmapDeviceAddressSpace(process_address, size, true));
|
||||
|
||||
/* Unmap the pages. */
|
||||
{
|
||||
/* If we fail to unmap, we want to do a partial unlock. */
|
||||
ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size)); };
|
||||
|
||||
/* Perform the unmap. */
|
||||
R_TRY(m_table.Unmap(page_table, process_address, size, device_address));
|
||||
}
|
||||
|
||||
/* Unlock the pages. */
|
||||
MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpace(process_address, size));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
class KDpcTask {
|
||||
private:
|
||||
static constinit inline KLightLock s_req_lock;
|
||||
static constinit inline KLightLock s_lock;
|
||||
static constinit inline KLightConditionVariable s_cond_var{util::ConstantInitialize};
|
||||
static constinit inline u64 s_core_mask;
|
||||
static constinit inline KDpcTask *s_task;
|
||||
private:
|
||||
static bool HasRequest(s32 core_id) {
|
||||
return (s_core_mask & (1ull << core_id)) != 0;
|
||||
}
|
||||
|
||||
static void SetRequest(s32 core_id) {
|
||||
s_core_mask |= (1ull << core_id);
|
||||
}
|
||||
|
||||
static void ClearRequest(s32 core_id) {
|
||||
s_core_mask &= ~(1ull << core_id);
|
||||
}
|
||||
public:
|
||||
virtual void DoTask() { /* ... */ }
|
||||
|
||||
static void Request(KDpcTask *task) {
|
||||
KScopedLightLock rlk(s_req_lock);
|
||||
|
||||
/* Acquire the requested task. */
|
||||
MESOSPHERE_ABORT_UNLESS(s_task == nullptr);
|
||||
s_task = task;
|
||||
{
|
||||
KScopedLightLock lk(s_lock);
|
||||
MESOSPHERE_ABORT_UNLESS(s_core_mask == 0);
|
||||
|
||||
for (auto core = 0; core < static_cast<s32>(cpu::NumCores); ++core) {
|
||||
SetRequest(core);
|
||||
}
|
||||
|
||||
s_cond_var.Broadcast();
|
||||
|
||||
while (s_core_mask != 0) {
|
||||
s_cond_var.Wait(std::addressof(s_lock), -1ll);
|
||||
}
|
||||
}
|
||||
s_task = nullptr;
|
||||
}
|
||||
|
||||
static void WaitForRequest() {
|
||||
/* Wait for a request to come in. */
|
||||
const auto core_id = GetCurrentCoreId();
|
||||
KScopedLightLock lk(s_lock);
|
||||
while (!HasRequest(core_id)) {
|
||||
s_cond_var.Wait(std::addressof(s_lock), -1ll);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TimedWaitForRequest(s64 timeout) {
|
||||
/* Wait for a request to come in. */
|
||||
const auto core_id = GetCurrentCoreId();
|
||||
KScopedLightLock lk(s_lock);
|
||||
while (!HasRequest(core_id)) {
|
||||
s_cond_var.Wait(std::addressof(s_lock), timeout);
|
||||
if (KHardwareTimer::GetTick() >= timeout) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void HandleRequest() {
|
||||
/* Perform the request. */
|
||||
s_task->DoTask();
|
||||
|
||||
/* Clear the request. */
|
||||
const auto core_id = GetCurrentCoreId();
|
||||
KScopedLightLock lk(s_lock);
|
||||
ClearRequest(core_id);
|
||||
if (s_core_mask == 0) {
|
||||
s_cond_var.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr s32 DpcManagerThreadPriority = 3;
|
||||
constexpr s64 DpcManagerTimeout = ams::svc::Tick(TimeSpan::FromMilliSeconds(10));
|
||||
|
||||
/* Globals. */
|
||||
s64 g_preemption_priorities[cpu::NumCores];
|
||||
|
||||
/* Manager thread functions. */
|
||||
void DpcManagerNormalThreadFunction(uintptr_t arg) {
|
||||
/* Input argument goes unused. */
|
||||
MESOSPHERE_UNUSED(arg);
|
||||
|
||||
/* Forever wait and service requests. */
|
||||
while (true) {
|
||||
KDpcTask::WaitForRequest();
|
||||
KDpcTask::HandleRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void DpcManagerPreemptionThreadFunction(uintptr_t arg) {
|
||||
/* Input argument goes unused. */
|
||||
MESOSPHERE_UNUSED(arg);
|
||||
|
||||
/* Forever wait and service requests, rotating the scheduled queue every 10 ms. */
|
||||
s64 timeout = KHardwareTimer::GetTick() + DpcManagerTimeout;
|
||||
while (true) {
|
||||
if (KDpcTask::TimedWaitForRequest(timeout)) {
|
||||
KDpcTask::HandleRequest();
|
||||
} else {
|
||||
/* Rotate the scheduler queue for each core. */
|
||||
KScopedSchedulerLock lk;
|
||||
|
||||
for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) {
|
||||
if (const s32 priority = g_preemption_priorities[core_id]; priority > DpcManagerThreadPriority) {
|
||||
KScheduler::RotateScheduledQueue(static_cast<s32>(core_id), priority);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update our next timeout. */
|
||||
timeout = KHardwareTimer::GetTick() + DpcManagerTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KDpcManager::Initialize(s32 core_id, s32 priority) {
|
||||
/* Reserve a thread from the system limit. */
|
||||
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1));
|
||||
|
||||
/* Create a new thread. */
|
||||
KThread *new_thread = KThread::Create();
|
||||
MESOSPHERE_ABORT_UNLESS(new_thread != nullptr);
|
||||
|
||||
/* Launch the new thread. */
|
||||
g_preemption_priorities[core_id] = priority;
|
||||
if (core_id == cpu::NumCores - 1) {
|
||||
MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerPreemptionThreadFunction, 0, DpcManagerThreadPriority, core_id));
|
||||
} else {
|
||||
MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerNormalThreadFunction, 0, DpcManagerThreadPriority, core_id));
|
||||
}
|
||||
|
||||
/* Register the new thread. */
|
||||
KThread::Register(new_thread);
|
||||
|
||||
/* Run the thread. */
|
||||
new_thread->Run();
|
||||
}
|
||||
|
||||
void KDpcManager::HandleDpc() {
|
||||
MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled());
|
||||
MESOSPHERE_ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* Get reference to the current thread. */
|
||||
KThread &cur_thread = GetCurrentThread();
|
||||
|
||||
/* Enable interrupts, temporarily. */
|
||||
KScopedInterruptEnable ei;
|
||||
|
||||
/* If the thread is scheduled for termination, exit the thread. */
|
||||
if (cur_thread.IsTerminationRequested()) {
|
||||
cur_thread.Exit();
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/* We may also need to destroy any closed objects. */
|
||||
cur_thread.DestroyClosedObjects();
|
||||
}
|
||||
|
||||
void KDpcManager::Sync() {
|
||||
MESOSPHERE_ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
KDpcTask dummy_task;
|
||||
KDpcTask::Request(std::addressof(dummy_task));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,915 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern::KDumpObject {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char * const ThreadStates[] = {
|
||||
[KThread::ThreadState_Initialized] = "Initialized",
|
||||
[KThread::ThreadState_Waiting] = "Waiting",
|
||||
[KThread::ThreadState_Runnable] = "Runnable",
|
||||
[KThread::ThreadState_Terminated] = "Terminated",
|
||||
};
|
||||
|
||||
void DumpThread(KThread *thread) {
|
||||
if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3lu %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu Run=%d Ideal=%d (%d) Affinity=%016lx (%016lx)\n",
|
||||
thread->GetId(), process->GetId(), process->GetName(), thread->GetPriority(), ThreadStates[thread->GetState()],
|
||||
thread->GetKernelStackUsage(), PageSize, thread->GetActiveCore(), thread->GetIdealVirtualCore(), thread->GetIdealPhysicalCore(),
|
||||
thread->GetVirtualAffinityMask(), thread->GetAffinityMask().GetAffinityMask());
|
||||
|
||||
MESOSPHERE_RELEASE_LOG(" State: 0x%04x Suspend: 0x%04x Dpc: 0x%x\n", thread->GetRawState(), thread->GetSuspendFlags(), thread->GetDpc());
|
||||
|
||||
MESOSPHERE_RELEASE_LOG(" TLS: %p (%p)\n", GetVoidPointer(thread->GetThreadLocalRegionAddress()), thread->GetThreadLocalRegionHeapAddress());
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3d %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu Run=%d Ideal=%d (%d) Affinity=%016lx (%016lx)\n",
|
||||
thread->GetId(), -1, "(kernel)", thread->GetPriority(), ThreadStates[thread->GetState()],
|
||||
thread->GetKernelStackUsage(), PageSize, thread->GetActiveCore(), thread->GetIdealVirtualCore(), thread->GetIdealPhysicalCore(),
|
||||
thread->GetVirtualAffinityMask(), thread->GetAffinityMask().GetAffinityMask());
|
||||
}
|
||||
}
|
||||
|
||||
void DumpThreadCallStack(KThread *thread) {
|
||||
if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3lu %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu\n",
|
||||
thread->GetId(), process->GetId(), process->GetName(), thread->GetPriority(), ThreadStates[thread->GetState()], thread->GetKernelStackUsage(), PageSize);
|
||||
|
||||
KDebug::PrintRegister(thread);
|
||||
KDebug::PrintBacktrace(thread);
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3d %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu\n",
|
||||
thread->GetId(), -1, "(kernel)", thread->GetPriority(), ThreadStates[thread->GetState()], thread->GetKernelStackUsage(), PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DumpHandle(const KProcess::ListAccessor &accessor, KProcess *process) {
|
||||
MESOSPHERE_RELEASE_LOG("Process ID=%lu (%s)\n", process->GetId(), process->GetName());
|
||||
|
||||
const auto end = accessor.end();
|
||||
const auto &handle_table = process->GetHandleTable();
|
||||
const size_t max_handles = handle_table.GetTableSize();
|
||||
for (size_t i = 0; i < max_handles; ++i) {
|
||||
/* Get the object + handle. */
|
||||
ams::svc::Handle handle = ams::svc::InvalidHandle;
|
||||
KScopedAutoObject obj = handle_table.GetObjectByIndex(std::addressof(handle), i);
|
||||
if (obj.IsNotNull()) {
|
||||
if (auto *target = obj->DynamicCast<KServerSession *>(); target != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Client=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), std::addressof(target->GetParent()->GetClientSession()));
|
||||
target->Dump();
|
||||
} else if (auto *target = obj->DynamicCast<KClientSession *>(); target != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Server=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), std::addressof(target->GetParent()->GetServerSession()));
|
||||
} else if (auto *target = obj->DynamicCast<KThread *>(); target != nullptr) {
|
||||
KProcess *target_owner = target->GetOwnerProcess();
|
||||
const s32 owner_pid = target_owner != nullptr ? static_cast<s32>(target_owner->GetId()) : -1;
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s ID=%d PID=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target->GetId()), owner_pid);
|
||||
} else if (auto *target = obj->DynamicCast<KProcess *>(); target != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s ID=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target->GetId()));
|
||||
} else if (auto *target = obj->DynamicCast<KSharedMemory *>(); target != nullptr) {
|
||||
/* Find the owner. */
|
||||
KProcess *target_owner = nullptr;
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
if (static_cast<KProcess *>(std::addressof(*it))->GetId() == target->GetOwnerProcessId()) {
|
||||
target_owner = static_cast<KProcess *>(std::addressof(*it));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_ASSERT(target_owner != nullptr);
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Size=%zu KB OwnerPID=%d (%s)\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), target->GetSize() / 1_KB, static_cast<s32>(target_owner->GetId()), target_owner->GetName());
|
||||
} else if (auto *target = obj->DynamicCast<KTransferMemory *>(); target != nullptr) {
|
||||
KProcess *target_owner = target->GetOwner();
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s OwnerPID=%d (%s) OwnerAddress=%lx Size=%zu KB\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target_owner->GetId()), target_owner->GetName(), GetInteger(target->GetSourceAddress()), target->GetSize() / 1_KB);
|
||||
} else if (auto *target = obj->DynamicCast<KCodeMemory *>(); target != nullptr) {
|
||||
KProcess *target_owner = target->GetOwner();
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s OwnerPID=%d (%s) OwnerAddress=%lx Size=%zu KB\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target_owner->GetId()), target_owner->GetName(), GetInteger(target->GetSourceAddress()), target->GetSize() / 1_KB);
|
||||
} else if (auto *target = obj->DynamicCast<KInterruptEvent *>(); target != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s irq=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), target->GetInterruptId());
|
||||
} else if (auto *target = obj->DynamicCast<KEvent *>(); target != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName());
|
||||
} else if (auto *target = obj->DynamicCast<KReadableEvent *>(); target != nullptr) {
|
||||
if (KEvent *event = target->GetParent(); event != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Parent=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), event);
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName());
|
||||
}
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName());
|
||||
}
|
||||
|
||||
if (auto *sync = obj->DynamicCast<KSynchronizationObject *>(); sync != nullptr) {
|
||||
sync->DumpWaiters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("%zu(max %zu)/%zu used.\n", handle_table.GetCount(), max_handles, handle_table.GetTableSize());
|
||||
MESOSPHERE_RELEASE_LOG("\n\n");
|
||||
}
|
||||
|
||||
void DumpMemory(KProcess *process) {
|
||||
const auto process_id = process->GetId();
|
||||
MESOSPHERE_RELEASE_LOG("Process ID=%3lu (%s)\n", process_id, process->GetName());
|
||||
|
||||
/* Dump the memory blocks. */
|
||||
process->GetPageTable().DumpMemoryBlocks();
|
||||
|
||||
/* Collect information about memory totals. */
|
||||
const size_t code = process->GetPageTable().GetCodeSize();
|
||||
const size_t code_data = process->GetPageTable().GetCodeDataSize();
|
||||
const size_t alias_code = process->GetPageTable().GetAliasCodeSize();
|
||||
const size_t alias_code_data = process->GetPageTable().GetAliasCodeDataSize();
|
||||
const size_t normal = process->GetPageTable().GetNormalMemorySize();
|
||||
const size_t main_stack = process->GetMainStackSize();
|
||||
|
||||
size_t shared = 0;
|
||||
{
|
||||
KSharedMemory::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KSharedMemory *shared_mem = static_cast<KSharedMemory *>(std::addressof(*it));
|
||||
if (shared_mem->GetOwnerProcessId() == process_id) {
|
||||
shared += shared_mem->GetSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump the totals. */
|
||||
MESOSPHERE_RELEASE_LOG("---\n");
|
||||
MESOSPHERE_RELEASE_LOG("Code %8zu KB\n", code / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("CodeData %8zu KB\n", code_data / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("AliasCode %8zu KB\n", alias_code / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("AliasCodeData %8zu KB\n", alias_code_data / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("Heap %8zu KB\n", normal / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("SharedMemory %8zu KB\n", shared / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("InitialStack %8zu KB\n", main_stack / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("---\n");
|
||||
MESOSPHERE_RELEASE_LOG("TOTAL %8zu KB\n", (code + code_data + alias_code + alias_code_data + normal + main_stack + shared) / 1_KB);
|
||||
MESOSPHERE_RELEASE_LOG("\n\n");
|
||||
}
|
||||
|
||||
void DumpPageTable(KProcess *process) {
|
||||
MESOSPHERE_RELEASE_LOG("Process ID=%3lu (%s)\n", process->GetId(), process->GetName());
|
||||
process->GetPageTable().DumpPageTable();
|
||||
MESOSPHERE_RELEASE_LOG("\n\n");
|
||||
}
|
||||
|
||||
void DumpProcess(KProcess *process) {
|
||||
MESOSPHERE_RELEASE_LOG("Process ID=%3lu index=%3zu State=%d (%s)\n", process->GetId(), process->GetSlabIndex(), process->GetState(), process->GetName());
|
||||
}
|
||||
|
||||
void DumpPort(const KProcess::ListAccessor &accessor, KProcess *process) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Port Process ID=%lu (%s)\n", process->GetId(), process->GetName());
|
||||
|
||||
const auto end = accessor.end();
|
||||
const auto &handle_table = process->GetHandleTable();
|
||||
const size_t max_handles = handle_table.GetTableSize();
|
||||
for (size_t i = 0; i < max_handles; ++i) {
|
||||
/* Get the object + handle. */
|
||||
ams::svc::Handle handle = ams::svc::InvalidHandle;
|
||||
KScopedAutoObject obj = handle_table.GetObjectByIndex(std::addressof(handle), i);
|
||||
if (obj.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Process the object as a port. */
|
||||
if (auto *server = obj->DynamicCast<KServerPort *>(); server != nullptr) {
|
||||
const KClientPort *client = std::addressof(server->GetParent()->GetClientPort());
|
||||
const uintptr_t port_name = server->GetParent()->GetName();
|
||||
|
||||
/* Get the port name. */
|
||||
char name[9] = {};
|
||||
{
|
||||
/* Find the client port process. */
|
||||
KProcess *client_port_process = nullptr;
|
||||
ON_SCOPE_EXIT { if (client_port_process != nullptr) { client_port_process->Close(); } };
|
||||
|
||||
{
|
||||
for (auto it = accessor.begin(); it != end && client_port_process == nullptr; ++it) {
|
||||
KProcess *cur = static_cast<KProcess *>(std::addressof(*it));
|
||||
for (size_t j = 0; j < cur->GetHandleTable().GetTableSize(); ++j) {
|
||||
ams::svc::Handle cur_h = ams::svc::InvalidHandle;
|
||||
KScopedAutoObject cur_o = cur->GetHandleTable().GetObjectByIndex(std::addressof(cur_h), j);
|
||||
if (cur_o.IsNotNull()) {
|
||||
if (cur_o.GetPointerUnsafe() == client) {
|
||||
client_port_process = cur;
|
||||
client_port_process->Open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the port name. */
|
||||
if (client_port_process != nullptr) {
|
||||
if (R_FAILED(client_port_process->GetPageTable().CopyMemoryFromLinearToKernel(KProcessAddress(name), 8, port_name, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) {
|
||||
std::memset(name, 0, sizeof(name));
|
||||
}
|
||||
for (size_t i = 0; i < 8 && name[i] != 0; i++) {
|
||||
if (name[i] > 0x7F) {
|
||||
std::memset(name, 0, sizeof(name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("%-9s: Handle %08x Obj=%p Cur=%3d Peak=%3d Max=%3d\n", name, handle, obj.GetPointerUnsafe(), client->GetNumSessions(), client->GetPeakSessions(), client->GetMaxSessions());
|
||||
|
||||
/* Identify any sessions. */
|
||||
{
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KProcess *cur = static_cast<KProcess *>(std::addressof(*it));
|
||||
for (size_t j = 0; j < cur->GetHandleTable().GetTableSize(); ++j) {
|
||||
ams::svc::Handle cur_h = ams::svc::InvalidHandle;
|
||||
KScopedAutoObject cur_o = cur->GetHandleTable().GetObjectByIndex(std::addressof(cur_h), j);
|
||||
if (cur_o.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
if (auto *session = cur_o->DynamicCast<KClientSession *>(); session != nullptr && session->GetParent()->GetParent() == client) {
|
||||
MESOSPHERE_RELEASE_LOG(" Client %p Server %p %-12s: PID=%3lu\n", session, std::addressof(session->GetParent()->GetServerSession()), cur->GetName(), cur->GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE s64 GetTickOrdered() {
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
const s64 tick = KHardwareTimer::GetTick();
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
return tick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DumpThread() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Thread\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KThread::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each thread. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpThread(static_cast<KThread *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpThread(u64 thread_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Thread\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target thread. */
|
||||
if (KThread *thread = KThread::GetThreadFromId(thread_id); thread != nullptr) {
|
||||
ON_SCOPE_EXIT { thread->Close(); };
|
||||
DumpThread(thread);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpThreadCallStack() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Thread\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KThread::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each thread. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpThreadCallStack(static_cast<KThread *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpThreadCallStack(u64 thread_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Thread\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target thread. */
|
||||
if (KThread *thread = KThread::GetThreadFromId(thread_id); thread != nullptr) {
|
||||
ON_SCOPE_EXIT { thread->Close(); };
|
||||
DumpThreadCallStack(thread);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpKernelObject() {
|
||||
MESOSPHERE_LOG("Dump Kernel Object\n");
|
||||
|
||||
{
|
||||
/* Static slab heaps. */
|
||||
{
|
||||
#define DUMP_KSLABOBJ(__OBJECT__) \
|
||||
MESOSPHERE_RELEASE_LOG(#__OBJECT__ "\n"); \
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%3zu Peak=%3zu Max=%3zu\n", __OBJECT__::GetSlabHeapSize() - __OBJECT__::GetNumRemaining(), __OBJECT__::GetPeakIndex(), __OBJECT__::GetSlabHeapSize())
|
||||
|
||||
DUMP_KSLABOBJ(KEvent);
|
||||
DUMP_KSLABOBJ(KInterruptEvent);
|
||||
DUMP_KSLABOBJ(KProcess);
|
||||
DUMP_KSLABOBJ(KThread);
|
||||
DUMP_KSLABOBJ(KPort);
|
||||
DUMP_KSLABOBJ(KSharedMemory);
|
||||
DUMP_KSLABOBJ(KTransferMemory);
|
||||
DUMP_KSLABOBJ(KDeviceAddressSpace);
|
||||
DUMP_KSLABOBJ(KDebug);
|
||||
DUMP_KSLABOBJ(KSession);
|
||||
DUMP_KSLABOBJ(KLightSession);
|
||||
DUMP_KSLABOBJ(KThreadLocalPage);
|
||||
DUMP_KSLABOBJ(KObjectName);
|
||||
DUMP_KSLABOBJ(KEventInfo);
|
||||
DUMP_KSLABOBJ(KSessionRequest);
|
||||
DUMP_KSLABOBJ(KResourceLimit);
|
||||
DUMP_KSLABOBJ(KIoPool);
|
||||
DUMP_KSLABOBJ(KIoRegion);
|
||||
|
||||
#undef DUMP_KSLABOBJ
|
||||
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
/* Dynamic slab heaps. */
|
||||
{
|
||||
/* Memory block slabs. */
|
||||
{
|
||||
MESOSPHERE_RELEASE_LOG("App Memory Block\n");
|
||||
auto &app = Kernel::GetApplicationSystemResource().GetMemoryBlockSlabManager();
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", app.GetUsed(), app.GetPeak(), app.GetCount());
|
||||
MESOSPHERE_RELEASE_LOG("Sys Memory Block\n");
|
||||
auto &sys = Kernel::GetSystemSystemResource().GetMemoryBlockSlabManager();
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", sys.GetUsed(), sys.GetPeak(), sys.GetCount());
|
||||
}
|
||||
|
||||
/* KBlockInfo slab. */
|
||||
{
|
||||
MESOSPHERE_RELEASE_LOG("KBlockInfo\n");
|
||||
auto &manager = Kernel::GetSystemSystemResource().GetBlockInfoManager();
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", manager.GetUsed(), manager.GetPeak(), manager.GetCount());
|
||||
}
|
||||
|
||||
/* Page Table slab. */
|
||||
{
|
||||
MESOSPHERE_RELEASE_LOG("Page Table\n");
|
||||
auto &manager = Kernel::GetSystemSystemResource().GetPageTableManager();
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", manager.GetUsed(), manager.GetPeak(), manager.GetCount());
|
||||
}
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
/* Process resources. */
|
||||
{
|
||||
KProcess::ListAccessor accessor;
|
||||
|
||||
size_t process_pts = 0;
|
||||
|
||||
const auto end = accessor.end();
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KProcess *process = static_cast<KProcess *>(std::addressof(*it));
|
||||
|
||||
/* Count the number of threads. */
|
||||
int threads = 0;
|
||||
{
|
||||
KThread::ListAccessor thr_accessor;
|
||||
const auto thr_end = thr_accessor.end();
|
||||
for (auto thr_it = thr_accessor.begin(); thr_it != thr_end; ++thr_it) {
|
||||
KThread *thread = static_cast<KThread *>(std::addressof(*thr_it));
|
||||
if (thread->GetOwnerProcess() == process) {
|
||||
++threads;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Count the number of events. */
|
||||
int events = 0;
|
||||
{
|
||||
KEvent::ListAccessor ev_accessor;
|
||||
const auto ev_end = ev_accessor.end();
|
||||
for (auto ev_it = ev_accessor.begin(); ev_it != ev_end; ++ev_it) {
|
||||
KEvent *event = static_cast<KEvent *>(std::addressof(*ev_it));
|
||||
if (event->GetOwner() == process) {
|
||||
++events;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t pts = process->GetPageTable().CountPageTables();
|
||||
process_pts += pts;
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("%-12s: PID=%3lu Thread %4d / Event %4d / PageTable %5zu\n", process->GetName(), process->GetId(), threads, events, pts);
|
||||
if (const auto &system_resource = process->GetSystemResource(); system_resource.IsSecureResource()) {
|
||||
const auto &secure_resource = static_cast<const KSecureSystemResource &>(system_resource);
|
||||
|
||||
MESOSPHERE_RELEASE_LOG(" System Resource\n");
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetDynamicPageManager().GetUsed(), secure_resource.GetDynamicPageManager().GetPeak(), secure_resource.GetDynamicPageManager().GetCount());
|
||||
MESOSPHERE_RELEASE_LOG(" Memory Block\n");
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetMemoryBlockSlabManager().GetUsed(), secure_resource.GetMemoryBlockSlabManager().GetPeak(), secure_resource.GetMemoryBlockSlabManager().GetCount());
|
||||
MESOSPHERE_RELEASE_LOG(" Page Table\n");
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetPageTableManager().GetUsed(), secure_resource.GetPageTableManager().GetPeak(), secure_resource.GetPageTableManager().GetCount());
|
||||
MESOSPHERE_RELEASE_LOG(" Block Info\n");
|
||||
MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetBlockInfoManager().GetUsed(), secure_resource.GetBlockInfoManager().GetPeak(), secure_resource.GetBlockInfoManager().GetCount());
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("Process Page Table %zu\n", process_pts);
|
||||
MESOSPHERE_RELEASE_LOG("Kernel Page Table %zu\n", Kernel::GetKernelPageTable().CountPageTables());
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
/* Resource limits. */
|
||||
{
|
||||
auto &sys_rl = Kernel::GetSystemResourceLimit();
|
||||
|
||||
u64 cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_PhysicalMemoryMax);
|
||||
u64 lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax);
|
||||
MESOSPHERE_RELEASE_LOG("System ResourceLimit PhysicalMemory 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(lim >> 32), static_cast<u32>(lim));
|
||||
|
||||
cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_ThreadCountMax);
|
||||
lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_ThreadCountMax);
|
||||
MESOSPHERE_RELEASE_LOG("System ResourceLimit Thread %4lu / %4lu\n", cur, lim);
|
||||
|
||||
cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_EventCountMax);
|
||||
lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_EventCountMax);
|
||||
MESOSPHERE_RELEASE_LOG("System ResourceLimit Event %4lu / %4lu\n", cur, lim);
|
||||
|
||||
cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_TransferMemoryCountMax);
|
||||
lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_TransferMemoryCountMax);
|
||||
MESOSPHERE_RELEASE_LOG("System ResourceLimit TransferMemory %4lu / %4lu\n", cur, lim);
|
||||
|
||||
cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_SessionCountMax);
|
||||
lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_SessionCountMax);
|
||||
MESOSPHERE_RELEASE_LOG("System ResourceLimit Session %4lu / %4lu\n", cur, lim);
|
||||
|
||||
{
|
||||
KResourceLimit::ListAccessor accessor;
|
||||
|
||||
const auto end = accessor.end();
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KResourceLimit *rl = static_cast<KResourceLimit *>(std::addressof(*it));
|
||||
cur = rl->GetCurrentValue(ams::svc::LimitableResource_PhysicalMemoryMax);
|
||||
lim = rl->GetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax);
|
||||
MESOSPHERE_RELEASE_LOG("ResourceLimit %zu PhysicalMemory 0x%01x_%08x / 0x%01x_%08x\n", rl->GetSlabIndex(), static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(lim >> 32), static_cast<u32>(lim));
|
||||
}
|
||||
}
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
/* Memory Manager. */
|
||||
{
|
||||
auto &mm = Kernel::GetMemoryManager();
|
||||
u64 max = mm.GetSize();
|
||||
u64 cur = max - mm.GetFreeSize();
|
||||
MESOSPHERE_RELEASE_LOG("Kernel Heap Size 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max));
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
max = mm.GetSize(KMemoryManager::Pool_Application);
|
||||
cur = max - mm.GetFreeSize(KMemoryManager::Pool_Application);
|
||||
MESOSPHERE_RELEASE_LOG("Application 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max));
|
||||
mm.DumpFreeList(KMemoryManager::Pool_Application);
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
max = mm.GetSize(KMemoryManager::Pool_Applet);
|
||||
cur = max - mm.GetFreeSize(KMemoryManager::Pool_Applet);
|
||||
MESOSPHERE_RELEASE_LOG("Applet 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max));
|
||||
mm.DumpFreeList(KMemoryManager::Pool_Applet);
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
max = mm.GetSize(KMemoryManager::Pool_System);
|
||||
cur = max - mm.GetFreeSize(KMemoryManager::Pool_System);
|
||||
MESOSPHERE_RELEASE_LOG("System 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max));
|
||||
mm.DumpFreeList(KMemoryManager::Pool_System);
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
max = mm.GetSize(KMemoryManager::Pool_SystemNonSecure);
|
||||
cur = max - mm.GetFreeSize(KMemoryManager::Pool_SystemNonSecure);
|
||||
MESOSPHERE_RELEASE_LOG("SystemNonSecure 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max));
|
||||
mm.DumpFreeList(KMemoryManager::Pool_SystemNonSecure);
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpHandle() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Handle\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each process. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpHandle(accessor, static_cast<KProcess *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpHandle(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Handle\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target process. */
|
||||
if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) {
|
||||
ON_SCOPE_EXIT { process->Close(); };
|
||||
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
DumpHandle(accessor, process);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpKernelMemory() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Kernel Memory Info\n");
|
||||
|
||||
{
|
||||
Kernel::GetKernelPageTable().DumpMemoryBlocks();
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpMemory() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Memory Info\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each process. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpMemory(static_cast<KProcess *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpMemory(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Memory Info\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target process. */
|
||||
if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) {
|
||||
ON_SCOPE_EXIT { process->Close(); };
|
||||
DumpMemory(process);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpProcess() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Process\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each process. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpProcess(static_cast<KProcess *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpKernelPageTable() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Kernel PageTable\n");
|
||||
|
||||
{
|
||||
Kernel::GetKernelPageTable().DumpPageTable();
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpPageTable() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Process\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each process. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpPageTable(static_cast<KProcess *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpPageTable(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump PageTable\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target process. */
|
||||
if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) {
|
||||
ON_SCOPE_EXIT { process->Close(); };
|
||||
DumpPageTable(process);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpKernelCpuUtilization() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Kernel Cpu Utilization\n");
|
||||
|
||||
constexpr size_t MaxObjects = 64;
|
||||
{
|
||||
/* Create tracking arrays. */
|
||||
KAutoObject *objects[MaxObjects];
|
||||
u32 cpu_time[MaxObjects];
|
||||
|
||||
s64 start_tick;
|
||||
size_t i, n;
|
||||
KDpcManager::Sync();
|
||||
{
|
||||
/* Lock the thread list. */
|
||||
KThread::ListAccessor accessor;
|
||||
|
||||
/* Begin tracking. */
|
||||
start_tick = GetTickOrdered();
|
||||
|
||||
/* Iterate, finding kernel threads. */
|
||||
const auto end = accessor.end();
|
||||
i = 0;
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KThread *thread = static_cast<KThread *>(std::addressof(*it));
|
||||
if (KProcess *process = thread->GetOwnerProcess(); process == nullptr) {
|
||||
if (AMS_LIKELY(i < MaxObjects)) {
|
||||
if (AMS_LIKELY(thread->Open())) {
|
||||
cpu_time[i] = thread->GetCpuTime();
|
||||
objects[i] = thread;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep track of how many kernel threads we found. */
|
||||
n = i;
|
||||
}
|
||||
|
||||
/* Wait one second. */
|
||||
const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1));
|
||||
GetCurrentThread().Sleep(timeout);
|
||||
KDpcManager::Sync();
|
||||
|
||||
/* Update our metrics. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KThread *thread = static_cast<KThread *>(objects[i]);
|
||||
cpu_time[i] = thread->GetCpuTime() - cpu_time[i];
|
||||
}
|
||||
|
||||
/* End tracking. */
|
||||
const s64 end_tick = GetTickOrdered();
|
||||
|
||||
/* Log thread utilization. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KThread *thread = static_cast<KThread *>(objects[i]);
|
||||
const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick);
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("tid=%3lu (kernel) %3lu.%lu%% pri=%2d af=%lx\n", thread->GetId(), t / 10, t % 10, thread->GetPriority(), thread->GetAffinityMask().GetAffinityMask());
|
||||
}
|
||||
|
||||
/* Close all objects. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
objects[i]->Close();
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpCpuUtilization() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Cpu Utilization\n");
|
||||
|
||||
/* NOTE: Nintendo uses 0x40 as maximum here, but the KProcess slabheap has 0x50 entries. */
|
||||
/* We have the stack space, so there's no reason not to allow logging all processes. */
|
||||
constexpr size_t MaxObjects = 0x50;
|
||||
{
|
||||
/* Create tracking arrays. */
|
||||
KAutoObject *objects[MaxObjects];
|
||||
u32 cpu_time[MaxObjects];
|
||||
|
||||
s64 start_tick;
|
||||
size_t i, n;
|
||||
KDpcManager::Sync();
|
||||
{
|
||||
/* Lock the process list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
|
||||
/* Begin tracking. */
|
||||
start_tick = GetTickOrdered();
|
||||
|
||||
/* Iterate, finding processes. */
|
||||
const auto end = accessor.end();
|
||||
i = 0;
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KProcess *process = static_cast<KProcess *>(std::addressof(*it));
|
||||
if (AMS_LIKELY(i < MaxObjects)) {
|
||||
if (AMS_LIKELY(process->Open())) {
|
||||
cpu_time[i] = process->GetCpuTime();
|
||||
objects[i] = process;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep track of how many processes we found. */
|
||||
n = i;
|
||||
}
|
||||
|
||||
/* Wait one second. */
|
||||
const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1));
|
||||
GetCurrentThread().Sleep(timeout);
|
||||
KDpcManager::Sync();
|
||||
|
||||
/* Update our metrics. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KProcess *process = static_cast<KProcess *>(objects[i]);
|
||||
cpu_time[i] = process->GetCpuTime() - cpu_time[i];
|
||||
}
|
||||
|
||||
/* End tracking. */
|
||||
const s64 end_tick = GetTickOrdered();
|
||||
|
||||
/* Log process utilization. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KProcess *process = static_cast<KProcess *>(objects[i]);
|
||||
const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick);
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("pid=%3lu %-11s %3lu.%lu%%\n", process->GetId(), process->GetName(), t / 10, t % 10);
|
||||
}
|
||||
|
||||
/* Close all objects. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
objects[i]->Close();
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpCpuUtilization(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Cpu Utilization\n");
|
||||
|
||||
constexpr size_t MaxObjects = 64;
|
||||
{
|
||||
/* Create tracking arrays. */
|
||||
KAutoObject *objects[MaxObjects];
|
||||
u32 cpu_time[MaxObjects];
|
||||
|
||||
s64 start_tick;
|
||||
size_t i, n;
|
||||
KDpcManager::Sync();
|
||||
{
|
||||
/* Lock the thread list. */
|
||||
KThread::ListAccessor accessor;
|
||||
|
||||
/* Begin tracking. */
|
||||
start_tick = GetTickOrdered();
|
||||
|
||||
/* Iterate, finding process threads. */
|
||||
const auto end = accessor.end();
|
||||
i = 0;
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
KThread *thread = static_cast<KThread *>(std::addressof(*it));
|
||||
if (KProcess *process = thread->GetOwnerProcess(); process != nullptr && process->GetId() == process_id) {
|
||||
if (AMS_LIKELY(i < MaxObjects)) {
|
||||
if (AMS_LIKELY(thread->Open())) {
|
||||
cpu_time[i] = thread->GetCpuTime();
|
||||
objects[i] = thread;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep track of how many process threads we found. */
|
||||
n = i;
|
||||
}
|
||||
|
||||
/* Wait one second. */
|
||||
const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1));
|
||||
GetCurrentThread().Sleep(timeout);
|
||||
KDpcManager::Sync();
|
||||
|
||||
/* Update our metrics. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KThread *thread = static_cast<KThread *>(objects[i]);
|
||||
cpu_time[i] = thread->GetCpuTime() - cpu_time[i];
|
||||
}
|
||||
|
||||
/* End tracking. */
|
||||
const s64 end_tick = GetTickOrdered();
|
||||
|
||||
/* Log thread utilization. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
KThread *thread = static_cast<KThread *>(objects[i]);
|
||||
KProcess *process = thread->GetOwnerProcess();
|
||||
const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick);
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("tid=%3lu pid=%3lu %-11s %3lu.%lu%% pri=%2d af=%lx\n", thread->GetId(), process->GetId(), process->GetName(), t / 10, t % 10, thread->GetPriority(), thread->GetAffinityMask().GetAffinityMask());
|
||||
}
|
||||
|
||||
/* Close all objects. */
|
||||
for (i = 0; i < n; ++i) {
|
||||
objects[i]->Close();
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpProcess(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Process\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target process. */
|
||||
if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) {
|
||||
ON_SCOPE_EXIT { process->Close(); };
|
||||
DumpProcess(process);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpPort() {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Port\n");
|
||||
|
||||
{
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
const auto end = accessor.end();
|
||||
|
||||
/* Dump each process. */
|
||||
for (auto it = accessor.begin(); it != end; ++it) {
|
||||
DumpPort(accessor, static_cast<KProcess *>(std::addressof(*it)));
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
void DumpPort(u64 process_id) {
|
||||
MESOSPHERE_RELEASE_LOG("Dump Port\n");
|
||||
|
||||
{
|
||||
/* Find and dump the target process. */
|
||||
if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) {
|
||||
ON_SCOPE_EXIT { process->Close(); };
|
||||
|
||||
/* Lock the list. */
|
||||
KProcess::ListAccessor accessor;
|
||||
DumpPort(accessor, process);
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("\n");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KEvent::Initialize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Create our readable event. */
|
||||
KAutoObject::Create<KReadableEvent>(std::addressof(m_readable_event));
|
||||
|
||||
/* Initialize our readable event. */
|
||||
m_readable_event.Initialize(this);
|
||||
|
||||
/* Set our owner process. */
|
||||
m_owner = GetCurrentProcessPointer();
|
||||
m_owner->Open();
|
||||
|
||||
/* Mark initialized. */
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void KEvent::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
}
|
||||
|
||||
Result KEvent::Signal() {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
R_SUCCEED_IF(m_readable_event_destroyed);
|
||||
|
||||
R_RETURN(m_readable_event.Signal());
|
||||
}
|
||||
|
||||
Result KEvent::Clear() {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
R_SUCCEED_IF(m_readable_event_destroyed);
|
||||
|
||||
R_RETURN(m_readable_event.Clear());
|
||||
}
|
||||
|
||||
void KEvent::PostDestroy(uintptr_t arg) {
|
||||
/* Release the event count resource the owner process holds. */
|
||||
KProcess *owner = reinterpret_cast<KProcess *>(arg);
|
||||
owner->ReleaseResource(ams::svc::LimitableResource_EventCountMax, 1);
|
||||
owner->Close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KHandleTable::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Get the table and clear our record of it. */
|
||||
u16 saved_table_size = 0;
|
||||
{
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
std::swap(m_table_size, saved_table_size);
|
||||
}
|
||||
|
||||
/* Close and free all entries. */
|
||||
for (size_t i = 0; i < saved_table_size; i++) {
|
||||
if (KAutoObject *obj = m_objects[i]; obj != nullptr) {
|
||||
obj->Close();
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool KHandleTable::Remove(ams::svc::Handle handle) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Don't allow removal of a pseudo-handle. */
|
||||
if (AMS_UNLIKELY(ams::svc::IsPseudoHandle(handle))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handles must not have reserved bits set. */
|
||||
const auto handle_pack = GetHandleBitPack(handle);
|
||||
if (AMS_UNLIKELY(handle_pack.Get<HandleReserved>() != 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Find the object and free the entry. */
|
||||
KAutoObject *obj = nullptr;
|
||||
{
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
if (AMS_LIKELY(this->IsValidHandle(handle))) {
|
||||
const auto index = handle_pack.Get<HandleIndex>();
|
||||
|
||||
obj = m_objects[index];
|
||||
this->FreeEntry(index);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the object. */
|
||||
obj->Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
Result KHandleTable::Add(ams::svc::Handle *out_handle, KAutoObject *obj) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
/* Never exceed our capacity. */
|
||||
R_UNLESS(m_count < m_table_size, svc::ResultOutOfHandles());
|
||||
|
||||
/* Allocate entry, set output handle. */
|
||||
{
|
||||
const auto linear_id = this->AllocateLinearId();
|
||||
const auto index = this->AllocateEntry();
|
||||
|
||||
m_entry_infos[index].linear_id = linear_id;
|
||||
m_objects[index] = obj;
|
||||
|
||||
obj->Open();
|
||||
|
||||
*out_handle = EncodeHandle(index, linear_id);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KHandleTable::Reserve(ams::svc::Handle *out_handle) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
/* Never exceed our capacity. */
|
||||
R_UNLESS(m_count < m_table_size, svc::ResultOutOfHandles());
|
||||
|
||||
*out_handle = EncodeHandle(this->AllocateEntry(), this->AllocateLinearId());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KHandleTable::Unreserve(ams::svc::Handle handle) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
/* Unpack the handle. */
|
||||
const auto handle_pack = GetHandleBitPack(handle);
|
||||
const auto index = handle_pack.Get<HandleIndex>();
|
||||
const auto linear_id = handle_pack.Get<HandleLinearId>();
|
||||
const auto reserved = handle_pack.Get<HandleReserved>();
|
||||
MESOSPHERE_ASSERT(reserved == 0);
|
||||
MESOSPHERE_ASSERT(linear_id != 0);
|
||||
MESOSPHERE_UNUSED(linear_id, reserved);
|
||||
|
||||
if (AMS_LIKELY(index < m_table_size)) {
|
||||
/* NOTE: This code does not check the linear id. */
|
||||
MESOSPHERE_ASSERT(m_objects[index] == nullptr);
|
||||
this->FreeEntry(index);
|
||||
}
|
||||
}
|
||||
|
||||
void KHandleTable::Register(ams::svc::Handle handle, KAutoObject *obj) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
KScopedDisableDispatch dd;
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
/* Unpack the handle. */
|
||||
const auto handle_pack = GetHandleBitPack(handle);
|
||||
const auto index = handle_pack.Get<HandleIndex>();
|
||||
const auto linear_id = handle_pack.Get<HandleLinearId>();
|
||||
const auto reserved = handle_pack.Get<HandleReserved>();
|
||||
MESOSPHERE_ASSERT(reserved == 0);
|
||||
MESOSPHERE_ASSERT(linear_id != 0);
|
||||
MESOSPHERE_UNUSED(reserved);
|
||||
|
||||
if (AMS_LIKELY(index < m_table_size)) {
|
||||
/* Set the entry. */
|
||||
MESOSPHERE_ASSERT(m_objects[index] == nullptr);
|
||||
|
||||
m_entry_infos[index].linear_id = linear_id;
|
||||
m_objects[index] = obj;
|
||||
|
||||
obj->Open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
struct BlzSegmentFlags {
|
||||
using Offset = util::BitPack16::Field<0, 12, u32>;
|
||||
using Size = util::BitPack16::Field<Offset::Next, 4, u32>;
|
||||
};
|
||||
|
||||
NOINLINE void BlzUncompress(void *_end) {
|
||||
/* Parse the footer, endian agnostic. */
|
||||
static_assert(sizeof(u32) == 4);
|
||||
static_assert(sizeof(u16) == 2);
|
||||
static_assert(sizeof(u8) == 1);
|
||||
|
||||
u8 *end = static_cast<u8 *>(_end);
|
||||
const u32 total_size = (end[-12] << 0) | (end[-11] << 8) | (end[-10] << 16) | (end[- 9] << 24);
|
||||
const u32 footer_size = (end[- 8] << 0) | (end[- 7] << 8) | (end[- 6] << 16) | (end[- 5] << 24);
|
||||
const u32 additional_size = (end[- 4] << 0) | (end[- 3] << 8) | (end[- 2] << 16) | (end[- 1] << 24);
|
||||
|
||||
/* Prepare to decompress. */
|
||||
u8 *cmp_start = end - total_size;
|
||||
u32 cmp_ofs = total_size - footer_size;
|
||||
u32 out_ofs = total_size + additional_size;
|
||||
|
||||
/* Decompress. */
|
||||
while (out_ofs) {
|
||||
u8 control = cmp_start[--cmp_ofs];
|
||||
|
||||
/* Each bit in the control byte is a flag indicating compressed or not compressed. */
|
||||
for (size_t i = 0; i < 8 && out_ofs; ++i, control <<= 1) {
|
||||
if (control & 0x80) {
|
||||
/* NOTE: Nintendo does not check if it's possible to decompress. */
|
||||
/* As such, we will leave the following as a debug assertion, and not a release assertion. */
|
||||
MESOSPHERE_AUDIT(cmp_ofs >= sizeof(u16));
|
||||
cmp_ofs -= sizeof(u16);
|
||||
|
||||
/* Extract segment bounds. */
|
||||
const util::BitPack16 seg_flags{static_cast<u16>((cmp_start[cmp_ofs] << 0) | (cmp_start[cmp_ofs + 1] << 8))};
|
||||
const u32 seg_ofs = seg_flags.Get<BlzSegmentFlags::Offset>() + 3;
|
||||
const u32 seg_size = std::min(seg_flags.Get<BlzSegmentFlags::Size>() + 3, out_ofs);
|
||||
MESOSPHERE_AUDIT(out_ofs + seg_ofs <= total_size + additional_size);
|
||||
|
||||
/* Copy the data. */
|
||||
out_ofs -= seg_size;
|
||||
for (size_t j = 0; j < seg_size; j++) {
|
||||
cmp_start[out_ofs + j] = cmp_start[out_ofs + seg_ofs + j];
|
||||
}
|
||||
} else {
|
||||
/* NOTE: Nintendo does not check if it's possible to copy. */
|
||||
/* As such, we will leave the following as a debug assertion, and not a release assertion. */
|
||||
MESOSPHERE_AUDIT(cmp_ofs >= sizeof(u8));
|
||||
cmp_start[--out_ofs] = cmp_start[--cmp_ofs];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NOINLINE void LoadInitialProcessSegment(const KPageGroup &pg, size_t seg_offset, size_t seg_size, size_t binary_size, KVirtualAddress data, bool compressed) {
|
||||
/* Save the original binary extents, for later use. */
|
||||
const KPhysicalAddress binary_phys = KMemoryLayout::GetLinearPhysicalAddress(data);
|
||||
|
||||
/* Create a page group representing the segment. */
|
||||
KPageGroup segment_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer());
|
||||
MESOSPHERE_R_ABORT_UNLESS(pg.CopyRangeTo(segment_pg, seg_offset, util::AlignUp(seg_size, PageSize)));
|
||||
|
||||
/* Setup the new page group's memory so that we can load the segment. */
|
||||
{
|
||||
KVirtualAddress last_block = Null<KVirtualAddress>;
|
||||
KVirtualAddress last_data = Null<KVirtualAddress>;
|
||||
size_t last_copy_size = 0;
|
||||
size_t last_clear_size = 0;
|
||||
size_t remaining_copy_size = binary_size;
|
||||
for (const auto &block : segment_pg) {
|
||||
/* Get the current block extents. */
|
||||
const auto block_addr = block.GetAddress();
|
||||
const size_t block_size = block.GetSize();
|
||||
if (remaining_copy_size > 0) {
|
||||
/* Determine if we need to copy anything. */
|
||||
const size_t cur_size = std::min<size_t>(block_size, remaining_copy_size);
|
||||
|
||||
/* NOTE: The first block may potentially overlap the binary we want to copy to. */
|
||||
/* Consider e.g. the case where the overall compressed image has size 0x40000, seg_offset is 0x30000, and binary_size is > 0x20000. */
|
||||
/* Suppose too that data points, say, 0x18000 into the compressed image. */
|
||||
/* Suppose finally that we simply naively copy in order. */
|
||||
/* The first iteration of this loop will perform an 0x10000 copy from image+0x18000 to image + 0x30000 (as there is no overlap). */
|
||||
/* The second iteration will perform a copy from image+0x28000 to <allocated pages>. */
|
||||
/* However, the first copy will have trashed the data in the second copy. */
|
||||
/* Thus, we must copy the first block after-the-fact to avoid potentially trashing data in the overlap case. */
|
||||
/* It is guaranteed by pre-condition that only the very first block can overlap with the physical binary, so we can simply memmove it at the end. */
|
||||
if (last_block != Null<KVirtualAddress>) {
|
||||
/* This is guaranteed by pre-condition, but for ease of debugging, check for no overlap. */
|
||||
MESOSPHERE_ASSERT(!util::HasOverlap(GetInteger(binary_phys), binary_size, GetInteger(block_addr), cur_size));
|
||||
MESOSPHERE_UNUSED(binary_phys);
|
||||
|
||||
/* We need to copy. */
|
||||
std::memcpy(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr)), GetVoidPointer(data), cur_size);
|
||||
|
||||
/* If we need to, clear past where we're copying. */
|
||||
if (cur_size != block_size) {
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr + cur_size)), 0, block_size - cur_size);
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
remaining_copy_size -= cur_size;
|
||||
data += cur_size;
|
||||
} else {
|
||||
/* Save the first block, which may potentially overlap, so that we can copy it later. */
|
||||
last_block = KMemoryLayout::GetLinearVirtualAddress(block_addr);
|
||||
last_data = data;
|
||||
last_copy_size = cur_size;
|
||||
last_clear_size = block_size - cur_size;
|
||||
|
||||
/* Advance. */
|
||||
remaining_copy_size -= cur_size;
|
||||
data += cur_size;
|
||||
}
|
||||
} else {
|
||||
/* We don't have data to copy, so we should just clear the pages. */
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr)), 0, block_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle a last block. */
|
||||
if (last_copy_size != 0) {
|
||||
if (last_block != last_data) {
|
||||
std::memmove(GetVoidPointer(last_block), GetVoidPointer(last_data), last_copy_size);
|
||||
}
|
||||
if (last_clear_size != 0) {
|
||||
std::memset(GetVoidPointer(last_block + last_copy_size), 0, last_clear_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If compressed, uncompress the data. */
|
||||
if (compressed) {
|
||||
/* Get the temporary region. */
|
||||
const auto &temp_region = KMemoryLayout::GetTempRegion();
|
||||
MESOSPHERE_ABORT_UNLESS(temp_region.GetEndAddress() != 0);
|
||||
|
||||
/* Map the process's memory into the temporary region. */
|
||||
KProcessAddress temp_address = Null<KProcessAddress>;
|
||||
MESOSPHERE_R_ABORT_UNLESS(Kernel::GetKernelPageTable().MapPageGroup(std::addressof(temp_address), segment_pg, temp_region.GetAddress(), temp_region.GetSize() / PageSize, KMemoryState_Kernel, KMemoryPermission_KernelReadWrite));
|
||||
ON_SCOPE_EXIT { MESOSPHERE_R_ABORT_UNLESS(Kernel::GetKernelPageTable().UnmapPageGroup(temp_address, segment_pg, KMemoryState_Kernel)); };
|
||||
|
||||
/* Uncompress the data. */
|
||||
BlzUncompress(GetVoidPointer(temp_address + binary_size));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result KInitialProcessReader::MakeCreateProcessParameter(ams::svc::CreateProcessParameter *out, bool enable_aslr) const {
|
||||
/* Get and validate addresses/sizes. */
|
||||
const uintptr_t rx_address = m_kip_header.GetRxAddress();
|
||||
const size_t rx_size = m_kip_header.GetRxSize();
|
||||
const uintptr_t ro_address = m_kip_header.GetRoAddress();
|
||||
const size_t ro_size = m_kip_header.GetRoSize();
|
||||
const uintptr_t rw_address = m_kip_header.GetRwAddress();
|
||||
const size_t rw_size = m_kip_header.GetRwSize();
|
||||
const uintptr_t bss_address = m_kip_header.GetBssAddress();
|
||||
const size_t bss_size = m_kip_header.GetBssSize();
|
||||
R_UNLESS(util::IsAligned(rx_address, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(util::IsAligned(ro_address, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(util::IsAligned(rw_address, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(rx_address <= rx_address + util::AlignUp(rx_size, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(ro_address <= ro_address + util::AlignUp(ro_size, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(rw_address <= rw_address + util::AlignUp(rw_size, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(bss_address <= bss_address + util::AlignUp(bss_size, PageSize), svc::ResultInvalidAddress());
|
||||
R_UNLESS(rx_address + util::AlignUp(rx_size, PageSize) <= ro_address, svc::ResultInvalidAddress());
|
||||
R_UNLESS(ro_address + util::AlignUp(ro_size, PageSize) <= rw_address, svc::ResultInvalidAddress());
|
||||
R_UNLESS(rw_address + rw_size <= bss_address, svc::ResultInvalidAddress());
|
||||
|
||||
/* Validate the address space. */
|
||||
if (this->Is64BitAddressSpace()) {
|
||||
R_UNLESS(this->Is64Bit(), svc::ResultInvalidCombination());
|
||||
}
|
||||
|
||||
const uintptr_t start_address = rx_address;
|
||||
const uintptr_t end_address = bss_size > 0 ? bss_address + bss_size : rw_address + rw_size;
|
||||
MESOSPHERE_ABORT_UNLESS(start_address == 0);
|
||||
|
||||
/* Default fields in parameter to zero. */
|
||||
*out = {};
|
||||
|
||||
/* Set fields in parameter. */
|
||||
out->code_address = 0;
|
||||
out->code_num_pages = util::AlignUp(end_address - start_address, PageSize) / PageSize;
|
||||
out->program_id = m_kip_header.GetProgramId();
|
||||
out->version = m_kip_header.GetVersion();
|
||||
out->flags = 0;
|
||||
out->reslimit = ams::svc::InvalidHandle;
|
||||
out->system_resource_num_pages = 0;
|
||||
|
||||
/* Copy name field. */
|
||||
m_kip_header.GetName(out->name, sizeof(out->name));
|
||||
|
||||
/* Apply other flags. */
|
||||
if (this->Is64Bit()) {
|
||||
out->flags |= ams::svc::CreateProcessFlag_Is64Bit;
|
||||
}
|
||||
if (this->Is64BitAddressSpace()) {
|
||||
out->flags |= (GetTargetFirmware() >= TargetFirmware_2_0_0) ? ams::svc::CreateProcessFlag_AddressSpace64Bit : ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated;
|
||||
} else {
|
||||
out->flags |= ams::svc::CreateProcessFlag_AddressSpace32Bit;
|
||||
}
|
||||
if (enable_aslr) {
|
||||
out->flags |= ams::svc::CreateProcessFlag_EnableAslr;
|
||||
}
|
||||
|
||||
/* All initial processes should disable device address space merge. */
|
||||
out->flags |= ams::svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
|
||||
|
||||
/* Set and check code address. */
|
||||
/* NOTE: Even though Nintendo passes a size to GetAddressSpaceStart at other call sites, they pass */
|
||||
/* a number of pages here. Even though this is presumably only used for debug assertions, this is */
|
||||
/* almost certainly a bug. */
|
||||
using ASType = KAddressSpaceInfo::Type;
|
||||
const ASType as_type = this->Is64BitAddressSpace() ? ((GetTargetFirmware() >= TargetFirmware_2_0_0) ? KAddressSpaceInfo::Type_Map39Bit : KAddressSpaceInfo::Type_MapSmall) : KAddressSpaceInfo::Type_MapSmall;
|
||||
const uintptr_t map_start = KAddressSpaceInfo::GetAddressSpaceStart(static_cast<ams::svc::CreateProcessFlag>(out->flags), as_type, out->code_num_pages);
|
||||
const size_t map_size = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(out->flags), as_type);
|
||||
const uintptr_t map_end = map_start + map_size;
|
||||
out->code_address = map_start + start_address;
|
||||
MESOSPHERE_ABORT_UNLESS((out->code_address / PageSize) + out->code_num_pages <= (map_end / PageSize));
|
||||
|
||||
/* Apply ASLR, if needed. */
|
||||
if (enable_aslr) {
|
||||
const size_t choices = (map_end / KernelAslrAlignment) - (util::AlignUp(out->code_address + out->code_num_pages * PageSize, KernelAslrAlignment) / KernelAslrAlignment);
|
||||
out->code_address += KSystemControl::GenerateRandomRange(0, choices) * KernelAslrAlignment;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KInitialProcessReader::Load(const KPageGroup &pg, KVirtualAddress data) const {
|
||||
/* Prepare to layout the data. */
|
||||
const KVirtualAddress rx_data = data;
|
||||
const KVirtualAddress ro_data = rx_data + m_kip_header.GetRxCompressedSize();
|
||||
const KVirtualAddress rw_data = ro_data + m_kip_header.GetRoCompressedSize();
|
||||
const size_t rx_size = m_kip_header.GetRxSize();
|
||||
const size_t ro_size = m_kip_header.GetRoSize();
|
||||
const size_t rw_size = m_kip_header.GetRwSize();
|
||||
|
||||
/* If necessary, setup bss. */
|
||||
if (const size_t bss_size = m_kip_header.GetBssSize(); bss_size > 0) {
|
||||
/* Determine how many additional pages are needed for bss. */
|
||||
const u64 rw_end = util::AlignUp<u64>(m_kip_header.GetRwAddress() + m_kip_header.GetRwSize(), PageSize);
|
||||
const u64 bss_end = util::AlignUp<u64>(m_kip_header.GetBssAddress() + m_kip_header.GetBssSize(), PageSize);
|
||||
if (rw_end != bss_end) {
|
||||
/* Find the pages corresponding to bss. */
|
||||
size_t cur_offset = 0;
|
||||
size_t remaining_size = bss_end - rw_end;
|
||||
size_t bss_offset = rw_end - m_kip_header.GetRxAddress();
|
||||
for (auto it = pg.begin(); it != pg.end() && remaining_size > 0; ++it) {
|
||||
/* Get the current size. */
|
||||
const size_t cur_size = it->GetSize();
|
||||
|
||||
/* Determine if the offset is in range. */
|
||||
const size_t rel_diff = bss_offset - cur_offset;
|
||||
const bool is_before = cur_offset <= bss_offset;
|
||||
cur_offset += cur_size;
|
||||
if (is_before && bss_offset < cur_offset) {
|
||||
/* It is, so clear the bss range. */
|
||||
const size_t block_size = std::min<size_t>(cur_size - rel_diff, remaining_size);
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(it->GetAddress() + rel_diff)), 0, block_size);
|
||||
|
||||
/* Advance. */
|
||||
cur_offset = bss_offset + block_size;
|
||||
remaining_size -= block_size;
|
||||
bss_offset += block_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Load .rwdata. */
|
||||
LoadInitialProcessSegment(pg, m_kip_header.GetRwAddress() - m_kip_header.GetRxAddress(), rw_size, m_kip_header.GetRwCompressedSize(), rw_data, m_kip_header.IsRwCompressed());
|
||||
|
||||
/* Load .rodata. */
|
||||
LoadInitialProcessSegment(pg, m_kip_header.GetRoAddress() - m_kip_header.GetRxAddress(), ro_size, m_kip_header.GetRoCompressedSize(), ro_data, m_kip_header.IsRoCompressed());
|
||||
|
||||
/* Load .text. */
|
||||
LoadInitialProcessSegment(pg, m_kip_header.GetRxAddress() - m_kip_header.GetRxAddress(), rx_size, m_kip_header.GetRxCompressedSize(), rx_data, m_kip_header.IsRxCompressed());
|
||||
}
|
||||
|
||||
Result KInitialProcessReader::SetMemoryPermissions(KProcessPageTable &page_table, const ams::svc::CreateProcessParameter ¶ms) const {
|
||||
const size_t rx_size = m_kip_header.GetRxSize();
|
||||
const size_t ro_size = m_kip_header.GetRoSize();
|
||||
const size_t rw_size = m_kip_header.GetRwSize();
|
||||
const size_t bss_size = m_kip_header.GetBssSize();
|
||||
|
||||
/* Set R-X pages. */
|
||||
if (rx_size) {
|
||||
const uintptr_t start = m_kip_header.GetRxAddress() + params.code_address;
|
||||
R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(rx_size, PageSize), ams::svc::MemoryPermission_ReadExecute));
|
||||
}
|
||||
|
||||
/* Set R-- pages. */
|
||||
if (ro_size) {
|
||||
const uintptr_t start = m_kip_header.GetRoAddress() + params.code_address;
|
||||
R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(ro_size, PageSize), ams::svc::MemoryPermission_Read));
|
||||
}
|
||||
|
||||
/* Set RW- pages. */
|
||||
if (rw_size || bss_size) {
|
||||
const uintptr_t start = (rw_size ? m_kip_header.GetRwAddress() : m_kip_header.GetBssAddress()) + params.code_address;
|
||||
const uintptr_t end = (bss_size ? m_kip_header.GetBssAddress() + bss_size : m_kip_header.GetRwAddress() + rw_size) + params.code_address;
|
||||
R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(end - start, PageSize), ams::svc::MemoryPermission_ReadWrite));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KInterruptEvent::Initialize(int32_t interrupt_name, ams::svc::InterruptType type) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Verify the interrupt is defined and global. */
|
||||
R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(interrupt_name), svc::ResultOutOfRange());
|
||||
R_UNLESS(Kernel::GetInterruptManager().IsGlobal(interrupt_name), svc::ResultOutOfRange());
|
||||
|
||||
/* Set interrupt id. */
|
||||
m_interrupt_id = interrupt_name;
|
||||
|
||||
/* Set core id. */
|
||||
m_core_id = GetCurrentCoreId();
|
||||
|
||||
/* Initialize readable event base. */
|
||||
KReadableEvent::Initialize(nullptr);
|
||||
|
||||
/* Bind ourselves as the handler for our interrupt id. */
|
||||
R_TRY(Kernel::GetInterruptManager().BindHandler(this, m_interrupt_id, m_core_id, KInterruptController::PriorityLevel_High, true, type == ams::svc::InterruptType_Level));
|
||||
|
||||
/* Mark initialized. */
|
||||
m_is_initialized = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KInterruptEvent::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Unbind ourselves as the handler for our interrupt id. */
|
||||
Kernel::GetInterruptManager().UnbindHandler(m_interrupt_id, m_core_id);
|
||||
|
||||
/* Synchronize the unbind on all cores, before proceeding. */
|
||||
KDpcManager::Sync();
|
||||
|
||||
/* Perform inherited finalization. */
|
||||
KReadableEvent::Finalize();
|
||||
}
|
||||
|
||||
Result KInterruptEvent::Reset() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Lock the scheduler. */
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Clear the event. */
|
||||
R_TRY(KReadableEvent::Reset());
|
||||
|
||||
/* Clear the interrupt. */
|
||||
Kernel::GetInterruptManager().ClearInterrupt(m_interrupt_id, m_core_id);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
KInterruptTask *KInterruptEvent::OnInterrupt(s32 interrupt_id) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
return this;
|
||||
}
|
||||
|
||||
void KInterruptEvent::DoTask() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Lock the scheduler. */
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Signal. */
|
||||
this->Signal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KInterruptTaskManager::TaskQueue::Enqueue(KInterruptTask *task) {
|
||||
MESOSPHERE_ASSERT(task != m_head);
|
||||
MESOSPHERE_ASSERT(task != m_tail);
|
||||
MESOSPHERE_AUDIT(task->GetNextTask() == nullptr);
|
||||
|
||||
/* Insert the task into the queue. */
|
||||
if (m_tail != nullptr) {
|
||||
m_tail->SetNextTask(task);
|
||||
} else {
|
||||
m_head = task;
|
||||
}
|
||||
|
||||
m_tail = task;
|
||||
|
||||
/* Set the next task for auditing. */
|
||||
#if defined (MESOSPHERE_BUILD_FOR_AUDITING)
|
||||
task->SetNextTask(GetDummyInterruptTask());
|
||||
#endif
|
||||
}
|
||||
|
||||
void KInterruptTaskManager::TaskQueue::Dequeue() {
|
||||
MESOSPHERE_ASSERT(m_head != nullptr);
|
||||
MESOSPHERE_ASSERT(m_tail != nullptr);
|
||||
MESOSPHERE_AUDIT(m_tail->GetNextTask() == GetDummyInterruptTask());
|
||||
|
||||
/* Pop the task from the front of the queue. */
|
||||
KInterruptTask *old_head = m_head;
|
||||
|
||||
if (m_head == m_tail) {
|
||||
m_head = nullptr;
|
||||
m_tail = nullptr;
|
||||
} else {
|
||||
m_head = m_head->GetNextTask();
|
||||
}
|
||||
|
||||
#if defined (MESOSPHERE_BUILD_FOR_AUDITING)
|
||||
old_head->SetNextTask(nullptr);
|
||||
#else
|
||||
AMS_UNUSED(old_head);
|
||||
#endif
|
||||
}
|
||||
|
||||
void KInterruptTaskManager::EnqueueTask(KInterruptTask *task) {
|
||||
MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled());
|
||||
|
||||
/* Enqueue the task and signal the scheduler. */
|
||||
m_task_queue.Enqueue(task);
|
||||
Kernel::GetScheduler().SetInterruptTaskRunnable();
|
||||
}
|
||||
|
||||
void KInterruptTaskManager::DoTasks() {
|
||||
/* Execute pending tasks. */
|
||||
const s64 start_time = KHardwareTimer::GetTick();
|
||||
for (KInterruptTask *task = m_task_queue.GetHead(); task != nullptr; task = m_task_queue.GetHead()) {
|
||||
/* Dequeue the task. */
|
||||
m_task_queue.Dequeue();
|
||||
|
||||
/* Do the task with interrupts temporarily enabled. */
|
||||
{
|
||||
KScopedInterruptEnable ei;
|
||||
|
||||
task->DoTask();
|
||||
}
|
||||
}
|
||||
const s64 end_time = KHardwareTimer::GetTick();
|
||||
|
||||
/* Increment the time we've spent executing. */
|
||||
m_cpu_time += end_time - start_time;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit KLightLock g_io_pool_lock;
|
||||
constinit bool g_pool_used[ams::svc::IoPoolType_Count];
|
||||
|
||||
struct IoRegionExtents {
|
||||
KPhysicalAddress address;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
|
||||
|
||||
#include "board/nintendo/nx/kern_k_io_pool.board.nintendo_nx.inc"
|
||||
|
||||
#elif defined(AMS_SVC_IO_POOL_NOT_SUPPORTED)
|
||||
|
||||
#include "kern_k_io_pool.unsupported.inc"
|
||||
|
||||
#else
|
||||
|
||||
#error "Unknown context for IoPoolType!"
|
||||
|
||||
#endif
|
||||
|
||||
constexpr bool IsValidIoRegionImpl(ams::svc::IoPoolType pool_type, KPhysicalAddress address, size_t size) {
|
||||
/* NOTE: It seems likely this depends on pool type, but this isn't confirmable as of now. */
|
||||
MESOSPHERE_UNUSED(pool_type);
|
||||
|
||||
/* Check if the address/size falls within any allowable extents. */
|
||||
for (const auto &extents : g_io_region_extents) {
|
||||
if (extents.size != 0 && extents.address <= address && address + size - 1 <= extents.address + extents.size - 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool KIoPool::IsValidIoPoolType(ams::svc::IoPoolType pool_type) {
|
||||
return IsValidIoPoolTypeImpl(pool_type);
|
||||
}
|
||||
|
||||
Result KIoPool::Initialize(ams::svc::IoPoolType pool_type) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Register the pool type. */
|
||||
{
|
||||
/* Lock the pool used table. */
|
||||
KScopedLightLock lk(g_io_pool_lock);
|
||||
|
||||
/* Check that the pool isn't already used. */
|
||||
R_UNLESS(!g_pool_used[pool_type], svc::ResultBusy());
|
||||
|
||||
/* Set the pool as used. */
|
||||
g_pool_used[pool_type] = true;
|
||||
}
|
||||
|
||||
/* Set our fields. */
|
||||
m_pool_type = pool_type;
|
||||
m_is_initialized = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KIoPool::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Lock the pool used table. */
|
||||
KScopedLightLock lk(g_io_pool_lock);
|
||||
|
||||
/* Check that the pool is used. */
|
||||
MESOSPHERE_ASSERT(g_pool_used[m_pool_type]);
|
||||
|
||||
/* Set the pool as unused. */
|
||||
g_pool_used[m_pool_type] = false;
|
||||
}
|
||||
|
||||
Result KIoPool::AddIoRegion(KIoRegion *new_region) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Check that the region is allowed. */
|
||||
R_UNLESS(IsValidIoRegionImpl(m_pool_type, new_region->GetAddress(), new_region->GetSize()), svc::ResultInvalidMemoryRegion());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Check that the desired range isn't already in our pool. */
|
||||
{
|
||||
/* Get the lowest region with address >= the new region that's already in our tree. */
|
||||
auto lowest_after = m_io_region_tree.nfind_key(new_region->GetAddress());
|
||||
if (lowest_after != m_io_region_tree.end()) {
|
||||
R_UNLESS(!lowest_after->Overlaps(new_region->GetAddress(), new_region->GetSize()), svc::ResultBusy());
|
||||
}
|
||||
|
||||
/* There is no region with address >= the new region already in our tree, but we also need to check */
|
||||
/* for a region with address < the new region already in our tree. */
|
||||
if (lowest_after != m_io_region_tree.begin()) {
|
||||
auto highest_before = --lowest_after;
|
||||
R_UNLESS(!highest_before->Overlaps(new_region->GetAddress(), new_region->GetSize()), svc::ResultBusy());
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the region to our pool. */
|
||||
m_io_region_tree.insert(*new_region);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KIoPool::RemoveIoRegion(KIoRegion *region) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Remove the region from our tree. */
|
||||
m_io_region_tree.erase(m_io_region_tree.iterator_to(*region));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
|
||||
constexpr IoRegionExtents g_io_region_extents[4] = {
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
{ Null<KPhysicalAddress>, 0 },
|
||||
};
|
||||
|
||||
constexpr bool IsValidIoPoolTypeImpl(ams::svc::IoPoolType pool_type) {
|
||||
MESOSPHERE_UNUSED(pool_type);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KIoRegion::Initialize(KIoPool *pool, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set fields. */
|
||||
m_physical_address = phys_addr;
|
||||
m_size = size;
|
||||
m_mapping = mapping;
|
||||
m_permission = perm;
|
||||
m_pool = pool;
|
||||
m_is_mapped = false;
|
||||
|
||||
/* Add ourselves to our pool. */
|
||||
R_TRY(m_pool->AddIoRegion(this));
|
||||
|
||||
/* Open a reference to our pool. */
|
||||
m_pool->Open();
|
||||
|
||||
/* Mark ourselves as initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KIoRegion::Finalize() {
|
||||
/* Remove ourselves from our pool. */
|
||||
m_pool->RemoveIoRegion(this);
|
||||
|
||||
/* Close our reference to our pool. */
|
||||
m_pool->Close();
|
||||
}
|
||||
|
||||
Result KIoRegion::Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Check that the desired perm is allowable. */
|
||||
R_UNLESS((m_permission | map_perm) == m_permission, svc::ResultInvalidNewMemoryPermission());
|
||||
|
||||
/* Check that the size is correct. */
|
||||
R_UNLESS(size == m_size, svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Check that we're not already mapped. */
|
||||
R_UNLESS(!m_is_mapped, svc::ResultInvalidState());
|
||||
|
||||
/* Map ourselves. */
|
||||
R_TRY(GetCurrentProcess().GetPageTable().MapIoRegion(address, m_physical_address, size, m_mapping, map_perm));
|
||||
|
||||
/* Add ourselves to the current process. */
|
||||
GetCurrentProcess().AddIoRegion(this);
|
||||
|
||||
/* Note that we're mapped. */
|
||||
m_is_mapped = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KIoRegion::Unmap(KProcessAddress address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Check that the size is correct. */
|
||||
R_UNLESS(size == m_size, svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Unmap ourselves. */
|
||||
R_TRY(GetCurrentProcess().GetPageTable().UnmapIoRegion(address, m_physical_address, size, m_mapping));
|
||||
|
||||
/* Remove ourselves from the current process. */
|
||||
GetCurrentProcess().RemoveIoRegion(this);
|
||||
|
||||
/* Note that we're unmapped. */
|
||||
m_is_mapped = false;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KLightClientSession::Destroy() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
m_parent->OnClientClosed();
|
||||
}
|
||||
|
||||
void KLightClientSession::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
}
|
||||
|
||||
Result KLightClientSession::SendSyncRequest(u32 *data) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Get the request thread. */
|
||||
KThread *cur_thread = GetCurrentThreadPointer();
|
||||
|
||||
/* Set the light data. */
|
||||
cur_thread->SetLightSessionData(data);
|
||||
|
||||
/* Send the request. */
|
||||
R_RETURN(m_parent->OnRequest(cur_thread));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue {
|
||||
private:
|
||||
KThread::WaiterList *m_wait_list;
|
||||
bool m_allow_terminating_thread;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKLightConditionVariable(KThread::WaiterList *wl, bool term) : KThreadQueue(), m_wait_list(wl), m_allow_terminating_thread(term) { /* ... */ }
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Only process waits if we're allowed to. */
|
||||
if (svc::ResultTerminationRequested::Includes(wait_result) && m_allow_terminating_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the thread from the waiting thread from the light condition variable. */
|
||||
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void KLightConditionVariable::Wait(KLightLock *lock, s64 timeout, bool allow_terminating_thread) {
|
||||
/* Create thread queue. */
|
||||
KThread *owner = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
|
||||
ThreadQueueImplForKLightConditionVariable wait_queue(std::addressof(m_wait_list), allow_terminating_thread);
|
||||
|
||||
/* Sleep the thread. */
|
||||
{
|
||||
KScopedSchedulerLockAndSleep lk(std::addressof(timer), owner, timeout);
|
||||
|
||||
if (!allow_terminating_thread && owner->IsTerminationRequested()) {
|
||||
lk.CancelSleep();
|
||||
return;
|
||||
}
|
||||
|
||||
lock->Unlock();
|
||||
|
||||
/* Add the thread to the queue. */
|
||||
m_wait_list.push_back(*owner);
|
||||
|
||||
/* Begin waiting. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
owner->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Re-acquire the lock. */
|
||||
lock->Lock();
|
||||
}
|
||||
|
||||
void KLightConditionVariable::Broadcast() {
|
||||
KScopedSchedulerLock lk;
|
||||
|
||||
/* Signal all threads. */
|
||||
for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it = m_wait_list.erase(it)) {
|
||||
it->EndWait(ResultSuccess());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
class ThreadQueueImplForKLightLock final : public KThreadQueue {
|
||||
public:
|
||||
constexpr ThreadQueueImplForKLightLock() : KThreadQueue() { /* ... */ }
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Do nothing, waiting to acquire a light lock cannot be canceled. */
|
||||
MESOSPHERE_UNUSED(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||
KThread *cur_thread = reinterpret_cast<KThread *>(_cur_thread);
|
||||
ThreadQueueImplForKLightLock wait_queue;
|
||||
|
||||
/* Pend the current thread waiting on the owner thread. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Ensure we actually have locking to do. */
|
||||
if (m_tag.Load<std::memory_order_relaxed>() != _owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add the current thread as a waiter on the owner. */
|
||||
KThread *owner_thread = reinterpret_cast<KThread *>(_owner & ~1ul);
|
||||
cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(m_tag)));
|
||||
owner_thread->AddWaiter(cur_thread);
|
||||
|
||||
/* Begin waiting to hold the lock. */
|
||||
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||
|
||||
if (owner_thread->IsSuspended()) {
|
||||
owner_thread->ContinueIfHasKernelWaiters();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
||||
KThread *owner_thread = reinterpret_cast<KThread *>(_cur_thread);
|
||||
|
||||
/* Unlock. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Get the next owner. */
|
||||
bool has_waiters;
|
||||
KThread *next_owner = owner_thread->RemoveWaiterByKey(std::addressof(has_waiters), reinterpret_cast<uintptr_t>(std::addressof(m_tag)));
|
||||
|
||||
/* Pass the lock to the next owner. */
|
||||
uintptr_t next_tag = 0;
|
||||
if (next_owner != nullptr) {
|
||||
next_tag = reinterpret_cast<uintptr_t>(next_owner) | static_cast<uintptr_t>(has_waiters);
|
||||
|
||||
next_owner->EndWait(ResultSuccess());
|
||||
|
||||
if (next_owner->IsSuspended()) {
|
||||
next_owner->ContinueIfHasKernelWaiters();
|
||||
}
|
||||
}
|
||||
|
||||
/* We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if so. */
|
||||
if (owner_thread->IsSuspended()) {
|
||||
owner_thread->TrySuspend();
|
||||
}
|
||||
|
||||
/* Write the new tag value. */
|
||||
m_tag.Store<std::memory_order_release>(next_tag);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u64 InvalidThreadId = -1ull;
|
||||
|
||||
class ThreadQueueImplForKLightServerSessionRequest final : public KThreadQueue {
|
||||
private:
|
||||
KThread::WaiterList *m_wait_list;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKLightServerSessionRequest(KThread::WaiterList *wl) : KThreadQueue(), m_wait_list(wl) { /* ... */ }
|
||||
|
||||
virtual void EndWait(KThread *waiting_thread, Result wait_result) override {
|
||||
/* Remove the thread from our wait list. */
|
||||
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
|
||||
|
||||
/* Invoke the base end wait handler. */
|
||||
KThreadQueue::EndWait(waiting_thread, wait_result);
|
||||
}
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Remove the thread from our wait list. */
|
||||
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
class ThreadQueueImplForKLightServerSessionReceive final : public KThreadQueue {
|
||||
private:
|
||||
KThread **m_server_thread;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKLightServerSessionReceive(KThread **st) : KThreadQueue(), m_server_thread(st) { /* ... */ }
|
||||
|
||||
virtual void EndWait(KThread *waiting_thread, Result wait_result) override {
|
||||
/* Clear the server thread. */
|
||||
*m_server_thread = nullptr;
|
||||
|
||||
/* Set the waiting thread as not cancelable. */
|
||||
waiting_thread->ClearCancellable();
|
||||
|
||||
/* Invoke the base end wait handler. */
|
||||
KThreadQueue::EndWait(waiting_thread, wait_result);
|
||||
}
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Clear the server thread. */
|
||||
*m_server_thread = nullptr;
|
||||
|
||||
/* Set the waiting thread as not cancelable. */
|
||||
waiting_thread->ClearCancellable();
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void KLightServerSession::Destroy() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
this->CleanupRequests();
|
||||
|
||||
m_parent->OnServerClosed();
|
||||
}
|
||||
|
||||
void KLightServerSession::OnClientClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
this->CleanupRequests();
|
||||
}
|
||||
|
||||
Result KLightServerSession::OnRequest(KThread *request_thread) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
ThreadQueueImplForKLightServerSessionRequest wait_queue(std::addressof(m_request_list));
|
||||
|
||||
/* Send the request. */
|
||||
{
|
||||
/* Lock the scheduler. */
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Check that the server isn't closed. */
|
||||
R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed());
|
||||
|
||||
/* Check that the request thread isn't terminating. */
|
||||
R_UNLESS(!request_thread->IsTerminationRequested(), svc::ResultTerminationRequested());
|
||||
|
||||
/* Add the request thread to our list. */
|
||||
m_request_list.push_back(*request_thread);
|
||||
|
||||
/* Begin waiting on the request. */
|
||||
request_thread->BeginWait(std::addressof(wait_queue));
|
||||
|
||||
/* If we have a server thread, end its wait. */
|
||||
if (m_server_thread != nullptr) {
|
||||
m_server_thread->EndWait(ResultSuccess());
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: Nintendo returns GetCurrentThread().GetWaitResult() here. */
|
||||
/* This is technically incorrect, although it doesn't cause problems in practice */
|
||||
/* because this is only ever called with request_thread = GetCurrentThreadPointer(). */
|
||||
R_RETURN(request_thread->GetWaitResult());
|
||||
}
|
||||
|
||||
Result KLightServerSession::ReplyAndReceive(u32 *data) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set the server context. */
|
||||
GetCurrentThread().SetLightSessionData(data);
|
||||
|
||||
/* Reply, if we need to. */
|
||||
if (data[0] & KLightSession::ReplyFlag) {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Check that we're open. */
|
||||
R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed());
|
||||
R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed());
|
||||
|
||||
/* Check that we have a request to reply to. */
|
||||
R_UNLESS(m_current_request != nullptr, svc::ResultInvalidState());
|
||||
|
||||
/* Check that the server thread id is correct. */
|
||||
R_UNLESS(m_server_thread_id == GetCurrentThread().GetId(), svc::ResultInvalidState());
|
||||
|
||||
/* If we can reply, do so. */
|
||||
if (!m_current_request->IsTerminationRequested()) {
|
||||
std::memcpy(m_current_request->GetLightSessionData(), GetCurrentThread().GetLightSessionData(), KLightSession::DataSize);
|
||||
m_current_request->EndWait(ResultSuccess());
|
||||
}
|
||||
|
||||
/* Close our current request. */
|
||||
m_current_request->Close();
|
||||
|
||||
/* Clear our current request. */
|
||||
m_current_request = nullptr;
|
||||
m_server_thread_id = InvalidThreadId;
|
||||
}
|
||||
|
||||
/* Close any pending objects before we wait. */
|
||||
GetCurrentThread().DestroyClosedObjects();
|
||||
|
||||
/* Create the wait queue for our receive. */
|
||||
ThreadQueueImplForKLightServerSessionReceive wait_queue(std::addressof(m_server_thread));
|
||||
|
||||
/* Receive. */
|
||||
while (true) {
|
||||
/* Try to receive a request. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Check that we aren't already receiving. */
|
||||
R_UNLESS(m_server_thread == nullptr, svc::ResultInvalidState());
|
||||
R_UNLESS(m_server_thread_id == InvalidThreadId, svc::ResultInvalidState());
|
||||
|
||||
/* Check that we're open. */
|
||||
R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed());
|
||||
R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed());
|
||||
|
||||
/* Check that we're not terminating. */
|
||||
R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested());
|
||||
|
||||
/* If we have a request available, use it. */
|
||||
if (auto head = m_request_list.begin(); head != m_request_list.end()) {
|
||||
/* Set our current request. */
|
||||
m_current_request = std::addressof(*head);
|
||||
m_current_request->Open();
|
||||
|
||||
/* Set our server thread id. */
|
||||
m_server_thread_id = GetCurrentThread().GetId();
|
||||
|
||||
/* Copy the client request data. */
|
||||
std::memcpy(GetCurrentThread().GetLightSessionData(), m_current_request->GetLightSessionData(), KLightSession::DataSize);
|
||||
|
||||
/* We successfully received. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* We need to wait for a request to come in. */
|
||||
|
||||
/* Check if we were cancelled. */
|
||||
if (GetCurrentThread().IsWaitCancelled()) {
|
||||
GetCurrentThread().ClearWaitCancelled();
|
||||
R_THROW(svc::ResultCancelled());
|
||||
}
|
||||
|
||||
/* Mark ourselves as cancellable. */
|
||||
GetCurrentThread().SetCancellable();
|
||||
|
||||
/* Wait for a request to come in. */
|
||||
m_server_thread = GetCurrentThreadPointer();
|
||||
GetCurrentThread().BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* We waited to receive a request; if our wait failed, return the failing result. */
|
||||
R_TRY(GetCurrentThread().GetWaitResult());
|
||||
}
|
||||
}
|
||||
|
||||
void KLightServerSession::CleanupRequests() {
|
||||
/* Cleanup all pending requests. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Handle the current request. */
|
||||
if (m_current_request != nullptr) {
|
||||
/* Reply to the current request. */
|
||||
if (!m_current_request->IsTerminationRequested()) {
|
||||
m_current_request->EndWait(svc::ResultSessionClosed());
|
||||
}
|
||||
|
||||
/* Clear our current request. */
|
||||
m_current_request->Close();
|
||||
m_current_request = nullptr;
|
||||
m_server_thread_id = InvalidThreadId;
|
||||
}
|
||||
|
||||
/* Reply to all other requests. */
|
||||
for (auto &thread : m_request_list) {
|
||||
thread.EndWait(svc::ResultSessionClosed());
|
||||
}
|
||||
|
||||
/* Wait up our server thread, if we have one. */
|
||||
if (m_server_thread != nullptr) {
|
||||
m_server_thread->EndWait(svc::ResultSessionClosed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KLightSession::Initialize(KClientPort *client_port, uintptr_t name) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Increment reference count. */
|
||||
/* Because reference count is one on creation, this will result */
|
||||
/* in a reference count of two. Thus, when both server and client are closed */
|
||||
/* this object will be destroyed. */
|
||||
this->Open();
|
||||
|
||||
/* Create our sub sessions. */
|
||||
KAutoObject::Create<KLightServerSession>(std::addressof(m_server));
|
||||
KAutoObject::Create<KLightClientSession>(std::addressof(m_client));
|
||||
|
||||
/* Initialize our sub sessions. */
|
||||
m_server.Initialize(this);
|
||||
m_client.Initialize(this);
|
||||
|
||||
/* Set state and name. */
|
||||
m_state = State::Normal;
|
||||
m_name = name;
|
||||
|
||||
/* Set our owner process. */
|
||||
m_process = GetCurrentProcessPointer();
|
||||
m_process->Open();
|
||||
|
||||
/* Set our port. */
|
||||
m_port = client_port;
|
||||
if (m_port != nullptr) {
|
||||
m_port->Open();
|
||||
}
|
||||
|
||||
/* Mark initialized. */
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void KLightSession::Finalize() {
|
||||
if (m_port != nullptr) {
|
||||
m_port->OnSessionFinalized();
|
||||
m_port->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void KLightSession::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
if (m_state == State::Normal) {
|
||||
m_state = State::ServerClosed;
|
||||
m_client.OnServerClosed();
|
||||
}
|
||||
|
||||
this->Close();
|
||||
}
|
||||
|
||||
void KLightSession::OnClientClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
if (m_state == State::Normal) {
|
||||
m_state = State::ClientClosed;
|
||||
m_server.OnClientClosed();
|
||||
}
|
||||
|
||||
this->Close();
|
||||
}
|
||||
|
||||
void KLightSession::PostDestroy(uintptr_t arg) {
|
||||
/* Release the session count resource the owner process holds. */
|
||||
KProcess *owner = reinterpret_cast<KProcess *>(arg);
|
||||
owner->ReleaseResource(ams::svc::LimitableResource_SessionCountMax, 1);
|
||||
owner->Close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const std::pair<KMemoryState, const char *> MemoryStateNames[] = {
|
||||
{KMemoryState_Free , "----- Free -----"},
|
||||
{KMemoryState_IoMemory , "IoMemory "},
|
||||
{KMemoryState_IoRegister , "IoRegister "},
|
||||
{KMemoryState_Static , "Static "},
|
||||
{KMemoryState_Code , "Code "},
|
||||
{KMemoryState_CodeData , "CodeData "},
|
||||
{KMemoryState_Normal , "Normal "},
|
||||
{KMemoryState_Shared , "Shared "},
|
||||
{KMemoryState_AliasCode , "AliasCode "},
|
||||
{KMemoryState_AliasCodeData , "AliasCodeData "},
|
||||
{KMemoryState_Ipc , "Ipc "},
|
||||
{KMemoryState_Stack , "Stack "},
|
||||
{KMemoryState_ThreadLocal , "ThreadLocal "},
|
||||
{KMemoryState_Transfered , "Transfered "},
|
||||
{KMemoryState_SharedTransfered , "SharedTransfered"},
|
||||
{KMemoryState_SharedCode , "SharedCode "},
|
||||
{KMemoryState_Inaccessible , "Inaccessible "},
|
||||
{KMemoryState_NonSecureIpc , "NonSecureIpc "},
|
||||
{KMemoryState_NonDeviceIpc , "NonDeviceIpc "},
|
||||
{KMemoryState_Kernel , "Kernel "},
|
||||
{KMemoryState_GeneratedCode , "GeneratedCode "},
|
||||
{KMemoryState_CodeOut , "CodeOut "},
|
||||
{KMemoryState_Coverage , "Coverage "},
|
||||
};
|
||||
|
||||
constexpr const char *GetMemoryStateName(KMemoryState state) {
|
||||
for (size_t i = 0; i < util::size(MemoryStateNames); i++) {
|
||||
if (std::get<0>(MemoryStateNames[i]) == state) {
|
||||
return std::get<1>(MemoryStateNames[i]);
|
||||
}
|
||||
}
|
||||
return "Unknown ";
|
||||
}
|
||||
|
||||
constexpr const char *GetMemoryPermissionString(const KMemoryBlock &block) {
|
||||
if (block.GetState() == KMemoryState_Free) {
|
||||
return " ";
|
||||
} else {
|
||||
switch (block.GetPermission()) {
|
||||
case KMemoryPermission_UserReadExecute:
|
||||
return "r-x";
|
||||
case KMemoryPermission_UserRead:
|
||||
return "r--";
|
||||
case KMemoryPermission_UserReadWrite:
|
||||
return "rw-";
|
||||
default:
|
||||
return "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DumpMemoryBlock(const KMemoryBlock &block) {
|
||||
const char *state = GetMemoryStateName(block.GetState());
|
||||
const char *perm = GetMemoryPermissionString(block);
|
||||
const uintptr_t start = GetInteger(block.GetAddress());
|
||||
const uintptr_t end = GetInteger(block.GetLastAddress());
|
||||
const size_t kb = block.GetSize() / 1_KB;
|
||||
|
||||
const char l = (block.GetAttribute() & KMemoryAttribute_Locked) ? 'L' : '-';
|
||||
const char i = (block.GetAttribute() & KMemoryAttribute_IpcLocked) ? 'I' : '-';
|
||||
const char d = (block.GetAttribute() & KMemoryAttribute_DeviceShared) ? 'D' : '-';
|
||||
const char u = (block.GetAttribute() & KMemoryAttribute_Uncached) ? 'U' : '-';
|
||||
|
||||
MESOSPHERE_LOG("0x%10lx - 0x%10lx (%9zu KB) %s %s %c%c%c%c [%d, %d]\n", start, end, kb, perm, state, l, i, d, u, block.GetIpcLockCount(), block.GetDeviceUseCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result KMemoryBlockManager::Initialize(KProcessAddress st, KProcessAddress nd, KMemoryBlockSlabManager *slab_manager) {
|
||||
/* Allocate a block to encapsulate the address space, insert it into the tree. */
|
||||
KMemoryBlock *start_block = slab_manager->Allocate();
|
||||
R_UNLESS(start_block != nullptr, svc::ResultOutOfResource());
|
||||
|
||||
/* Set our start and end. */
|
||||
m_start_address = st;
|
||||
m_end_address = nd;
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(m_start_address), PageSize));
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(m_end_address), PageSize));
|
||||
|
||||
/* Initialize and insert the block. */
|
||||
start_block->Initialize(m_start_address, (m_end_address - m_start_address) / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None);
|
||||
m_memory_block_tree.insert(*start_block);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager *slab_manager) {
|
||||
/* Erase every block until we have none left. */
|
||||
auto it = m_memory_block_tree.begin();
|
||||
while (it != m_memory_block_tree.end()) {
|
||||
KMemoryBlock *block = std::addressof(*it);
|
||||
it = m_memory_block_tree.erase(it);
|
||||
slab_manager->Free(block);
|
||||
}
|
||||
|
||||
MESOSPHERE_ASSERT(m_memory_block_tree.empty());
|
||||
}
|
||||
|
||||
bool KMemoryBlockManager::GetRegionForFindFreeArea(KProcessAddress *out_start, KProcessAddress *out_end, KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) {
|
||||
/* Check that there's room for the pages in the specified region. */
|
||||
if (num_pages + 2 * guard_pages > region_num_pages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Determine the aligned start of the guarded region. */
|
||||
const KProcessAddress guarded_start = region_start + guard_pages * PageSize;
|
||||
const KProcessAddress aligned_guarded_start = util::AlignDown(GetInteger(guarded_start), alignment);
|
||||
KProcessAddress aligned_guarded_start_with_offset = aligned_guarded_start + offset;
|
||||
if (guarded_start > aligned_guarded_start_with_offset) {
|
||||
if (!util::CanAddWithoutOverflow<uintptr_t>(GetInteger(aligned_guarded_start), alignment)) {
|
||||
return false;
|
||||
}
|
||||
aligned_guarded_start_with_offset += alignment;
|
||||
}
|
||||
|
||||
/* Determine the aligned end of the guarded region. */
|
||||
const KProcessAddress guarded_end = region_start + ((region_num_pages - (num_pages + guard_pages)) * PageSize);
|
||||
const KProcessAddress aligned_guarded_end = util::AlignDown(GetInteger(guarded_end), alignment);
|
||||
KProcessAddress aligned_guarded_end_with_offset = aligned_guarded_end + offset;
|
||||
if (aligned_guarded_end_with_offset > guarded_end) {
|
||||
if (aligned_guarded_end < alignment) {
|
||||
return false;
|
||||
}
|
||||
aligned_guarded_end_with_offset -= alignment;
|
||||
}
|
||||
|
||||
/* Check that the extents are valid. */
|
||||
if (aligned_guarded_end_with_offset < aligned_guarded_start_with_offset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the output extents. */
|
||||
*out_start = aligned_guarded_start_with_offset;
|
||||
*out_end = aligned_guarded_end_with_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
KProcessAddress KMemoryBlockManager::FindFreeArea(KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const {
|
||||
/* Determine the range to search in. */
|
||||
KProcessAddress search_start = Null<KProcessAddress>;
|
||||
KProcessAddress search_end = Null<KProcessAddress>;
|
||||
if (this->GetRegionForFindFreeArea(std::addressof(search_start), std::addressof(search_end), region_start, region_num_pages, num_pages, alignment, offset, guard_pages)) {
|
||||
/* Iterate over blocks in the search space, looking for a suitable one. */
|
||||
for (const_iterator it = this->FindIterator(search_start); it != m_memory_block_tree.cend(); it++) {
|
||||
/* If our block is past the end of our search space, we're done. */
|
||||
if (search_end < it->GetAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* We only want to consider free blocks. */
|
||||
if (it->GetState() != KMemoryState_Free) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Determine the candidate range. */
|
||||
KProcessAddress candidate_start = Null<KProcessAddress>;
|
||||
KProcessAddress candidate_end = Null<KProcessAddress>;
|
||||
if (!this->GetRegionForFindFreeArea(std::addressof(candidate_start), std::addressof(candidate_end), it->GetAddress(), it->GetNumPages(), num_pages, alignment, offset, guard_pages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try the suggested candidate (coercing into the search region if needed). */
|
||||
KProcessAddress candidate = candidate_start;
|
||||
if (candidate < search_start) {
|
||||
candidate = search_start;
|
||||
}
|
||||
|
||||
/* Check if the candidate is valid. */
|
||||
if (candidate <= search_end && candidate <= candidate_end) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Null<KProcessAddress>;
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::CoalesceForUpdate(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages) {
|
||||
/* Find the iterator now that we've updated. */
|
||||
iterator it = this->FindIterator(address);
|
||||
if (address != m_start_address) {
|
||||
it--;
|
||||
}
|
||||
|
||||
/* Coalesce blocks that we can. */
|
||||
while (true) {
|
||||
iterator prev = it++;
|
||||
if (it == m_memory_block_tree.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prev->CanMergeWith(*it)) {
|
||||
KMemoryBlock *block = std::addressof(*it);
|
||||
m_memory_block_tree.erase(it);
|
||||
prev->Add(*block);
|
||||
allocator->Free(block);
|
||||
it = prev;
|
||||
}
|
||||
|
||||
if (address + num_pages * PageSize < it->GetEndAddress()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::Update(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr) {
|
||||
/* Ensure for auditing that we never end up with an invalid tree. */
|
||||
KScopedMemoryBlockManagerAuditor auditor(this);
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize));
|
||||
MESOSPHERE_ASSERT((attr & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared)) == 0);
|
||||
|
||||
KProcessAddress cur_address = address;
|
||||
size_t remaining_pages = num_pages;
|
||||
iterator it = this->FindIterator(address);
|
||||
|
||||
while (remaining_pages > 0) {
|
||||
const size_t remaining_size = remaining_pages * PageSize;
|
||||
if (it->HasProperties(state, perm, attr)) {
|
||||
/* If we already have the right properties, just advance. */
|
||||
if (cur_address + remaining_size < it->GetEndAddress()) {
|
||||
remaining_pages = 0;
|
||||
cur_address += remaining_size;
|
||||
} else {
|
||||
remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize;
|
||||
cur_address = it->GetEndAddress();
|
||||
}
|
||||
} else {
|
||||
/* If we need to, create a new block before and insert it. */
|
||||
if (it->GetAddress() != GetInteger(cur_address)) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
it++;
|
||||
|
||||
cur_address = it->GetAddress();
|
||||
}
|
||||
|
||||
/* If we need to, create a new block after and insert it. */
|
||||
if (it->GetSize() > remaining_size) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address + remaining_size);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
}
|
||||
|
||||
/* Update block state. */
|
||||
it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr);
|
||||
cur_address += it->GetSize();
|
||||
remaining_pages -= it->GetNumPages();
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
this->CoalesceForUpdate(allocator, address, num_pages);
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr) {
|
||||
/* Ensure for auditing that we never end up with an invalid tree. */
|
||||
KScopedMemoryBlockManagerAuditor auditor(this);
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize));
|
||||
MESOSPHERE_ASSERT((attr & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared)) == 0);
|
||||
|
||||
KProcessAddress cur_address = address;
|
||||
size_t remaining_pages = num_pages;
|
||||
iterator it = this->FindIterator(address);
|
||||
|
||||
while (remaining_pages > 0) {
|
||||
const size_t remaining_size = remaining_pages * PageSize;
|
||||
if (it->HasProperties(test_state, test_perm, test_attr) && !it->HasProperties(state, perm, attr)) {
|
||||
/* If we need to, create a new block before and insert it. */
|
||||
if (it->GetAddress() != GetInteger(cur_address)) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
it++;
|
||||
|
||||
cur_address = it->GetAddress();
|
||||
}
|
||||
|
||||
/* If we need to, create a new block after and insert it. */
|
||||
if (it->GetSize() > remaining_size) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address + remaining_size);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
}
|
||||
|
||||
/* Update block state. */
|
||||
it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr);
|
||||
cur_address += it->GetSize();
|
||||
remaining_pages -= it->GetNumPages();
|
||||
} else {
|
||||
/* If we already have the right properties, just advance. */
|
||||
if (cur_address + remaining_size < it->GetEndAddress()) {
|
||||
remaining_pages = 0;
|
||||
cur_address += remaining_size;
|
||||
} else {
|
||||
remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize;
|
||||
cur_address = it->GetEndAddress();
|
||||
}
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
this->CoalesceForUpdate(allocator, address, num_pages);
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::UpdateLock(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, MemoryBlockLockFunction lock_func, KMemoryPermission perm) {
|
||||
/* Ensure for auditing that we never end up with an invalid tree. */
|
||||
KScopedMemoryBlockManagerAuditor auditor(this);
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize));
|
||||
|
||||
KProcessAddress cur_address = address;
|
||||
size_t remaining_pages = num_pages;
|
||||
iterator it = this->FindIterator(address);
|
||||
|
||||
const KProcessAddress end_address = address + (num_pages * PageSize);
|
||||
|
||||
while (remaining_pages > 0) {
|
||||
const size_t remaining_size = remaining_pages * PageSize;
|
||||
|
||||
/* If we need to, create a new block before and insert it. */
|
||||
if (it->GetAddress() != cur_address) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
it++;
|
||||
|
||||
cur_address = it->GetAddress();
|
||||
}
|
||||
|
||||
if (it->GetSize() > remaining_size) {
|
||||
/* If we need to, create a new block after and insert it. */
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address + remaining_size);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
}
|
||||
|
||||
/* Call the locked update function. */
|
||||
(std::addressof(*it)->*lock_func)(perm, it->GetAddress() == address, it->GetEndAddress() == end_address);
|
||||
cur_address += it->GetSize();
|
||||
remaining_pages -= it->GetNumPages();
|
||||
it++;
|
||||
}
|
||||
|
||||
this->CoalesceForUpdate(allocator, address, num_pages);
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::UpdateAttribute(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, u32 mask, u32 attr) {
|
||||
/* Ensure for auditing that we never end up with an invalid tree. */
|
||||
KScopedMemoryBlockManagerAuditor auditor(this);
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize));
|
||||
|
||||
KProcessAddress cur_address = address;
|
||||
size_t remaining_pages = num_pages;
|
||||
iterator it = this->FindIterator(address);
|
||||
|
||||
while (remaining_pages > 0) {
|
||||
const size_t remaining_size = remaining_pages * PageSize;
|
||||
|
||||
if ((it->GetAttribute() & mask) != attr) {
|
||||
/* If we need to, create a new block before and insert it. */
|
||||
if (it->GetAddress() != GetInteger(cur_address)) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
it++;
|
||||
|
||||
cur_address = it->GetAddress();
|
||||
}
|
||||
|
||||
/* If we need to, create a new block after and insert it. */
|
||||
if (it->GetSize() > remaining_size) {
|
||||
KMemoryBlock *new_block = allocator->Allocate();
|
||||
|
||||
it->Split(new_block, cur_address + remaining_size);
|
||||
it = m_memory_block_tree.insert(*new_block);
|
||||
}
|
||||
|
||||
/* Update block state. */
|
||||
it->UpdateAttribute(mask, attr);
|
||||
cur_address += it->GetSize();
|
||||
remaining_pages -= it->GetNumPages();
|
||||
} else {
|
||||
/* If we already have the right attributes, just advance. */
|
||||
if (cur_address + remaining_size < it->GetEndAddress()) {
|
||||
remaining_pages = 0;
|
||||
cur_address += remaining_size;
|
||||
} else {
|
||||
remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize;
|
||||
cur_address = it->GetEndAddress();
|
||||
}
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
this->CoalesceForUpdate(allocator, address, num_pages);
|
||||
}
|
||||
|
||||
/* Debug. */
|
||||
bool KMemoryBlockManager::CheckState() const {
|
||||
/* If we fail, we should dump blocks. */
|
||||
auto dump_guard = SCOPE_GUARD { this->DumpBlocks(); };
|
||||
|
||||
/* Loop over every block, ensuring that we are sorted and coalesced. */
|
||||
auto it = m_memory_block_tree.cbegin();
|
||||
auto prev = it++;
|
||||
while (it != m_memory_block_tree.cend()) {
|
||||
|
||||
/* Sequential blocks which can be merged should be merged. */
|
||||
if (prev->CanMergeWith(*it)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Sequential blocks should be sequential. */
|
||||
if (prev->GetEndAddress() != it->GetAddress()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the block is ipc locked, it must have a count. */
|
||||
if ((it->GetAttribute() & KMemoryAttribute_IpcLocked) != 0 && it->GetIpcLockCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the block is device shared, it must have a count. */
|
||||
if ((it->GetAttribute() & KMemoryAttribute_DeviceShared) != 0 && it->GetDeviceUseCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Advance the iterator. */
|
||||
prev = it++;
|
||||
}
|
||||
|
||||
/* Our loop will miss checking the last block, potentially, so check it. */
|
||||
if (prev != m_memory_block_tree.cend()) {
|
||||
/* If the block is ipc locked, it must have a count. */
|
||||
if ((prev->GetAttribute() & KMemoryAttribute_IpcLocked) != 0 && prev->GetIpcLockCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the block is device shared, it must have a count. */
|
||||
if ((prev->GetAttribute() & KMemoryAttribute_DeviceShared) != 0 && prev->GetDeviceUseCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* We're valid, so no need to print. */
|
||||
dump_guard.Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
void KMemoryBlockManager::DumpBlocks() const {
|
||||
/* Dump each block. */
|
||||
for (const auto &block : m_memory_block_tree) {
|
||||
DumpMemoryBlock(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t ReservedEarlyDramSize = 0x60000;
|
||||
|
||||
constexpr size_t CarveoutAlignment = 0x20000;
|
||||
constexpr size_t CarveoutSizeMax = 512_MB - CarveoutAlignment;
|
||||
|
||||
template<typename... T> requires (std::same_as<T, KMemoryRegionAttr> && ...)
|
||||
constexpr ALWAYS_INLINE KMemoryRegionType GetMemoryRegionType(KMemoryRegionType base, T... attr) {
|
||||
return util::FromUnderlying<KMemoryRegionType>(util::ToUnderlying(base) | (util::ToUnderlying<T>(attr) | ...));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool SetupUartPhysicalMemoryRegion() {
|
||||
#if defined(MESOSPHERE_DEBUG_LOG_USE_UART)
|
||||
switch (KSystemControl::Init::GetDebugLogUartPort()) {
|
||||
case 0: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006000, 0x40, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap));
|
||||
case 1: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006040, 0x40, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap));
|
||||
case 2: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006200, 0x100, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap));
|
||||
case 3: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006300, 0x100, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap));
|
||||
default: return false;
|
||||
}
|
||||
#elif defined(MESOSPHERE_DEBUG_LOG_USE_IRAM_RINGBUFFER)
|
||||
return true;
|
||||
#else
|
||||
#error "Unknown Debug UART device!"
|
||||
#endif
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool SetupPowerManagementControllerMemoryRegion() {
|
||||
/* For backwards compatibility, the PMC must remain mappable on < 2.0.0. */
|
||||
const KMemoryRegionAttr rtc_restrict_attr = GetTargetFirmware() >= TargetFirmware_2_0_0 ? KMemoryRegionAttr_NoUserMap : static_cast<KMemoryRegionAttr>(0);
|
||||
const KMemoryRegionAttr pmc_restrict_attr = GetTargetFirmware() >= TargetFirmware_2_0_0 ? KMemoryRegionAttr_NoUserMap : KMemoryRegionAttr_ShouldKernelMap;
|
||||
|
||||
return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7000E000, 0x400, GetMemoryRegionType(KMemoryRegionType_None, rtc_restrict_attr)) &&
|
||||
KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7000E400, 0xC00, GetMemoryRegionType(KMemoryRegionType_PowerManagementController, pmc_restrict_attr));
|
||||
}
|
||||
|
||||
void InsertPoolPartitionRegionIntoBothTrees(size_t start, size_t size, KMemoryRegionType phys_type, KMemoryRegionType virt_type, u32 &cur_attr) {
|
||||
const u32 attr = cur_attr++;
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(start, size, phys_type, attr));
|
||||
const KMemoryRegion *phys = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(phys_type, attr);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(phys != nullptr);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(phys->GetEndAddress() != 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(phys->GetPairAddress(), size, virt_type, attr));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace init {
|
||||
|
||||
void SetupDevicePhysicalMemoryRegions() {
|
||||
/* TODO: Give these constexpr defines somewhere? */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(SetupUartPhysicalMemoryRegion());
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(SetupPowerManagementControllerMemoryRegion());
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70019000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController, KMemoryRegionAttr_NoUserMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7001C000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController0, KMemoryRegionAttr_NoUserMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7001D000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController1, KMemoryRegionAttr_NoUserMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50040000, 0x1000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50041000, 0x1000, GetMemoryRegionType(KMemoryRegionType_InterruptDistributor, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50042000, 0x1000, GetMemoryRegionType(KMemoryRegionType_InterruptCpuInterface, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50043000, 0x1D000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap)));
|
||||
|
||||
/* Map IRAM unconditionally, to support debug-logging-to-iram build config. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x40000000, 0x40000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsIram, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
|
||||
if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
|
||||
/* Prevent mapping the bpmp exception vectors or the ipatch region. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6000F000, 0x1000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6001DC00, 0x400, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap)));
|
||||
} else {
|
||||
/* Map devices required for legacy lps driver. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6000F000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsExceptionVectors, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60007000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsFlowController, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60004000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsPrimaryICtlr, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60001000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsSemaphore, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70016000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsAtomics, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60006000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsClkRst, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
}
|
||||
}
|
||||
|
||||
void SetupDramPhysicalMemoryRegions() {
|
||||
const size_t intended_memory_size = KSystemControl::Init::GetIntendedMemorySize();
|
||||
const KPhysicalAddress physical_memory_base_address = KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress);
|
||||
|
||||
/* Insert blocks into the tree. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), intended_memory_size, KMemoryRegionType_Dram));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), ReservedEarlyDramSize, KMemoryRegionType_DramReservedEarly));
|
||||
}
|
||||
|
||||
void SetupPoolPartitionMemoryRegions() {
|
||||
/* Start by identifying the extents of the DRAM memory region. */
|
||||
const auto dram_extents = KMemoryLayout::GetMainMemoryPhysicalExtents();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(dram_extents.GetEndAddress() != 0);
|
||||
|
||||
/* Find the pool partitions region. */
|
||||
const KMemoryRegion *pool_partitions_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(KMemoryRegionType_DramPoolPartition, 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(pool_partitions_region != nullptr);
|
||||
|
||||
const uintptr_t pool_partitions_start = pool_partitions_region->GetAddress();
|
||||
|
||||
/* Determine the end of the pool region. */
|
||||
const uintptr_t pool_end = pool_partitions_region->GetEndAddress();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(pool_end == dram_extents.GetEndAddress());
|
||||
|
||||
/* Find the start of the kernel DRAM region. */
|
||||
const KMemoryRegion *kernel_dram_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramKernelBase);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(kernel_dram_region != nullptr);
|
||||
|
||||
const uintptr_t kernel_dram_start = kernel_dram_region->GetAddress();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(kernel_dram_start, CarveoutAlignment));
|
||||
|
||||
/* Setup the pool partition layouts. */
|
||||
if (GetTargetFirmware() >= TargetFirmware_5_0_0) {
|
||||
/* On 5.0.0+, setup modern 4-pool-partition layout. */
|
||||
|
||||
/* Get Application and Applet pool sizes. */
|
||||
const size_t application_pool_size = KSystemControl::Init::GetApplicationPoolSize();
|
||||
const size_t applet_pool_size = KSystemControl::Init::GetAppletPoolSize();
|
||||
const size_t unsafe_system_pool_min_size = KSystemControl::Init::GetMinimumNonSecureSystemPoolSize();
|
||||
|
||||
/* Decide on starting addresses for our pools. */
|
||||
const uintptr_t application_pool_start = pool_end - application_pool_size;
|
||||
const uintptr_t applet_pool_start = application_pool_start - applet_pool_size;
|
||||
const uintptr_t unsafe_system_pool_start = std::min(kernel_dram_start + CarveoutSizeMax, util::AlignDown(applet_pool_start - unsafe_system_pool_min_size, CarveoutAlignment));
|
||||
const size_t unsafe_system_pool_size = applet_pool_start - unsafe_system_pool_start;
|
||||
|
||||
/* We want to arrange application pool depending on where the middle of dram is. */
|
||||
const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2;
|
||||
u32 cur_pool_attr = 0;
|
||||
size_t total_overhead_size = 0;
|
||||
if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) {
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size);
|
||||
} else {
|
||||
const size_t first_application_pool_size = dram_midpoint - application_pool_start;
|
||||
const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint;
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size);
|
||||
}
|
||||
|
||||
/* Insert the applet pool. */
|
||||
InsertPoolPartitionRegionIntoBothTrees(applet_pool_start, applet_pool_size, KMemoryRegionType_DramAppletPool, KMemoryRegionType_VirtualDramAppletPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(applet_pool_size);
|
||||
|
||||
/* Insert the nonsecure system pool. */
|
||||
InsertPoolPartitionRegionIntoBothTrees(unsafe_system_pool_start, unsafe_system_pool_size, KMemoryRegionType_DramSystemNonSecurePool, KMemoryRegionType_VirtualDramSystemNonSecurePool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(unsafe_system_pool_size);
|
||||
|
||||
/* Determine final total overhead size. */
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize((unsafe_system_pool_start - pool_partitions_start) - total_overhead_size);
|
||||
|
||||
/* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the four UserPool regions are contiguous. */
|
||||
|
||||
/* Insert the system pool. */
|
||||
const uintptr_t system_pool_start = pool_partitions_start + total_overhead_size;
|
||||
const size_t system_pool_size = unsafe_system_pool_start - system_pool_start;
|
||||
InsertPoolPartitionRegionIntoBothTrees(system_pool_start, system_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr);
|
||||
|
||||
/* Insert the pool management region. */
|
||||
const uintptr_t pool_management_start = pool_partitions_start;
|
||||
const size_t pool_management_size = total_overhead_size;
|
||||
u32 pool_management_attr = 0;
|
||||
InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr);
|
||||
} else {
|
||||
/* On < 5.0.0, setup a legacy 2-pool layout for backwards compatibility. */
|
||||
|
||||
static_assert(KMemoryManager::Pool_Count == 4);
|
||||
static_assert(KMemoryManager::Pool_Unsafe == KMemoryManager::Pool_Application);
|
||||
static_assert(KMemoryManager::Pool_Secure == KMemoryManager::Pool_System);
|
||||
|
||||
/* Get Secure pool size. */
|
||||
const size_t secure_pool_size = [](auto target_firmware) ALWAYS_INLINE_LAMBDA -> size_t {
|
||||
constexpr size_t LegacySecureKernelSize = 8_MB; /* KPageBuffer pages, other small kernel allocations. */
|
||||
constexpr size_t LegacySecureMiscSize = 1_MB; /* Miscellaneous pages for secure process mapping. */
|
||||
constexpr size_t LegacySecureHeapSize = 24_MB; /* Heap pages for secure process mapping (fs). */
|
||||
constexpr size_t LegacySecureEsSize = 1_MB + 232_KB; /* Size for additional secure process (es, 4.0.0+). */
|
||||
|
||||
/* The baseline size for the secure region is enough to cover any allocations the kernel might make. */
|
||||
size_t size = LegacySecureKernelSize;
|
||||
|
||||
/* If on 2.0.0+, initial processes will fall within the secure region. */
|
||||
if (target_firmware >= TargetFirmware_2_0_0) {
|
||||
/* Account for memory used directly for the processes. */
|
||||
size += GetInitialProcessesSecureMemorySize();
|
||||
|
||||
/* Account for heap and transient memory used by the processes. */
|
||||
size += LegacySecureHeapSize + LegacySecureMiscSize;
|
||||
}
|
||||
|
||||
/* If on 4.0.0+, any process may use secure memory via a create process flag. */
|
||||
/* In process this is used for es alone, and the secure pool's size should be */
|
||||
/* increased to accommodate es's binary. */
|
||||
if (target_firmware >= TargetFirmware_4_0_0) {
|
||||
size += LegacySecureEsSize;
|
||||
}
|
||||
|
||||
return size;
|
||||
}(GetTargetFirmware());
|
||||
|
||||
/* Calculate the overhead for the secure and (defunct) applet/non-secure-system pools. */
|
||||
size_t total_overhead_size = KMemoryManager::CalculateManagementOverheadSize(secure_pool_size);
|
||||
|
||||
/* Calculate the overhead for (an amount larger than) the unsafe pool. */
|
||||
const size_t approximate_total_overhead_size = total_overhead_size + KMemoryManager::CalculateManagementOverheadSize((pool_end - pool_partitions_start) - secure_pool_size - total_overhead_size) + 2 * PageSize;
|
||||
|
||||
/* Determine the start of the unsafe region. */
|
||||
const uintptr_t unsafe_memory_start = util::AlignUp(pool_partitions_start + secure_pool_size + approximate_total_overhead_size, CarveoutAlignment);
|
||||
|
||||
/* Determine the start of the pool regions. */
|
||||
const uintptr_t application_pool_start = unsafe_memory_start;
|
||||
|
||||
/* Determine the pool sizes. */
|
||||
const size_t application_pool_size = pool_end - application_pool_start;
|
||||
|
||||
/* We want to arrange application pool depending on where the middle of dram is. */
|
||||
const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2;
|
||||
u32 cur_pool_attr = 0;
|
||||
if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) {
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size);
|
||||
} else {
|
||||
const size_t first_application_pool_size = dram_midpoint - application_pool_start;
|
||||
const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint;
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size);
|
||||
}
|
||||
|
||||
/* Validate the true overhead size. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(total_overhead_size <= approximate_total_overhead_size);
|
||||
|
||||
/* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the UserPool regions are contiguous. */
|
||||
|
||||
/* Insert the secure pool. */
|
||||
const uintptr_t secure_pool_start = unsafe_memory_start - secure_pool_size;
|
||||
InsertPoolPartitionRegionIntoBothTrees(secure_pool_start, secure_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr);
|
||||
|
||||
/* Insert the pool management region. */
|
||||
const uintptr_t pool_management_start = pool_partitions_start;
|
||||
const size_t pool_management_size = secure_pool_start - pool_management_start;
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(total_overhead_size <= pool_management_size);
|
||||
|
||||
u32 pool_management_attr = 0;
|
||||
InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t ReservedEarlyDramSize = 0x00080000;
|
||||
|
||||
template<typename... T> requires (std::same_as<T, KMemoryRegionAttr> && ...)
|
||||
constexpr ALWAYS_INLINE KMemoryRegionType GetMemoryRegionType(KMemoryRegionType base, T... attr) {
|
||||
return util::FromUnderlying<KMemoryRegionType>(util::ToUnderlying(base) | (util::ToUnderlying<T>(attr) | ...));
|
||||
}
|
||||
|
||||
void InsertPoolPartitionRegionIntoBothTrees(size_t start, size_t size, KMemoryRegionType phys_type, KMemoryRegionType virt_type, u32 &cur_attr) {
|
||||
const u32 attr = cur_attr++;
|
||||
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(start, size, phys_type, attr));
|
||||
const KMemoryRegion *phys = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(phys_type, attr);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(phys != nullptr);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(phys->GetEndAddress() != 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(phys->GetPairAddress(), size, virt_type, attr));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace init {
|
||||
|
||||
void SetupDevicePhysicalMemoryRegions() {
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x08000000, 0x10000, GetMemoryRegionType(KMemoryRegionType_InterruptDistributor, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x08010000, 0x10000, GetMemoryRegionType(KMemoryRegionType_InterruptCpuInterface, KMemoryRegionAttr_ShouldKernelMap)));
|
||||
}
|
||||
|
||||
void SetupDramPhysicalMemoryRegions() {
|
||||
const size_t intended_memory_size = KSystemControl::Init::GetIntendedMemorySize();
|
||||
const KPhysicalAddress physical_memory_base_address = KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress);
|
||||
|
||||
/* Insert blocks into the tree. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), intended_memory_size, KMemoryRegionType_Dram));
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), ReservedEarlyDramSize, KMemoryRegionType_DramReservedEarly));
|
||||
}
|
||||
|
||||
void SetupPoolPartitionMemoryRegions() {
|
||||
/* Start by identifying the extents of the DRAM memory region. */
|
||||
const auto dram_extents = KMemoryLayout::GetMainMemoryPhysicalExtents();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(dram_extents.GetEndAddress() != 0);
|
||||
|
||||
/* Find the pool partitions region. */
|
||||
const KMemoryRegion *pool_partitions_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(KMemoryRegionType_DramPoolPartition, 0);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(pool_partitions_region != nullptr);
|
||||
|
||||
const uintptr_t pool_partitions_start = pool_partitions_region->GetAddress();
|
||||
|
||||
/* Determine the end of the pool region. */
|
||||
const uintptr_t pool_end = pool_partitions_region->GetEndAddress();
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(pool_end == dram_extents.GetEndAddress());
|
||||
|
||||
/* Find the start of the kernel DRAM region. */
|
||||
const KMemoryRegion *kernel_dram_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramKernelBase);
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(kernel_dram_region != nullptr);
|
||||
|
||||
/* Setup the pool partition layouts. */
|
||||
/* Get Application and Applet pool sizes. */
|
||||
const size_t application_pool_size = KSystemControl::Init::GetApplicationPoolSize();
|
||||
const size_t applet_pool_size = KSystemControl::Init::GetAppletPoolSize();
|
||||
const size_t unsafe_system_pool_min_size = KSystemControl::Init::GetMinimumNonSecureSystemPoolSize();
|
||||
|
||||
/* Decide on starting addresses for our pools. */
|
||||
const uintptr_t application_pool_start = pool_end - application_pool_size;
|
||||
const uintptr_t applet_pool_start = application_pool_start - applet_pool_size;
|
||||
const uintptr_t unsafe_system_pool_start = util::AlignDown(applet_pool_start - unsafe_system_pool_min_size, PageSize);
|
||||
const size_t unsafe_system_pool_size = applet_pool_start - unsafe_system_pool_start;
|
||||
|
||||
/* We want to arrange application pool depending on where the middle of dram is. */
|
||||
const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2;
|
||||
u32 cur_pool_attr = 0;
|
||||
size_t total_overhead_size = 0;
|
||||
|
||||
/* Insert the application pool. */
|
||||
if (application_pool_size > 0) {
|
||||
if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) {
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size);
|
||||
} else {
|
||||
const size_t first_application_pool_size = dram_midpoint - application_pool_start;
|
||||
const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint;
|
||||
InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert the applet pool. */
|
||||
if (applet_pool_size > 0) {
|
||||
InsertPoolPartitionRegionIntoBothTrees(applet_pool_start, applet_pool_size, KMemoryRegionType_DramAppletPool, KMemoryRegionType_VirtualDramAppletPool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(applet_pool_size);
|
||||
}
|
||||
|
||||
/* Insert the nonsecure system pool. */
|
||||
if (unsafe_system_pool_size > 0) {
|
||||
InsertPoolPartitionRegionIntoBothTrees(unsafe_system_pool_start, unsafe_system_pool_size, KMemoryRegionType_DramSystemNonSecurePool, KMemoryRegionType_VirtualDramSystemNonSecurePool, cur_pool_attr);
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(unsafe_system_pool_size);
|
||||
}
|
||||
|
||||
/* Determine final total overhead size. */
|
||||
total_overhead_size += KMemoryManager::CalculateManagementOverheadSize((unsafe_system_pool_start - pool_partitions_start) - total_overhead_size);
|
||||
|
||||
/* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the four UserPool regions are contiguous. */
|
||||
|
||||
/* Insert the system pool. */
|
||||
const uintptr_t system_pool_start = pool_partitions_start + total_overhead_size;
|
||||
const size_t system_pool_size = unsafe_system_pool_start - system_pool_start;
|
||||
InsertPoolPartitionRegionIntoBothTrees(system_pool_start, system_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr);
|
||||
|
||||
/* Insert the pool management region. */
|
||||
const uintptr_t pool_management_start = pool_partitions_start;
|
||||
const size_t pool_management_size = total_overhead_size;
|
||||
u32 pool_management_attr = 0;
|
||||
InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
class KMemoryRegionAllocator {
|
||||
NON_COPYABLE(KMemoryRegionAllocator);
|
||||
NON_MOVEABLE(KMemoryRegionAllocator);
|
||||
public:
|
||||
static constexpr size_t MaxMemoryRegions = 200;
|
||||
private:
|
||||
KMemoryRegion m_region_heap[MaxMemoryRegions];
|
||||
size_t m_num_regions;
|
||||
public:
|
||||
constexpr ALWAYS_INLINE KMemoryRegionAllocator() : m_region_heap(), m_num_regions() { /* ... */ }
|
||||
public:
|
||||
template<typename... Args>
|
||||
ALWAYS_INLINE KMemoryRegion *Allocate(Args&&... args) {
|
||||
/* Ensure we stay within the bounds of our heap. */
|
||||
MESOSPHERE_INIT_ABORT_UNLESS(m_num_regions < MaxMemoryRegions);
|
||||
|
||||
/* Create the new region. */
|
||||
KMemoryRegion *region = std::addressof(m_region_heap[m_num_regions++]);
|
||||
std::construct_at(region, std::forward<Args>(args)...);
|
||||
|
||||
return region;
|
||||
}
|
||||
};
|
||||
|
||||
constinit KMemoryRegionAllocator g_memory_region_allocator;
|
||||
|
||||
template<typename... Args>
|
||||
ALWAYS_INLINE KMemoryRegion *AllocateRegion(Args&&... args) {
|
||||
return g_memory_region_allocator.Allocate(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void KMemoryRegionTree::InsertDirectly(uintptr_t address, uintptr_t last_address, u32 attr, u32 type_id) {
|
||||
this->insert(*AllocateRegion(address, last_address, attr, type_id));
|
||||
}
|
||||
|
||||
bool KMemoryRegionTree::Insert(uintptr_t address, size_t size, u32 type_id, u32 new_attr, u32 old_attr) {
|
||||
/* Locate the memory region that contains the address. */
|
||||
KMemoryRegion *found = this->FindModifiable(address);
|
||||
|
||||
/* We require that the old attr is correct. */
|
||||
if (found->GetAttributes() != old_attr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We further require that the region can be split from the old region. */
|
||||
const uintptr_t inserted_region_end = address + size;
|
||||
const uintptr_t inserted_region_last = inserted_region_end - 1;
|
||||
if (found->GetLastAddress() < inserted_region_last) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Further, we require that the type id is a valid transformation. */
|
||||
if (!found->CanDerive(type_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Cache information from the region before we remove it. */
|
||||
const uintptr_t old_address = found->GetAddress();
|
||||
const uintptr_t old_last = found->GetLastAddress();
|
||||
const uintptr_t old_pair = found->GetPairAddress();
|
||||
const u32 old_type = found->GetType();
|
||||
|
||||
/* Erase the existing region from the tree. */
|
||||
this->erase(this->iterator_to(*found));
|
||||
|
||||
/* Insert the new region into the tree. */
|
||||
if (old_address == address) {
|
||||
/* Reuse the old object for the new region, if we can. */
|
||||
found->Reset(address, inserted_region_last, old_pair, new_attr, type_id);
|
||||
this->insert(*found);
|
||||
} else {
|
||||
/* If we can't re-use, adjust the old region. */
|
||||
found->Reset(old_address, address - 1, old_pair, old_attr, old_type);
|
||||
this->insert(*found);
|
||||
|
||||
/* Insert a new region for the split. */
|
||||
const uintptr_t new_pair = (old_pair != std::numeric_limits<uintptr_t>::max()) ? old_pair + (address - old_address) : old_pair;
|
||||
this->insert(*AllocateRegion(address, inserted_region_last, new_pair, new_attr, type_id));
|
||||
}
|
||||
|
||||
/* If we need to insert a region after the region, do so. */
|
||||
if (old_last != inserted_region_last) {
|
||||
const uintptr_t after_pair = (old_pair != std::numeric_limits<uintptr_t>::max()) ? old_pair + (inserted_region_end - old_address) : old_pair;
|
||||
this->insert(*AllocateRegion(inserted_region_end, old_last, after_pair, old_attr, old_type));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KMemoryLayout::InitializeLinearMemoryRegionTrees() {
|
||||
/* Initialize linear trees. */
|
||||
for (auto ®ion : GetPhysicalMemoryRegionTree()) {
|
||||
if (region.HasTypeAttribute(KMemoryRegionAttr_LinearMapped)) {
|
||||
GetPhysicalLinearMemoryRegionTree().InsertDirectly(region.GetAddress(), region.GetLastAddress(), region.GetAttributes(), region.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto ®ion : GetVirtualMemoryRegionTree()) {
|
||||
if (region.IsDerivedFrom(KMemoryRegionType_Dram)) {
|
||||
GetVirtualLinearMemoryRegionTree().InsertDirectly(region.GetAddress(), region.GetLastAddress(), region.GetAttributes(), region.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t KMemoryLayout::GetResourceRegionSizeForInit(bool use_extra_resource) {
|
||||
return KernelResourceSize + KSystemControl::SecureAppletMemorySize + (use_extra_resource ? KernelSlabHeapAdditionalSize + KernelPageBufferAdditionalSize : 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) {
|
||||
if ((type | KMemoryRegionType_DramApplicationPool) == type) {
|
||||
return KMemoryManager::Pool_Application;
|
||||
} else if ((type | KMemoryRegionType_DramAppletPool) == type) {
|
||||
return KMemoryManager::Pool_Applet;
|
||||
} else if ((type | KMemoryRegionType_DramSystemPool) == type) {
|
||||
return KMemoryManager::Pool_System;
|
||||
} else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) {
|
||||
return KMemoryManager::Pool_SystemNonSecure;
|
||||
} else {
|
||||
MESOSPHERE_PANIC("InvalidMemoryRegionType for conversion to Pool");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KMemoryManager::Initialize(KVirtualAddress management_region, size_t management_region_size, const u32 *min_align_shifts) {
|
||||
/* Clear the management region to zero. */
|
||||
const KVirtualAddress management_region_end = management_region + management_region_size;
|
||||
std::memset(GetVoidPointer(management_region), 0, management_region_size);
|
||||
|
||||
/* Reset our manager count. */
|
||||
m_num_managers = 0;
|
||||
|
||||
/* Traverse the virtual memory layout tree, initializing each manager as appropriate. */
|
||||
while (m_num_managers != MaxManagerCount) {
|
||||
/* Locate the region that should initialize the current manager. */
|
||||
KPhysicalAddress region_address = Null<KPhysicalAddress>;
|
||||
size_t region_size = 0;
|
||||
Pool region_pool = Pool_Count;
|
||||
for (const auto &it : KMemoryLayout::GetPhysicalMemoryRegionTree()) {
|
||||
/* We only care about regions that we need to create managers for. */
|
||||
if (!it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We want to initialize the managers in order. */
|
||||
if (it.GetAttributes() != m_num_managers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const KPhysicalAddress cur_start = it.GetAddress();
|
||||
const KPhysicalAddress cur_end = it.GetEndAddress();
|
||||
|
||||
/* Validate the region. */
|
||||
MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>);
|
||||
MESOSPHERE_ASSERT(cur_start != Null<KPhysicalAddress>);
|
||||
MESOSPHERE_ASSERT(it.GetSize() > 0);
|
||||
|
||||
/* Update the region's extents. */
|
||||
if (region_address == Null<KPhysicalAddress>) {
|
||||
region_address = cur_start;
|
||||
region_size = it.GetSize();
|
||||
region_pool = GetPoolFromMemoryRegionType(it.GetType());
|
||||
} else {
|
||||
MESOSPHERE_ASSERT(cur_start == region_address + region_size);
|
||||
|
||||
/* Update the size. */
|
||||
region_size = cur_end - region_address;
|
||||
MESOSPHERE_ABORT_UNLESS(GetPoolFromMemoryRegionType(it.GetType()) == region_pool);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we didn't find a region, we're done. */
|
||||
if (region_size == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Initialize a new manager for the region. */
|
||||
Impl *manager = std::addressof(m_managers[m_num_managers++]);
|
||||
MESOSPHERE_ABORT_UNLESS(m_num_managers <= util::size(m_managers));
|
||||
|
||||
const size_t cur_size = manager->Initialize(region_address, region_size, management_region, management_region_end, region_pool);
|
||||
management_region += cur_size;
|
||||
MESOSPHERE_ABORT_UNLESS(management_region <= management_region_end);
|
||||
|
||||
/* Insert the manager into the pool list. */
|
||||
if (m_pool_managers_tail[region_pool] == nullptr) {
|
||||
m_pool_managers_head[region_pool] = manager;
|
||||
} else {
|
||||
m_pool_managers_tail[region_pool]->SetNext(manager);
|
||||
manager->SetPrev(m_pool_managers_tail[region_pool]);
|
||||
}
|
||||
m_pool_managers_tail[region_pool] = manager;
|
||||
}
|
||||
|
||||
/* Free each region to its corresponding heap. */
|
||||
size_t reserved_sizes[MaxManagerCount] = {};
|
||||
const KPhysicalAddress ini_start = GetInitialProcessBinaryPhysicalAddress();
|
||||
const size_t ini_size = GetInitialProcessBinarySize();
|
||||
const KPhysicalAddress ini_end = ini_start + ini_size;
|
||||
const KPhysicalAddress ini_last = ini_end - 1;
|
||||
for (const auto &it : KMemoryLayout::GetPhysicalMemoryRegionTree()) {
|
||||
if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
|
||||
/* Get the manager for the region. */
|
||||
auto &manager = m_managers[it.GetAttributes()];
|
||||
|
||||
const KPhysicalAddress cur_start = it.GetAddress();
|
||||
const KPhysicalAddress cur_last = it.GetLastAddress();
|
||||
const KPhysicalAddress cur_end = it.GetEndAddress();
|
||||
|
||||
if (cur_start <= ini_start && ini_last <= cur_last) {
|
||||
/* Free memory before the ini to the heap. */
|
||||
if (cur_start != ini_start) {
|
||||
manager.Free(cur_start, (ini_start - cur_start) / PageSize);
|
||||
}
|
||||
|
||||
/* Open/reserve the ini memory. */
|
||||
manager.OpenFirst(ini_start, ini_size / PageSize);
|
||||
reserved_sizes[it.GetAttributes()] += ini_size;
|
||||
|
||||
/* Free memory after the ini to the heap. */
|
||||
if (ini_last != cur_last) {
|
||||
MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>);
|
||||
manager.Free(ini_end, (cur_end - ini_end) / PageSize);
|
||||
}
|
||||
} else {
|
||||
/* Ensure there's no partial overlap with the ini image. */
|
||||
if (cur_start <= ini_last) {
|
||||
MESOSPHERE_ABORT_UNLESS(cur_last < ini_start);
|
||||
} else {
|
||||
/* Otherwise, check the region for general validity. */
|
||||
MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>);
|
||||
}
|
||||
|
||||
/* Free the memory to the heap. */
|
||||
manager.Free(cur_start, it.GetSize() / PageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the used size for all managers. */
|
||||
for (size_t i = 0; i < m_num_managers; ++i) {
|
||||
m_managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
|
||||
}
|
||||
|
||||
/* Determine the min heap size for all pools. */
|
||||
for (size_t i = 0; i < Pool_Count; ++i) {
|
||||
/* Determine the min alignment for the pool in pages. */
|
||||
const size_t min_align_pages = 1 << min_align_shifts[i];
|
||||
|
||||
/* Determine a heap index. */
|
||||
if (const auto heap_index = KPageHeap::GetAlignedBlockIndex(min_align_pages, min_align_pages); heap_index >= 0) {
|
||||
m_min_heap_indexes[i] = heap_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
|
||||
/* Lock the pool. */
|
||||
KScopedLightLock lk(m_pool_locks[pool]);
|
||||
|
||||
/* Check that we don't already have an optimized process. */
|
||||
R_UNLESS(!m_has_optimized_process[pool], svc::ResultBusy());
|
||||
|
||||
/* Set the optimized process id. */
|
||||
m_optimized_process_ids[pool] = process_id;
|
||||
m_has_optimized_process[pool] = true;
|
||||
|
||||
/* Clear the management area for the optimized process. */
|
||||
for (auto *manager = this->GetFirstManager(pool, Direction_FromFront); manager != nullptr; manager = this->GetNextManager(manager, Direction_FromFront)) {
|
||||
manager->InitializeOptimizedMemory();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
|
||||
/* Lock the pool. */
|
||||
KScopedLightLock lk(m_pool_locks[pool]);
|
||||
|
||||
/* If the process was optimized, clear it. */
|
||||
if (m_has_optimized_process[pool] && m_optimized_process_ids[pool] == process_id) {
|
||||
m_has_optimized_process[pool] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option) {
|
||||
/* Early return if we're allocating no pages. */
|
||||
if (num_pages == 0) {
|
||||
return Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
/* Determine the pool and direction we're allocating from. */
|
||||
const auto [pool, dir] = DecodeOption(option);
|
||||
|
||||
/* Check that we're allocating a correctly aligned number of pages. */
|
||||
const size_t min_align_pages = KPageHeap::GetBlockNumPages(m_min_heap_indexes[pool]);
|
||||
if (!util::IsAligned(num_pages, min_align_pages)) {
|
||||
return Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
/* Update our alignment. */
|
||||
align_pages = std::max(align_pages, min_align_pages);
|
||||
|
||||
/* Lock the pool that we're allocating from. */
|
||||
KScopedLightLock lk(m_pool_locks[pool]);
|
||||
|
||||
/* Choose a heap based on our page size request. */
|
||||
const s32 heap_index = KPageHeap::GetAlignedBlockIndex(num_pages, align_pages);
|
||||
|
||||
/* Loop, trying to iterate from each block. */
|
||||
Impl *chosen_manager = nullptr;
|
||||
KPhysicalAddress allocated_block = Null<KPhysicalAddress>;
|
||||
for (chosen_manager = this->GetFirstManager(pool, dir); chosen_manager != nullptr; chosen_manager = this->GetNextManager(chosen_manager, dir)) {
|
||||
allocated_block = chosen_manager->AllocateAligned(heap_index, num_pages, align_pages);
|
||||
if (allocated_block != Null<KPhysicalAddress>) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we failed to allocate, quit now. */
|
||||
if (allocated_block == Null<KPhysicalAddress>) {
|
||||
return Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
/* Maintain the optimized memory bitmap, if we should. */
|
||||
if (m_has_optimized_process[pool]) {
|
||||
chosen_manager->TrackUnoptimizedAllocation(allocated_block, num_pages);
|
||||
}
|
||||
|
||||
/* Open the first reference to the pages. */
|
||||
chosen_manager->OpenFirst(allocated_block, num_pages);
|
||||
|
||||
return allocated_block;
|
||||
}
|
||||
|
||||
Result KMemoryManager::AllocatePageGroupImpl(KPageGroup *out, size_t num_pages, Pool pool, Direction dir, bool unoptimized, bool random, s32 min_heap_index) {
|
||||
/* Check that we're allocating a correctly aligned number of pages. */
|
||||
const size_t min_align_pages = KPageHeap::GetBlockNumPages(m_min_heap_indexes[pool]);
|
||||
R_UNLESS(util::IsAligned(num_pages, min_align_pages), svc::ResultInvalidSize());
|
||||
|
||||
/* Adjust our min heap index to the pool minimum if needed. */
|
||||
min_heap_index = std::max(min_heap_index, m_min_heap_indexes[pool]);
|
||||
|
||||
/* Choose a heap based on our page size request. */
|
||||
const s32 heap_index = KPageHeap::GetBlockIndex(num_pages);
|
||||
R_UNLESS(0 <= heap_index, svc::ResultOutOfMemory());
|
||||
|
||||
/* Ensure that we don't leave anything un-freed. */
|
||||
ON_RESULT_FAILURE {
|
||||
for (const auto &it : *out) {
|
||||
auto &manager = this->GetManager(it.GetAddress());
|
||||
const size_t num_pages = std::min(it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
|
||||
manager.Free(it.GetAddress(), num_pages);
|
||||
}
|
||||
out->Finalize();
|
||||
};
|
||||
|
||||
/* Keep allocating until we've allocated all our pages. */
|
||||
for (s32 index = heap_index; index >= min_heap_index && num_pages > 0; index--) {
|
||||
const size_t pages_per_alloc = KPageHeap::GetBlockNumPages(index);
|
||||
for (Impl *cur_manager = this->GetFirstManager(pool, dir); cur_manager != nullptr; cur_manager = this->GetNextManager(cur_manager, dir)) {
|
||||
while (num_pages >= pages_per_alloc) {
|
||||
/* Allocate a block. */
|
||||
KPhysicalAddress allocated_block = cur_manager->AllocateBlock(index, random);
|
||||
if (allocated_block == Null<KPhysicalAddress>) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ensure we don't leak the block if we fail. */
|
||||
ON_RESULT_FAILURE { cur_manager->Free(allocated_block, pages_per_alloc); };
|
||||
|
||||
/* Add the block to our group. */
|
||||
R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
|
||||
|
||||
/* Maintain the optimized memory bitmap, if we should. */
|
||||
if (unoptimized) {
|
||||
cur_manager->TrackUnoptimizedAllocation(allocated_block, pages_per_alloc);
|
||||
}
|
||||
|
||||
num_pages -= pages_per_alloc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Only succeed if we allocated as many pages as we wanted. */
|
||||
R_UNLESS(num_pages == 0, svc::ResultOutOfMemory());
|
||||
|
||||
/* We succeeded! */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KMemoryManager::AllocateAndOpen(KPageGroup *out, size_t num_pages, size_t align_pages, u32 option) {
|
||||
MESOSPHERE_ASSERT(out != nullptr);
|
||||
MESOSPHERE_ASSERT(out->GetNumPages() == 0);
|
||||
|
||||
/* Early return if we're allocating no pages. */
|
||||
R_SUCCEED_IF(num_pages == 0);
|
||||
|
||||
/* Lock the pool that we're allocating from. */
|
||||
const auto [pool, dir] = DecodeOption(option);
|
||||
KScopedLightLock lk(m_pool_locks[pool]);
|
||||
|
||||
/* Choose a heap based on our alignment size request. */
|
||||
const s32 heap_index = KPageHeap::GetAlignedBlockIndex(align_pages, align_pages);
|
||||
|
||||
/* Allocate the page group. */
|
||||
R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, m_has_optimized_process[pool], true, heap_index));
|
||||
|
||||
/* Open the first reference to the pages. */
|
||||
for (const auto &block : *out) {
|
||||
KPhysicalAddress cur_address = block.GetAddress();
|
||||
size_t remaining_pages = block.GetNumPages();
|
||||
while (remaining_pages > 0) {
|
||||
/* Get the manager for the current address. */
|
||||
auto &manager = this->GetManager(cur_address);
|
||||
|
||||
/* Process part or all of the block. */
|
||||
const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
|
||||
manager.OpenFirst(cur_address, cur_pages);
|
||||
|
||||
/* Advance. */
|
||||
cur_address += cur_pages * PageSize;
|
||||
remaining_pages -= cur_pages;
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KMemoryManager::AllocateForProcess(KPageGroup *out, size_t num_pages, u32 option, u64 process_id, u8 fill_pattern) {
|
||||
MESOSPHERE_ASSERT(out != nullptr);
|
||||
MESOSPHERE_ASSERT(out->GetNumPages() == 0);
|
||||
|
||||
/* Decode the option. */
|
||||
const auto [pool, dir] = DecodeOption(option);
|
||||
|
||||
/* Allocate the memory. */
|
||||
bool optimized;
|
||||
{
|
||||
/* Lock the pool that we're allocating from. */
|
||||
KScopedLightLock lk(m_pool_locks[pool]);
|
||||
|
||||
/* Check if we have an optimized process. */
|
||||
const bool has_optimized = m_has_optimized_process[pool];
|
||||
const bool is_optimized = m_optimized_process_ids[pool] == process_id;
|
||||
|
||||
/* Always use the minimum alignment size. */
|
||||
const s32 heap_index = 0;
|
||||
|
||||
/* Allocate the page group. */
|
||||
R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, has_optimized && !is_optimized, false, heap_index));
|
||||
|
||||
/* Set whether we should optimize. */
|
||||
optimized = has_optimized && is_optimized;
|
||||
}
|
||||
|
||||
/* Perform optimized memory tracking, if we should. */
|
||||
if (optimized) {
|
||||
/* Iterate over the allocated blocks. */
|
||||
for (const auto &block : *out) {
|
||||
/* Get the block extents. */
|
||||
const KPhysicalAddress block_address = block.GetAddress();
|
||||
const size_t block_pages = block.GetNumPages();
|
||||
|
||||
/* If it has no pages, we don't need to do anything. */
|
||||
if (block_pages == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Fill all the pages that we need to fill. */
|
||||
bool any_new = false;
|
||||
{
|
||||
KPhysicalAddress cur_address = block_address;
|
||||
size_t remaining_pages = block_pages;
|
||||
while (remaining_pages > 0) {
|
||||
/* Get the manager for the current address. */
|
||||
auto &manager = this->GetManager(cur_address);
|
||||
|
||||
/* Process part or all of the block. */
|
||||
const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
|
||||
any_new = manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern);
|
||||
|
||||
/* Advance. */
|
||||
cur_address += cur_pages * PageSize;
|
||||
remaining_pages -= cur_pages;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are new pages, update tracking for the allocation. */
|
||||
if (any_new) {
|
||||
/* Update tracking for the allocation. */
|
||||
KPhysicalAddress cur_address = block_address;
|
||||
size_t remaining_pages = block_pages;
|
||||
while (remaining_pages > 0) {
|
||||
/* Get the manager for the current address. */
|
||||
auto &manager = this->GetManager(cur_address);
|
||||
|
||||
/* Lock the pool for the manager. */
|
||||
KScopedLightLock lk(m_pool_locks[manager.GetPool()]);
|
||||
|
||||
/* Track some or all of the current pages. */
|
||||
const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
|
||||
manager.TrackOptimizedAllocation(cur_address, cur_pages);
|
||||
|
||||
/* Advance. */
|
||||
cur_address += cur_pages * PageSize;
|
||||
remaining_pages -= cur_pages;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Set all the allocated memory. */
|
||||
for (const auto &block : *out) {
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress())), fill_pattern, block.GetSize());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size, KVirtualAddress management, KVirtualAddress management_end, Pool p) {
|
||||
/* Calculate management sizes. */
|
||||
const size_t ref_count_size = (size / PageSize) * sizeof(u16);
|
||||
const size_t optimize_map_size = CalculateOptimizedProcessOverheadSize(size);
|
||||
const size_t manager_size = util::AlignUp(optimize_map_size + ref_count_size, PageSize);
|
||||
const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(size);
|
||||
const size_t total_management_size = manager_size + page_heap_size;
|
||||
MESOSPHERE_ABORT_UNLESS(manager_size <= total_management_size);
|
||||
MESOSPHERE_ABORT_UNLESS(management + total_management_size <= management_end);
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(total_management_size, PageSize));
|
||||
|
||||
/* Setup region. */
|
||||
m_pool = p;
|
||||
m_management_region = management;
|
||||
m_page_reference_counts = GetPointer<RefCount>(management + optimize_map_size);
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(m_management_region), PageSize));
|
||||
|
||||
/* Initialize the manager's KPageHeap. */
|
||||
m_heap.Initialize(address, size, management + manager_size, page_heap_size);
|
||||
|
||||
return total_management_size;
|
||||
}
|
||||
|
||||
void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
|
||||
/* Get the range we're tracking. */
|
||||
size_t offset = this->GetPageOffset(block);
|
||||
const size_t last = offset + num_pages - 1;
|
||||
|
||||
/* Track. */
|
||||
u64 *optimize_map = GetPointer<u64>(m_management_region);
|
||||
while (offset <= last) {
|
||||
/* Mark the page as not being optimized-allocated. */
|
||||
optimize_map[offset / BITSIZEOF(u64)] &= ~(u64(1) << (offset % BITSIZEOF(u64)));
|
||||
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
|
||||
/* Get the range we're tracking. */
|
||||
size_t offset = this->GetPageOffset(block);
|
||||
const size_t last = offset + num_pages - 1;
|
||||
|
||||
/* Track. */
|
||||
u64 *optimize_map = GetPointer<u64>(m_management_region);
|
||||
while (offset <= last) {
|
||||
/* Mark the page as being optimized-allocated. */
|
||||
optimize_map[offset / BITSIZEOF(u64)] |= (u64(1) << (offset % BITSIZEOF(u64)));
|
||||
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern) {
|
||||
/* We want to return whether any pages were newly allocated. */
|
||||
bool any_new = false;
|
||||
|
||||
/* Get the range we're processing. */
|
||||
size_t offset = this->GetPageOffset(block);
|
||||
const size_t last = offset + num_pages - 1;
|
||||
|
||||
/* Process. */
|
||||
u64 *optimize_map = GetPointer<u64>(m_management_region);
|
||||
while (offset <= last) {
|
||||
/* Check if the page has been optimized-allocated before. */
|
||||
if ((optimize_map[offset / BITSIZEOF(u64)] & (u64(1) << (offset % BITSIZEOF(u64)))) == 0) {
|
||||
/* If not, it's new. */
|
||||
any_new = true;
|
||||
|
||||
/* Fill the page. */
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(m_heap.GetAddress()) + offset * PageSize), fill_pattern, PageSize);
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* Return the number of pages we processed. */
|
||||
return any_new;
|
||||
}
|
||||
|
||||
size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
|
||||
const size_t ref_count_size = (region_size / PageSize) * sizeof(u16);
|
||||
const size_t optimize_map_size = (util::AlignUp((region_size / PageSize), BITSIZEOF(u64)) / BITSIZEOF(u64)) * sizeof(u64);
|
||||
const size_t manager_meta_size = util::AlignUp(optimize_map_size + ref_count_size, PageSize);
|
||||
const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(region_size);
|
||||
return manager_meta_size + page_heap_size;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit KLightLock g_object_list_lock;
|
||||
constinit KObjectName::List g_object_list;
|
||||
|
||||
}
|
||||
|
||||
void KObjectName::Initialize(KAutoObject *obj, const char *name) {
|
||||
/* Set member variables. */
|
||||
m_object = obj;
|
||||
std::strncpy(m_name, name, sizeof(m_name));
|
||||
m_name[sizeof(m_name) - 1] = '\x00';
|
||||
|
||||
/* Open a reference to the object we hold. */
|
||||
m_object->Open();
|
||||
}
|
||||
|
||||
bool KObjectName::MatchesName(const char *name) const {
|
||||
return std::strncmp(m_name, name, sizeof(m_name)) == 0;
|
||||
}
|
||||
|
||||
Result KObjectName::NewFromName(KAutoObject *obj, const char *name) {
|
||||
/* Create a new object name. */
|
||||
KObjectName *new_name = KObjectName::Allocate();
|
||||
R_UNLESS(new_name != nullptr, svc::ResultOutOfResource());
|
||||
|
||||
/* Initialize the new name. */
|
||||
new_name->Initialize(obj, name);
|
||||
|
||||
/* Check if there's an existing name. */
|
||||
{
|
||||
/* Ensure we have exclusive access to the global list. */
|
||||
KScopedLightLock lk(g_object_list_lock);
|
||||
|
||||
/* If the object doesn't exist, put it into the list. */
|
||||
KScopedAutoObject existing_object = FindImpl(name);
|
||||
if (existing_object.IsNull()) {
|
||||
g_object_list.push_back(*new_name);
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
/* The object already exists, which is an error condition. Perform cleanup. */
|
||||
obj->Close();
|
||||
KObjectName::Free(new_name);
|
||||
R_THROW(svc::ResultInvalidState());
|
||||
}
|
||||
|
||||
Result KObjectName::Delete(KAutoObject *obj, const char *compare_name) {
|
||||
/* Ensure we have exclusive access to the global list. */
|
||||
KScopedLightLock lk(g_object_list_lock);
|
||||
|
||||
/* Find a matching entry in the list, and delete it. */
|
||||
for (auto &name : g_object_list) {
|
||||
if (name.MatchesName(compare_name) && obj == name.GetObject()) {
|
||||
/* We found a match, clean up its resources. */
|
||||
obj->Close();
|
||||
g_object_list.erase(g_object_list.iterator_to(name));
|
||||
KObjectName::Free(std::addressof(name));
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
/* We didn't find the object in the list. */
|
||||
R_THROW(svc::ResultNotFound());
|
||||
}
|
||||
|
||||
KScopedAutoObject<KAutoObject> KObjectName::Find(const char *name) {
|
||||
/* Ensure we have exclusive access to the global list. */
|
||||
KScopedLightLock lk(g_object_list_lock);
|
||||
|
||||
return FindImpl(name);
|
||||
}
|
||||
|
||||
KScopedAutoObject<KAutoObject> KObjectName::FindImpl(const char *compare_name) {
|
||||
/* Try to find a matching object in the global list. */
|
||||
for (const auto &name : g_object_list) {
|
||||
if (name.MatchesName(compare_name)) {
|
||||
return name.GetObject();
|
||||
}
|
||||
}
|
||||
|
||||
/* There's no matching entry in the list. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KPageGroup::Finalize() {
|
||||
KBlockInfo *cur = m_first_block;
|
||||
while (cur != nullptr) {
|
||||
KBlockInfo *next = cur->GetNext();
|
||||
m_manager->Free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
m_first_block = nullptr;
|
||||
m_last_block = nullptr;
|
||||
}
|
||||
|
||||
void KPageGroup::CloseAndReset() {
|
||||
auto &mm = Kernel::GetMemoryManager();
|
||||
|
||||
KBlockInfo *cur = m_first_block;
|
||||
while (cur != nullptr) {
|
||||
KBlockInfo *next = cur->GetNext();
|
||||
mm.Close(cur->GetAddress(), cur->GetNumPages());
|
||||
m_manager->Free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
m_first_block = nullptr;
|
||||
m_last_block = nullptr;
|
||||
}
|
||||
|
||||
size_t KPageGroup::GetNumPages() const {
|
||||
size_t num_pages = 0;
|
||||
|
||||
for (const auto &it : *this) {
|
||||
num_pages += it.GetNumPages();
|
||||
}
|
||||
|
||||
return num_pages;
|
||||
}
|
||||
|
||||
Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) {
|
||||
/* Succeed immediately if we're adding no pages. */
|
||||
R_SUCCEED_IF(num_pages == 0);
|
||||
|
||||
/* Check for overflow. */
|
||||
MESOSPHERE_ASSERT(addr < addr + num_pages * PageSize);
|
||||
|
||||
/* Try to just append to the last block. */
|
||||
if (m_last_block != nullptr) {
|
||||
R_SUCCEED_IF(m_last_block->TryConcatenate(addr, num_pages));
|
||||
}
|
||||
|
||||
/* Allocate a new block. */
|
||||
KBlockInfo *new_block = m_manager->Allocate();
|
||||
R_UNLESS(new_block != nullptr, svc::ResultOutOfResource());
|
||||
|
||||
/* Initialize the block. */
|
||||
new_block->Initialize(addr, num_pages);
|
||||
|
||||
/* Add the block to our list. */
|
||||
if (m_last_block != nullptr) {
|
||||
m_last_block->SetNext(new_block);
|
||||
} else {
|
||||
m_first_block = new_block;
|
||||
}
|
||||
m_last_block = new_block;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageGroup::CopyRangeTo(KPageGroup &out, size_t range_offset, size_t range_size) const {
|
||||
/* Get the previous last block for the group. */
|
||||
KBlockInfo * const out_last = out.m_last_block;
|
||||
const auto out_last_addr = out_last != nullptr ? out_last->GetAddress() : Null<KPhysicalAddress>;
|
||||
const auto out_last_np = out_last != nullptr ? out_last->GetNumPages() : 0;
|
||||
|
||||
/* Ensure we cleanup the group on failure. */
|
||||
ON_RESULT_FAILURE {
|
||||
KBlockInfo *cur = out_last != nullptr ? out_last->GetNext() : out.m_first_block;
|
||||
while (cur != nullptr) {
|
||||
KBlockInfo *next = cur->GetNext();
|
||||
out.m_manager->Free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
if (out_last != nullptr) {
|
||||
out_last->Initialize(out_last_addr, out_last_np);
|
||||
out_last->SetNext(nullptr);
|
||||
} else {
|
||||
out.m_first_block = nullptr;
|
||||
}
|
||||
out.m_last_block = out_last;
|
||||
};
|
||||
|
||||
/* Find the pages within the requested range. */
|
||||
size_t cur_offset = 0, remaining_size = range_size;
|
||||
for (auto it = this->begin(); it != this->end() && remaining_size > 0; ++it) {
|
||||
/* Get the current size. */
|
||||
const size_t cur_size = it->GetSize();
|
||||
|
||||
/* Determine if the offset is in range. */
|
||||
const size_t rel_diff = range_offset - cur_offset;
|
||||
const bool is_before = cur_offset <= range_offset;
|
||||
cur_offset += cur_size;
|
||||
if (is_before && range_offset < cur_offset) {
|
||||
/* It is, so add the block. */
|
||||
const size_t block_size = std::min<size_t>(cur_size - rel_diff, remaining_size);
|
||||
R_TRY(out.AddBlock(it->GetAddress() + rel_diff, block_size / PageSize));
|
||||
|
||||
/* Advance. */
|
||||
cur_offset = range_offset + block_size;
|
||||
remaining_size -= block_size;
|
||||
range_offset += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that we successfully copied the range. */
|
||||
MESOSPHERE_ABORT_UNLESS(remaining_size == 0);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KPageGroup::Open() const {
|
||||
auto &mm = Kernel::GetMemoryManager();
|
||||
|
||||
for (const auto &it : *this) {
|
||||
mm.Open(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
void KPageGroup::OpenFirst() const {
|
||||
auto &mm = Kernel::GetMemoryManager();
|
||||
|
||||
for (const auto &it : *this) {
|
||||
mm.OpenFirst(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
void KPageGroup::Close() const {
|
||||
auto &mm = Kernel::GetMemoryManager();
|
||||
|
||||
for (const auto &it : *this) {
|
||||
mm.Close(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
bool KPageGroup::IsEquivalentTo(const KPageGroup &rhs) const {
|
||||
auto lit = this->begin();
|
||||
auto rit = rhs.begin();
|
||||
auto lend = this->end();
|
||||
auto rend = rhs.end();
|
||||
|
||||
while (lit != lend && rit != rend) {
|
||||
if (*lit != *rit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++lit;
|
||||
++rit;
|
||||
}
|
||||
|
||||
return lit == lend && rit == rend;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KPageHeap::Initialize(KPhysicalAddress address, size_t size, KVirtualAddress management_address, size_t management_size, const size_t *block_shifts, size_t num_block_shifts) {
|
||||
/* Check our assumptions. */
|
||||
MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize));
|
||||
MESOSPHERE_ASSERT(util::IsAligned(size, PageSize));
|
||||
MESOSPHERE_ASSERT(0 < num_block_shifts && num_block_shifts <= NumMemoryBlockPageShifts);
|
||||
const KVirtualAddress management_end = management_address + management_size;
|
||||
|
||||
/* Set our members. */
|
||||
m_heap_address = address;
|
||||
m_heap_size = size;
|
||||
m_num_blocks = num_block_shifts;
|
||||
|
||||
/* Setup bitmaps. */
|
||||
u64 *cur_bitmap_storage = GetPointer<u64>(management_address);
|
||||
for (size_t i = 0; i < num_block_shifts; i++) {
|
||||
const size_t cur_block_shift = block_shifts[i];
|
||||
const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
|
||||
cur_bitmap_storage = m_blocks[i].Initialize(m_heap_address, m_heap_size, cur_block_shift, next_block_shift, cur_bitmap_storage);
|
||||
}
|
||||
|
||||
/* Ensure we didn't overextend our bounds. */
|
||||
MESOSPHERE_ABORT_UNLESS(KVirtualAddress(cur_bitmap_storage) <= management_end);
|
||||
}
|
||||
|
||||
size_t KPageHeap::GetNumFreePages() const {
|
||||
size_t num_free = 0;
|
||||
|
||||
for (size_t i = 0; i < m_num_blocks; i++) {
|
||||
num_free += m_blocks[i].GetNumFreePages();
|
||||
}
|
||||
|
||||
return num_free;
|
||||
}
|
||||
|
||||
KPhysicalAddress KPageHeap::AllocateByLinearSearch(s32 index) {
|
||||
const size_t needed_size = m_blocks[index].GetSize();
|
||||
|
||||
for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) {
|
||||
if (const KPhysicalAddress addr = m_blocks[i].PopBlock(false); addr != Null<KPhysicalAddress>) {
|
||||
if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) {
|
||||
this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
KPhysicalAddress KPageHeap::AllocateByRandom(s32 index, size_t num_pages, size_t align_pages) {
|
||||
/* Get the size and required alignment. */
|
||||
const size_t needed_size = num_pages * PageSize;
|
||||
const size_t align_size = align_pages * PageSize;
|
||||
|
||||
/* Determine meta-alignment of our desired alignment size. */
|
||||
const size_t align_shift = util::CountTrailingZeros(align_size);
|
||||
|
||||
/* Decide on a block to allocate from. */
|
||||
constexpr size_t MinimumPossibleAlignmentsForRandomAllocation = 4;
|
||||
{
|
||||
/* By default, we'll want to look at all blocks larger than our current one. */
|
||||
s32 max_blocks = static_cast<s32>(m_num_blocks);
|
||||
|
||||
/* Determine the maximum block we should try to allocate from. */
|
||||
size_t possible_alignments = 0;
|
||||
for (s32 i = index; i < max_blocks; ++i) {
|
||||
/* Add the possible alignments from blocks at the current size. */
|
||||
possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) * m_blocks[i].GetNumFreeBlocks();
|
||||
|
||||
/* If there are enough possible alignments, we don't need to look at larger blocks. */
|
||||
if (possible_alignments >= MinimumPossibleAlignmentsForRandomAllocation) {
|
||||
max_blocks = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have any possible alignments which require a larger block, we need to pick one. */
|
||||
if (possible_alignments > 0 && index + 1 < max_blocks) {
|
||||
/* Select a random alignment from the possibilities. */
|
||||
const size_t rnd = m_rng.GenerateRandom(possible_alignments);
|
||||
|
||||
/* Determine which block corresponds to the random alignment we chose. */
|
||||
possible_alignments = 0;
|
||||
for (s32 i = index; i < max_blocks; ++i) {
|
||||
/* Add the possible alignments from blocks at the current size. */
|
||||
possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) * m_blocks[i].GetNumFreeBlocks();
|
||||
|
||||
/* If the current block gets us to our random choice, use the current block. */
|
||||
if (rnd < possible_alignments) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pop a block from the index we selected. */
|
||||
if (KPhysicalAddress addr = m_blocks[index].PopBlock(true); addr != Null<KPhysicalAddress>) {
|
||||
/* Determine how much size we have left over. */
|
||||
if (const size_t leftover_size = m_blocks[index].GetSize() - needed_size; leftover_size > 0) {
|
||||
/* Determine how many valid alignments we can have. */
|
||||
const size_t possible_alignments = 1 + (leftover_size >> align_shift);
|
||||
|
||||
/* Select a random valid alignment. */
|
||||
const size_t random_offset = m_rng.GenerateRandom(possible_alignments) << align_shift;
|
||||
|
||||
/* Free memory before the random offset. */
|
||||
if (random_offset != 0) {
|
||||
this->Free(addr, random_offset / PageSize);
|
||||
}
|
||||
|
||||
/* Advance our block by the random offset. */
|
||||
addr += random_offset;
|
||||
|
||||
/* Free memory after our allocated block. */
|
||||
if (random_offset != leftover_size) {
|
||||
this->Free(addr + needed_size, (leftover_size - random_offset) / PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the block we allocated. */
|
||||
return addr;
|
||||
}
|
||||
|
||||
return Null<KPhysicalAddress>;
|
||||
}
|
||||
|
||||
void KPageHeap::FreeBlock(KPhysicalAddress block, s32 index) {
|
||||
do {
|
||||
block = m_blocks[index++].PushBlock(block);
|
||||
} while (block != Null<KPhysicalAddress>);
|
||||
}
|
||||
|
||||
void KPageHeap::Free(KPhysicalAddress addr, size_t num_pages) {
|
||||
/* Freeing no pages is a no-op. */
|
||||
if (num_pages == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the largest block size that we can free, and free as many as possible. */
|
||||
s32 big_index = static_cast<s32>(m_num_blocks) - 1;
|
||||
const KPhysicalAddress start = addr;
|
||||
const KPhysicalAddress end = addr + num_pages * PageSize;
|
||||
KPhysicalAddress before_start = start;
|
||||
KPhysicalAddress before_end = start;
|
||||
KPhysicalAddress after_start = end;
|
||||
KPhysicalAddress after_end = end;
|
||||
while (big_index >= 0) {
|
||||
const size_t block_size = m_blocks[big_index].GetSize();
|
||||
const KPhysicalAddress big_start = util::AlignUp(GetInteger(start), block_size);
|
||||
const KPhysicalAddress big_end = util::AlignDown(GetInteger(end), block_size);
|
||||
if (big_start < big_end) {
|
||||
/* Free as many big blocks as we can. */
|
||||
for (auto block = big_start; block < big_end; block += block_size) {
|
||||
this->FreeBlock(block, big_index);
|
||||
}
|
||||
before_end = big_start;
|
||||
after_start = big_end;
|
||||
break;
|
||||
}
|
||||
big_index--;
|
||||
}
|
||||
MESOSPHERE_ASSERT(big_index >= 0);
|
||||
|
||||
/* Free space before the big blocks. */
|
||||
for (s32 i = big_index - 1; i >= 0; i--) {
|
||||
const size_t block_size = m_blocks[i].GetSize();
|
||||
while (before_start + block_size <= before_end) {
|
||||
before_end -= block_size;
|
||||
this->FreeBlock(before_end, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free space after the big blocks. */
|
||||
for (s32 i = big_index - 1; i >= 0; i--) {
|
||||
const size_t block_size = m_blocks[i].GetSize();
|
||||
while (after_start + block_size <= after_end) {
|
||||
this->FreeBlock(after_start, i);
|
||||
after_start += block_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t KPageHeap::CalculateManagementOverheadSize(size_t region_size, const size_t *block_shifts, size_t num_block_shifts) {
|
||||
size_t overhead_size = 0;
|
||||
for (size_t i = 0; i < num_block_shifts; i++) {
|
||||
const size_t cur_block_shift = block_shifts[i];
|
||||
const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
|
||||
overhead_size += KPageHeap::Block::CalculateManagementOverheadSize(region_size, cur_block_shift, next_block_shift);
|
||||
}
|
||||
return util::AlignUp(overhead_size, PageSize);
|
||||
}
|
||||
|
||||
void KPageHeap::DumpFreeList() const {
|
||||
MESOSPHERE_RELEASE_LOG("KPageHeap::DumpFreeList %p\n", this);
|
||||
|
||||
for (size_t i = 0; i < m_num_blocks; ++i) {
|
||||
const size_t block_size = m_blocks[i].GetSize();
|
||||
const char *suffix;
|
||||
size_t size;
|
||||
if (block_size >= 1_GB) {
|
||||
suffix = "GiB";
|
||||
size = block_size / 1_GB;
|
||||
} else if (block_size >= 1_MB) {
|
||||
suffix = "MiB";
|
||||
size = block_size / 1_MB;
|
||||
} else if (block_size >= 1_KB) {
|
||||
suffix = "KiB";
|
||||
size = block_size / 1_KB;
|
||||
} else {
|
||||
suffix = "B";
|
||||
size = block_size;
|
||||
}
|
||||
|
||||
MESOSPHERE_RELEASE_LOG(" %4zu %s block x %zu\n", size, suffix, m_blocks[i].GetNumFreeBlocks());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KPort::Initialize(s32 max_sessions, bool is_light, uintptr_t name) {
|
||||
/* Open a new reference count to the initialized port. */
|
||||
this->Open();
|
||||
|
||||
/* Create and initialize our server/client pair. */
|
||||
KAutoObject::Create<KServerPort>(std::addressof(m_server));
|
||||
KAutoObject::Create<KClientPort>(std::addressof(m_client));
|
||||
m_server.Initialize(this);
|
||||
m_client.Initialize(this, max_sessions);
|
||||
|
||||
/* Set our member variables. */
|
||||
m_is_light = is_light;
|
||||
m_name = name;
|
||||
m_state = State::Normal;
|
||||
}
|
||||
|
||||
void KPort::OnClientClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
if (m_state == State::Normal) {
|
||||
m_state = State::ClientClosed;
|
||||
}
|
||||
}
|
||||
|
||||
void KPort::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
if (m_state == State::Normal) {
|
||||
m_state = State::ServerClosed;
|
||||
}
|
||||
}
|
||||
|
||||
Result KPort::EnqueueSession(KServerSession *session) {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
R_UNLESS(m_state == State::Normal, svc::ResultPortClosed());
|
||||
|
||||
m_server.EnqueueSession(session);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPort::EnqueueSession(KLightServerSession *session) {
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
R_UNLESS(m_state == State::Normal, svc::ResultPortClosed());
|
||||
|
||||
m_server.EnqueueSession(session);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KReadableEvent::Initialize(KEvent *parent) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
m_is_signaled = false;
|
||||
m_parent = parent;
|
||||
|
||||
if (m_parent != nullptr) {
|
||||
m_parent->Open();
|
||||
}
|
||||
}
|
||||
|
||||
bool KReadableEvent::IsSignaled() const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
|
||||
|
||||
return m_is_signaled;
|
||||
}
|
||||
|
||||
void KReadableEvent::Destroy() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
if (m_parent) {
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
m_parent->OnReadableEventDestroyed();
|
||||
}
|
||||
|
||||
m_parent->Close();
|
||||
}
|
||||
}
|
||||
|
||||
Result KReadableEvent::Signal() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedSchedulerLock lk;
|
||||
|
||||
if (!m_is_signaled) {
|
||||
m_is_signaled = true;
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KReadableEvent::Reset() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedSchedulerLock lk;
|
||||
|
||||
R_UNLESS(m_is_signaled, svc::ResultInvalidState());
|
||||
|
||||
m_is_signaled = false;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr s64 DefaultTimeout = ams::svc::Tick(TimeSpan::FromSeconds(10));
|
||||
|
||||
}
|
||||
|
||||
void KResourceLimit::Initialize() {
|
||||
m_waiter_count = 0;
|
||||
std::memset(m_limit_values, 0, sizeof(m_limit_values));
|
||||
std::memset(m_current_values, 0, sizeof(m_current_values));
|
||||
std::memset(m_current_hints, 0, sizeof(m_current_hints));
|
||||
std::memset(m_peak_values, 0, sizeof(m_peak_values));
|
||||
}
|
||||
|
||||
void KResourceLimit::Finalize() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
s64 KResourceLimit::GetLimitValue(ams::svc::LimitableResource which) const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
s64 value;
|
||||
{
|
||||
KScopedLightLock lk(m_lock);
|
||||
value = m_limit_values[which];
|
||||
MESOSPHERE_ASSERT(value >= 0);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]);
|
||||
MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
s64 KResourceLimit::GetCurrentValue(ams::svc::LimitableResource which) const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
s64 value;
|
||||
{
|
||||
KScopedLightLock lk(m_lock);
|
||||
value = m_current_values[which];
|
||||
MESOSPHERE_ASSERT(value >= 0);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]);
|
||||
MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
s64 KResourceLimit::GetPeakValue(ams::svc::LimitableResource which) const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
s64 value;
|
||||
{
|
||||
KScopedLightLock lk(m_lock);
|
||||
value = m_peak_values[which];
|
||||
MESOSPHERE_ASSERT(value >= 0);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]);
|
||||
MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
s64 KResourceLimit::GetFreeValue(ams::svc::LimitableResource which) const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
s64 value;
|
||||
{
|
||||
KScopedLightLock lk(m_lock);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] >= 0);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]);
|
||||
MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
value = m_limit_values[which] - m_current_values[which];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Result KResourceLimit::SetLimitValue(ams::svc::LimitableResource which, s64 value) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedLightLock lk(m_lock);
|
||||
R_UNLESS(m_current_values[which] <= value, svc::ResultInvalidState());
|
||||
|
||||
m_limit_values[which] = value;
|
||||
m_peak_values[which] = m_current_values[which];
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KResourceLimit::Add(ams::svc::LimitableResource which, s64 value) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(KTargetSystem::IsDynamicResourceLimitsEnabled());
|
||||
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Check that this is a true increase. */
|
||||
MESOSPHERE_ABORT_UNLESS(value > 0);
|
||||
|
||||
/* Check that we can perform an increase. */
|
||||
MESOSPHERE_ABORT_UNLESS(m_current_values[which] <= m_peak_values[which]);
|
||||
MESOSPHERE_ABORT_UNLESS(m_peak_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ABORT_UNLESS(m_current_hints[which] <= m_current_values[which]);
|
||||
|
||||
/* Check that the increase doesn't cause an overflow. */
|
||||
const auto increased_limit = m_limit_values[which] + value;
|
||||
const auto increased_current = m_current_values[which] + value;
|
||||
const auto increased_hint = m_current_hints[which] + value;
|
||||
MESOSPHERE_ABORT_UNLESS(m_limit_values[which] < increased_limit);
|
||||
MESOSPHERE_ABORT_UNLESS(m_current_values[which] < increased_current);
|
||||
MESOSPHERE_ABORT_UNLESS(m_current_hints[which] < increased_hint);
|
||||
|
||||
/* Add the value. */
|
||||
m_limit_values[which] = increased_limit;
|
||||
m_current_values[which] = increased_current;
|
||||
m_current_hints[which] = increased_hint;
|
||||
|
||||
/* Update our peak. */
|
||||
m_peak_values[which] = std::max(m_peak_values[which], increased_current);
|
||||
}
|
||||
|
||||
bool KResourceLimit::Reserve(ams::svc::LimitableResource which, s64 value) {
|
||||
return this->Reserve(which, value, KHardwareTimer::GetTick() + DefaultTimeout);
|
||||
}
|
||||
|
||||
bool KResourceLimit::Reserve(ams::svc::LimitableResource which, s64 value, s64 timeout) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(value >= 0);
|
||||
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
if (m_current_hints[which] >= m_limit_values[which]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Loop until we reserve or run out of time. */
|
||||
while (true) {
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
|
||||
/* If we would overflow, don't allow to succeed. */
|
||||
if (m_current_values[which] + value <= m_current_values[which]) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_current_values[which] + value <= m_limit_values[which]) {
|
||||
m_current_values[which] += value;
|
||||
m_current_hints[which] += value;
|
||||
m_peak_values[which] = std::max(m_peak_values[which], m_current_values[which]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_current_hints[which] + value <= m_limit_values[which] && (timeout < 0 || KHardwareTimer::GetTick() < timeout)) {
|
||||
m_waiter_count++;
|
||||
m_cond_var.Wait(std::addressof(m_lock), timeout, false);
|
||||
m_waiter_count--;
|
||||
|
||||
if (GetCurrentThread().IsTerminationRequested()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KResourceLimit::Release(ams::svc::LimitableResource which, s64 value) {
|
||||
this->Release(which, value, value);
|
||||
}
|
||||
|
||||
void KResourceLimit::Release(ams::svc::LimitableResource which, s64 value, s64 hint) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(value >= 0);
|
||||
MESOSPHERE_ASSERT(hint >= 0);
|
||||
|
||||
KScopedLightLock lk(m_lock);
|
||||
MESOSPHERE_ASSERT(m_current_values[which] <= m_limit_values[which]);
|
||||
MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]);
|
||||
MESOSPHERE_ASSERT(value <= m_current_values[which]);
|
||||
MESOSPHERE_ASSERT(hint <= m_current_hints[which]);
|
||||
|
||||
m_current_values[which] -= value;
|
||||
m_current_hints[which] -= hint;
|
||||
|
||||
if (m_waiter_count != 0) {
|
||||
m_cond_var.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
bool KScheduler::s_scheduler_update_needed;
|
||||
KScheduler::LockType KScheduler::s_scheduler_lock;
|
||||
KSchedulerPriorityQueue KScheduler::s_priority_queue;
|
||||
|
||||
namespace {
|
||||
|
||||
class KSchedulerInterruptHandler : public KInterruptHandler {
|
||||
public:
|
||||
constexpr KSchedulerInterruptHandler() : KInterruptHandler() { /* ... */ }
|
||||
|
||||
virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override {
|
||||
MESOSPHERE_UNUSED(interrupt_id);
|
||||
return GetDummyInterruptTask();
|
||||
}
|
||||
};
|
||||
|
||||
ALWAYS_INLINE void IncrementScheduledCount(KThread *thread) {
|
||||
if (KProcess *parent = thread->GetOwnerProcess(); parent != nullptr) {
|
||||
parent->IncrementScheduledCount();
|
||||
}
|
||||
}
|
||||
|
||||
KSchedulerInterruptHandler g_scheduler_interrupt_handler;
|
||||
|
||||
ALWAYS_INLINE auto *GetSchedulerInterruptHandler() {
|
||||
return std::addressof(g_scheduler_interrupt_handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KScheduler::Initialize(KThread *idle_thread) {
|
||||
/* Set core ID/idle thread/interrupt task manager. */
|
||||
m_core_id = GetCurrentCoreId();
|
||||
m_idle_thread = idle_thread;
|
||||
m_state.idle_thread_stack = m_idle_thread->GetStackTop();
|
||||
m_state.interrupt_task_manager = std::addressof(Kernel::GetInterruptTaskManager());
|
||||
|
||||
/* Insert the main thread into the priority queue. */
|
||||
{
|
||||
KScopedSchedulerLock lk;
|
||||
GetPriorityQueue().PushBack(GetCurrentThreadPointer());
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
|
||||
/* Bind interrupt handler. */
|
||||
Kernel::GetInterruptManager().BindHandler(GetSchedulerInterruptHandler(), KInterruptName_Scheduler, m_core_id, KInterruptController::PriorityLevel_Scheduler, false, false);
|
||||
|
||||
/* Set the current thread. */
|
||||
m_current_thread = GetCurrentThreadPointer();
|
||||
}
|
||||
|
||||
void KScheduler::Activate() {
|
||||
MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() == 1);
|
||||
|
||||
m_state.should_count_idle = KTargetSystem::IsDebugMode();
|
||||
m_is_active = true;
|
||||
RescheduleCurrentCore();
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThread(KThread *highest_thread) {
|
||||
if (KThread *prev_highest_thread = m_state.highest_priority_thread; AMS_LIKELY(prev_highest_thread != highest_thread)) {
|
||||
if (AMS_LIKELY(prev_highest_thread != nullptr)) {
|
||||
IncrementScheduledCount(prev_highest_thread);
|
||||
prev_highest_thread->SetLastScheduledTick(KHardwareTimer::GetTick());
|
||||
}
|
||||
if (m_state.should_count_idle) {
|
||||
if (AMS_LIKELY(highest_thread != nullptr)) {
|
||||
if (KProcess *process = highest_thread->GetOwnerProcess(); process != nullptr) {
|
||||
/* Set running thread (and increment switch count). */
|
||||
process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, ++m_state.switch_count);
|
||||
}
|
||||
} else {
|
||||
/* Set idle count and switch count to switch count + 1. */
|
||||
m_state.idle_count = ++m_state.switch_count;
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_KTRACE_SCHEDULE_UPDATE(m_core_id, (prev_highest_thread != nullptr ? prev_highest_thread : m_idle_thread), (highest_thread != nullptr ? highest_thread : m_idle_thread));
|
||||
|
||||
m_state.highest_priority_thread = highest_thread;
|
||||
m_state.needs_scheduling = true;
|
||||
return (1ul << m_core_id);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreadsImpl() {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* Clear that we need to update. */
|
||||
ClearSchedulerUpdateNeeded();
|
||||
|
||||
u64 cores_needing_scheduling = 0, idle_cores = 0;
|
||||
KThread *top_threads[cpu::NumCores];
|
||||
auto &priority_queue = GetPriorityQueue();
|
||||
|
||||
/* We want to go over all cores, finding the highest priority thread and determining if scheduling is needed for that core. */
|
||||
for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) {
|
||||
KThread *top_thread = priority_queue.GetScheduledFront(core_id);
|
||||
if (top_thread != nullptr) {
|
||||
/* We need to check if the thread's process has a pinned thread. */
|
||||
if (KProcess *parent = top_thread->GetOwnerProcess(); parent != nullptr) {
|
||||
/* Check that there's a pinned thread other than the current top thread. */
|
||||
if (KThread *pinned = parent->GetPinnedThread(core_id); pinned != nullptr && pinned != top_thread) {
|
||||
/* We need to prefer threads with kernel waiters to the pinned thread. */
|
||||
if (top_thread->GetNumKernelWaiters() == 0 && top_thread != parent->GetExceptionThread()) {
|
||||
/* If the pinned thread is runnable, use it. */
|
||||
if (pinned->GetRawState() == KThread::ThreadState_Runnable) {
|
||||
top_thread = pinned;
|
||||
} else {
|
||||
top_thread = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idle_cores |= (1ul << core_id);
|
||||
}
|
||||
|
||||
top_threads[core_id] = top_thread;
|
||||
cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
}
|
||||
|
||||
/* Idle cores are bad. We're going to try to migrate threads to each idle core in turn. */
|
||||
while (idle_cores != 0) {
|
||||
s32 core_id = __builtin_ctzll(idle_cores);
|
||||
|
||||
if (KThread *suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
|
||||
s32 migration_candidates[cpu::NumCores];
|
||||
size_t num_candidates = 0;
|
||||
|
||||
/* While we have a suggested thread, try to migrate it! */
|
||||
while (suggested != nullptr) {
|
||||
/* Check if the suggested thread is the top thread on its core. */
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (KThread *top_thread = (suggested_core >= 0) ? top_threads[suggested_core] : nullptr; top_thread != suggested) {
|
||||
/* Make sure we're not dealing with threads too high priority for migration. */
|
||||
if (top_thread != nullptr && top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* The suggested thread isn't bound to its core, so we can migrate it! */
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested);
|
||||
MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 1);
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Note this core as a candidate for migration. */
|
||||
MESOSPHERE_ASSERT(num_candidates < cpu::NumCores);
|
||||
migration_candidates[num_candidates++] = suggested_core;
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
/* If suggested is nullptr, we failed to migrate a specific thread. So let's try all our candidate cores' top threads. */
|
||||
if (suggested == nullptr) {
|
||||
for (size_t i = 0; i < num_candidates; i++) {
|
||||
/* Check if there's some other thread that can run on the candidate core. */
|
||||
const s32 candidate_core = migration_candidates[i];
|
||||
suggested = top_threads[candidate_core];
|
||||
if (KThread *next_on_candidate_core = priority_queue.GetScheduledNext(candidate_core, suggested); next_on_candidate_core != nullptr) {
|
||||
/* The candidate core can run some other thread! We'll migrate its current top thread to us. */
|
||||
top_threads[candidate_core] = next_on_candidate_core;
|
||||
cores_needing_scheduling |= Kernel::GetScheduler(candidate_core).UpdateHighestPriorityThread(top_threads[candidate_core]);
|
||||
|
||||
/* Perform the migration. */
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(candidate_core, suggested);
|
||||
MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), candidate_core, core_id, 2);
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idle_cores &= ~(1ul << core_id);
|
||||
}
|
||||
|
||||
return cores_needing_scheduling;
|
||||
}
|
||||
|
||||
void KScheduler::SwitchThread(KThread *next_thread) {
|
||||
KProcess * const cur_process = GetCurrentProcessPointer();
|
||||
KThread * const cur_thread = GetCurrentThreadPointer();
|
||||
|
||||
/* We never want to schedule a null thread, so use the idle thread if we don't have a next. */
|
||||
if (next_thread == nullptr) {
|
||||
next_thread = m_idle_thread;
|
||||
}
|
||||
|
||||
if (next_thread->GetCurrentCore() != m_core_id) {
|
||||
next_thread->SetCurrentCore(m_core_id);
|
||||
}
|
||||
|
||||
/* If we're not actually switching thread, there's nothing to do. */
|
||||
if (next_thread == cur_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Next thread is now known not to be nullptr, and must not be dispatchable. */
|
||||
MESOSPHERE_ASSERT(next_thread->GetDisableDispatchCount() == 1);
|
||||
|
||||
/* Update the CPU time tracking variables. */
|
||||
const s64 prev_tick = m_last_context_switch_time;
|
||||
const s64 cur_tick = KHardwareTimer::GetTick();
|
||||
const s64 tick_diff = cur_tick - prev_tick;
|
||||
cur_thread->AddCpuTime(m_core_id, tick_diff);
|
||||
if (cur_process != nullptr) {
|
||||
cur_process->AddCpuTime(tick_diff);
|
||||
}
|
||||
m_last_context_switch_time = cur_tick;
|
||||
|
||||
/* Update our previous thread. */
|
||||
if (cur_process != nullptr) {
|
||||
/* NOTE: Combining this into AMS_LIKELY(!... && ...) triggers an internal compiler error: Segmentation fault in GCC 9.2.0. */
|
||||
if (AMS_LIKELY(!cur_thread->IsTerminationRequested()) && AMS_LIKELY(cur_thread->GetActiveCore() == m_core_id)) {
|
||||
m_state.prev_thread = cur_thread;
|
||||
} else {
|
||||
m_state.prev_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
MESOSPHERE_KTRACE_THREAD_SWITCH(next_thread);
|
||||
|
||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
||||
/* Ensure the single-step bit in mdscr reflects the correct single-step state for the new thread. */
|
||||
/* NOTE: Per ARM docs, changing the single-step bit requires a "context synchronization event" to */
|
||||
/* be sure that our new configuration takes. However, there are three types of synchronization event: */
|
||||
/* Taking an exception, returning from an exception, and ISB. The single-step bit change only matters */
|
||||
/* in EL0...which implies a return-from-exception has occurred since we set the bit. Thus, forcing */
|
||||
/* an ISB is unnecessary, and we can modify the register safely and be confident it will affect the next */
|
||||
/* userland instruction executed. */
|
||||
cpu::MonitorDebugSystemControlRegisterAccessor().SetSoftwareStep(next_thread->IsHardwareSingleStep()).Store();
|
||||
#endif
|
||||
|
||||
/* Switch the current process, if we're switching processes. */
|
||||
if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) {
|
||||
KProcess::Switch(cur_process, next_process);
|
||||
}
|
||||
|
||||
/* Set the new thread. */
|
||||
SetCurrentThread(next_thread);
|
||||
m_current_thread = next_thread;
|
||||
|
||||
/* Set the new Thread Local region. */
|
||||
cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress()));
|
||||
}
|
||||
|
||||
void KScheduler::ClearPreviousThread(KThread *thread) {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
for (size_t i = 0; i < cpu::NumCores; ++i) {
|
||||
/* Get an atomic reference to the core scheduler's previous thread. */
|
||||
const util::AtomicRef<KThread *> prev_thread(Kernel::GetScheduler(static_cast<s32>(i)).m_state.prev_thread);
|
||||
|
||||
/* Atomically clear the previous thread if it's our target. */
|
||||
KThread *compare = thread;
|
||||
prev_thread.CompareExchangeStrong(compare, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStateChanged(KThread *thread, KThread::ThreadState old_state) {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* Check if the state has changed, because if it hasn't there's nothing to do. */
|
||||
const KThread::ThreadState cur_state = thread->GetRawState();
|
||||
if (cur_state == old_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update the priority queues. */
|
||||
if (old_state == KThread::ThreadState_Runnable) {
|
||||
/* If we were previously runnable, then we're not runnable now, and we should remove. */
|
||||
GetPriorityQueue().Remove(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded();
|
||||
} else if (cur_state == KThread::ThreadState_Runnable) {
|
||||
/* If we're now runnable, then we weren't previously, and we should add. */
|
||||
GetPriorityQueue().PushBack(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadPriorityChanged(KThread *thread, s32 old_priority) {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* If the thread is runnable, we want to change its priority in the queue. */
|
||||
if (thread->GetRawState() == KThread::ThreadState_Runnable) {
|
||||
GetPriorityQueue().ChangePriority(old_priority, thread == GetCurrentThreadPointer(), thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadAffinityMaskChanged(KThread *thread, const KAffinityMask &old_affinity, s32 old_core) {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* If the thread is runnable, we want to change its affinity in the queue. */
|
||||
if (thread->GetRawState() == KThread::ThreadState_Runnable) {
|
||||
GetPriorityQueue().ChangeAffinityMask(old_core, old_affinity, thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) {
|
||||
MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread());
|
||||
|
||||
/* Get a reference to the priority queue. */
|
||||
auto &priority_queue = GetPriorityQueue();
|
||||
|
||||
/* Rotate the front of the queue to the end. */
|
||||
KThread *top_thread = priority_queue.GetScheduledFront(core_id, priority);
|
||||
KThread *next_thread = nullptr;
|
||||
if (top_thread != nullptr) {
|
||||
next_thread = priority_queue.MoveToScheduledBack(top_thread);
|
||||
if (next_thread != top_thread) {
|
||||
IncrementScheduledCount(top_thread);
|
||||
IncrementScheduledCount(next_thread);
|
||||
}
|
||||
}
|
||||
|
||||
/* While we have a suggested thread, try to migrate it! */
|
||||
{
|
||||
KThread *suggested = priority_queue.GetSuggestedFront(core_id, priority);
|
||||
while (suggested != nullptr) {
|
||||
/* Check if the suggested thread is the top thread on its core. */
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) {
|
||||
/* If the next thread is a new thread that has been waiting longer than our suggestion, we prefer it to our suggestion. */
|
||||
if (top_thread != next_thread && next_thread != nullptr && next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) {
|
||||
suggested = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we're allowed to do a migration, do one. */
|
||||
/* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */
|
||||
if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the next suggestion. */
|
||||
suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now that we might have migrated a thread with the same priority, check if we can do better. */
|
||||
{
|
||||
KThread *best_thread = priority_queue.GetScheduledFront(core_id);
|
||||
if (best_thread == GetCurrentThreadPointer()) {
|
||||
best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
|
||||
}
|
||||
|
||||
/* If the best thread we can choose has a priority the same or worse than ours, try to migrate a higher priority thread. */
|
||||
if (best_thread != nullptr && best_thread->GetPriority() >= priority) {
|
||||
KThread *suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
/* If the suggestion's priority is the same as ours, don't bother. */
|
||||
if (suggested->GetPriority() >= best_thread->GetPriority()) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if the suggested thread is the top thread on its core. */
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) {
|
||||
/* If we're allowed to do a migration, do one. */
|
||||
/* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */
|
||||
if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the next suggestion. */
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* After a rotation, we need a scheduler update. */
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
|
||||
void KScheduler::YieldWithoutCoreMigration() {
|
||||
/* Validate preconditions. */
|
||||
MESOSPHERE_ASSERT(CanSchedule());
|
||||
MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr);
|
||||
|
||||
/* Get the current thread and process. */
|
||||
KThread &cur_thread = GetCurrentThread();
|
||||
KProcess &cur_process = GetCurrentProcess();
|
||||
|
||||
/* If the thread's yield count matches, there's nothing for us to do. */
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get a reference to the priority queue. */
|
||||
auto &priority_queue = GetPriorityQueue();
|
||||
|
||||
/* Perform the yield. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == KThread::ThreadState_Runnable) {
|
||||
/* Put the current thread at the back of the queue. */
|
||||
KThread *next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
/* If the next thread is different, we have an update to perform. */
|
||||
if (next_thread != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded();
|
||||
} else {
|
||||
/* Otherwise, set the thread's yield count so that we won't waste work until the process is scheduled again. */
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::YieldWithCoreMigration() {
|
||||
/* Validate preconditions. */
|
||||
MESOSPHERE_ASSERT(CanSchedule());
|
||||
MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr);
|
||||
|
||||
/* Get the current thread and process. */
|
||||
KThread &cur_thread = GetCurrentThread();
|
||||
KProcess &cur_process = GetCurrentProcess();
|
||||
|
||||
/* If the thread's yield count matches, there's nothing for us to do. */
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get a reference to the priority queue. */
|
||||
auto &priority_queue = GetPriorityQueue();
|
||||
|
||||
/* Perform the yield. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == KThread::ThreadState_Runnable) {
|
||||
/* Get the current active core. */
|
||||
const s32 core_id = cur_thread.GetActiveCore();
|
||||
|
||||
/* Put the current thread at the back of the queue. */
|
||||
KThread *next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
/* While we have a suggested thread, try to migrate it! */
|
||||
bool recheck = false;
|
||||
KThread *suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
/* Check if the suggested thread is the thread running on its core. */
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
|
||||
if (KThread *running_on_suggested_core = (suggested_core >= 0) ? Kernel::GetScheduler(suggested_core).m_state.highest_priority_thread : nullptr; running_on_suggested_core != suggested) {
|
||||
/* If the current thread's priority is higher than our suggestion's we prefer the next thread to the suggestion. */
|
||||
/* We also prefer the next thread when the current thread's priority is equal to the suggestions, but the next thread has been waiting longer. */
|
||||
if ((suggested->GetPriority() > cur_thread.GetPriority()) ||
|
||||
(suggested->GetPriority() == cur_thread.GetPriority() && next_thread != std::addressof(cur_thread) && next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()))
|
||||
{
|
||||
suggested = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we're allowed to do a migration, do one. */
|
||||
/* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */
|
||||
if (running_on_suggested_core == nullptr || running_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 3);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
} else {
|
||||
/* We couldn't perform a migration, but we should check again on a future yield. */
|
||||
recheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the next suggestion. */
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
|
||||
/* If we still have a suggestion or the next thread is different, we have an update to perform. */
|
||||
if (suggested != nullptr || next_thread != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded();
|
||||
} else if (!recheck) {
|
||||
/* Otherwise if we don't need to re-check, set the thread's yield count so that we won't waste work until the process is scheduled again. */
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::YieldToAnyThread() {
|
||||
/* Validate preconditions. */
|
||||
MESOSPHERE_ASSERT(CanSchedule());
|
||||
MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr);
|
||||
|
||||
/* Get the current thread and process. */
|
||||
KThread &cur_thread = GetCurrentThread();
|
||||
KProcess &cur_process = GetCurrentProcess();
|
||||
|
||||
/* If the thread's yield count matches, there's nothing for us to do. */
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get a reference to the priority queue. */
|
||||
auto &priority_queue = GetPriorityQueue();
|
||||
|
||||
/* Perform the yield. */
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == KThread::ThreadState_Runnable) {
|
||||
/* Get the current active core. */
|
||||
const s32 core_id = cur_thread.GetActiveCore();
|
||||
|
||||
/* Migrate the current thread to core -1. */
|
||||
cur_thread.SetActiveCore(-1);
|
||||
priority_queue.ChangeCore(core_id, std::addressof(cur_thread));
|
||||
MESOSPHERE_KTRACE_CORE_MIGRATION(cur_thread.GetId(), core_id, -1, 4);
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
/* If there's nothing scheduled, we can try to perform a migration. */
|
||||
if (priority_queue.GetScheduledFront(core_id) == nullptr) {
|
||||
/* While we have a suggested thread, try to migrate it! */
|
||||
KThread *suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
/* Check if the suggested thread is the top thread on its core. */
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) {
|
||||
/* If we're allowed to do a migration, do one. */
|
||||
if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested);
|
||||
MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 5);
|
||||
IncrementScheduledCount(suggested);
|
||||
}
|
||||
|
||||
/* Regardless of whether we migrated, we had a candidate, so we're done. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get the next suggestion. */
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
/* If the suggestion is different from the current thread, we need to perform an update. */
|
||||
if (suggested != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded();
|
||||
} else {
|
||||
/* Otherwise, set the thread's yield count so that we won't waste work until the process is scheduled again. */
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
} else {
|
||||
/* Otherwise, we have an update to perform. */
|
||||
SetSchedulerUpdateNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma GCC pop_options
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
KScopedDisableDispatch::~KScopedDisableDispatch() {
|
||||
if (GetCurrentThread().GetDisableDispatchCount() <= 1) {
|
||||
Kernel::GetScheduler().RescheduleCurrentCore();
|
||||
} else {
|
||||
GetCurrentThread().EnableDispatch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KServerPort::Initialize(KPort *parent) {
|
||||
/* Set member variables. */
|
||||
m_parent = parent;
|
||||
}
|
||||
|
||||
bool KServerPort::IsLight() const {
|
||||
return this->GetParent()->IsLight();
|
||||
}
|
||||
|
||||
void KServerPort::CleanupSessions() {
|
||||
/* Ensure our preconditions are met. */
|
||||
if (this->IsLight()) {
|
||||
MESOSPHERE_ASSERT(m_session_list.empty());
|
||||
} else {
|
||||
MESOSPHERE_ASSERT(m_light_session_list.empty());
|
||||
}
|
||||
|
||||
/* Cleanup the session list. */
|
||||
while (true) {
|
||||
/* Get the last session in the list. */
|
||||
KServerSession *session = nullptr;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
if (!m_session_list.empty()) {
|
||||
session = std::addressof(m_session_list.front());
|
||||
m_session_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the session. */
|
||||
if (session != nullptr) {
|
||||
session->Close();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup the light session list. */
|
||||
while (true) {
|
||||
/* Get the last session in the list. */
|
||||
KLightServerSession *session = nullptr;
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
if (!m_light_session_list.empty()) {
|
||||
session = std::addressof(m_light_session_list.front());
|
||||
m_light_session_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the session. */
|
||||
if (session != nullptr) {
|
||||
session->Close();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KServerPort::Destroy() {
|
||||
/* Note with our parent that we're closed. */
|
||||
m_parent->OnServerClosed();
|
||||
|
||||
/* Perform necessary cleanup of our session lists. */
|
||||
this->CleanupSessions();
|
||||
|
||||
/* Close our reference to our parent. */
|
||||
m_parent->Close();
|
||||
}
|
||||
|
||||
bool KServerPort::IsSignaled() const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
if (this->IsLight()) {
|
||||
return !m_light_session_list.empty();
|
||||
} else {
|
||||
return !m_session_list.empty();
|
||||
}
|
||||
}
|
||||
|
||||
void KServerPort::EnqueueSession(KServerSession *session) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(!this->IsLight());
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Add the session to our queue. */
|
||||
m_session_list.push_back(*session);
|
||||
if (m_session_list.size() == 1) {
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void KServerPort::EnqueueSession(KLightServerSession *session) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(this->IsLight());
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Add the session to our queue. */
|
||||
m_light_session_list.push_back(*session);
|
||||
if (m_light_session_list.size() == 1) {
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
KServerSession *KServerPort::AcceptSession() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(!this->IsLight());
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Return the first session in the list. */
|
||||
if (m_session_list.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KServerSession *session = std::addressof(m_session_list.front());
|
||||
m_session_list.pop_front();
|
||||
return session;
|
||||
}
|
||||
|
||||
KLightServerSession *KServerPort::AcceptLightSession() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_ASSERT(this->IsLight());
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* Return the first session in the list. */
|
||||
if (m_light_session_list.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KLightServerSession *session = std::addressof(m_light_session_list.front());
|
||||
m_light_session_list.pop_front();
|
||||
return session;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KSession::Initialize(KClientPort *client_port, uintptr_t name) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Increment reference count. */
|
||||
/* Because reference count is one on creation, this will result */
|
||||
/* in a reference count of two. Thus, when both server and client are closed */
|
||||
/* this object will be destroyed. */
|
||||
this->Open();
|
||||
|
||||
/* Create our sub sessions. */
|
||||
KAutoObject::Create<KServerSession>(std::addressof(m_server));
|
||||
KAutoObject::Create<KClientSession>(std::addressof(m_client));
|
||||
|
||||
/* Initialize our sub sessions. */
|
||||
m_server.Initialize(this);
|
||||
m_client.Initialize(this);
|
||||
|
||||
/* Set state and name. */
|
||||
this->SetState(State::Normal);
|
||||
m_name = name;
|
||||
|
||||
/* Set our owner process. */
|
||||
m_process = GetCurrentProcessPointer();
|
||||
m_process->Open();
|
||||
|
||||
/* Set our port. */
|
||||
m_port = client_port;
|
||||
if (m_port != nullptr) {
|
||||
m_port->Open();
|
||||
}
|
||||
|
||||
/* Mark initialized. */
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void KSession::Finalize() {
|
||||
if (m_port != nullptr) {
|
||||
m_port->OnSessionFinalized();
|
||||
m_port->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void KSession::OnServerClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
if (this->GetState() == State::Normal) {
|
||||
this->SetState(State::ServerClosed);
|
||||
m_client.OnServerClosed();
|
||||
}
|
||||
}
|
||||
|
||||
void KSession::OnClientClosed() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
if (this->GetState() == State::Normal) {
|
||||
this->SetState(State::ClientClosed);
|
||||
m_server.OnClientClosed();
|
||||
}
|
||||
}
|
||||
|
||||
void KSession::PostDestroy(uintptr_t arg) {
|
||||
/* Release the session count resource the owner process holds. */
|
||||
KProcess *owner = reinterpret_cast<KProcess *>(arg);
|
||||
owner->ReleaseResource(ams::svc::LimitableResource_SessionCountMax, 1);
|
||||
owner->Close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KSessionRequest::SessionMappings::PushMap(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state, size_t index) {
|
||||
/* At most 15 buffers of each type (4-bit descriptor counts). */
|
||||
MESOSPHERE_ASSERT(index < NumMappings);
|
||||
|
||||
/* Get the mapping. */
|
||||
Mapping *mapping;
|
||||
if (index < NumStaticMappings) {
|
||||
mapping = std::addressof(m_static_mappings[index]);
|
||||
} else {
|
||||
/* Allocate dynamic mappings as necessary. */
|
||||
if (m_dynamic_mappings == nullptr) {
|
||||
m_dynamic_mappings = DynamicMappings::Allocate();
|
||||
R_UNLESS(m_dynamic_mappings != nullptr, svc::ResultOutOfMemory());
|
||||
}
|
||||
|
||||
mapping = std::addressof(m_dynamic_mappings->Get(index - NumStaticMappings));
|
||||
}
|
||||
|
||||
/* Set the mapping. */
|
||||
mapping->Set(client, server, size, state);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KSessionRequest::SessionMappings::PushSend(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) {
|
||||
MESOSPHERE_ASSERT(m_num_recv == 0);
|
||||
MESOSPHERE_ASSERT(m_num_exch == 0);
|
||||
R_RETURN(this->PushMap(client, server, size, state, m_num_send++));
|
||||
}
|
||||
|
||||
Result KSessionRequest::SessionMappings::PushReceive(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) {
|
||||
MESOSPHERE_ASSERT(m_num_exch == 0);
|
||||
R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv++));
|
||||
}
|
||||
|
||||
Result KSessionRequest::SessionMappings::PushExchange(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) {
|
||||
R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv + m_num_exch++));
|
||||
}
|
||||
|
||||
void KSessionRequest::SessionMappings::Finalize() {
|
||||
if (m_dynamic_mappings) {
|
||||
DynamicMappings::Free(m_dynamic_mappings);
|
||||
m_dynamic_mappings = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KSharedMemory::Initialize(KProcess *owner, size_t size, ams::svc::MemoryPermission own_perm, ams::svc::MemoryPermission rem_perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set members. */
|
||||
m_owner_process_id = owner->GetId();
|
||||
m_owner_perm = own_perm;
|
||||
m_remote_perm = rem_perm;
|
||||
|
||||
/* Get the number of pages. */
|
||||
const size_t num_pages = util::DivideUp(size, PageSize);
|
||||
MESOSPHERE_ASSERT(num_pages > 0);
|
||||
|
||||
/* Get the resource limit. */
|
||||
KResourceLimit *reslimit = owner->GetResourceLimit();
|
||||
|
||||
/* Reserve memory for ourselves. */
|
||||
KScopedResourceReservation memory_reservation(reslimit, ams::svc::LimitableResource_PhysicalMemoryMax, size);
|
||||
R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached());
|
||||
|
||||
/* Allocate the memory. */
|
||||
R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(m_page_group), num_pages, 1, owner->GetAllocateOption()));
|
||||
|
||||
/* Commit our reservation. */
|
||||
memory_reservation.Commit();
|
||||
|
||||
/* Set our resource limit. */
|
||||
m_resource_limit = reslimit;
|
||||
m_resource_limit->Open();
|
||||
|
||||
/* Mark initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
/* Clear all pages in the memory. */
|
||||
for (const auto &block : m_page_group) {
|
||||
std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress())), 0, block.GetSize());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KSharedMemory::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Get the number of pages. */
|
||||
const size_t num_pages = m_page_group.GetNumPages();
|
||||
const size_t size = num_pages * PageSize;
|
||||
|
||||
/* Close and finalize the page group. */
|
||||
m_page_group.Close();
|
||||
m_page_group.Finalize();
|
||||
|
||||
/* Release the memory reservation. */
|
||||
m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, size);
|
||||
m_resource_limit->Close();
|
||||
}
|
||||
|
||||
Result KSharedMemory::Map(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process, ams::svc::MemoryPermission map_perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(m_page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Validate the permission. */
|
||||
const ams::svc::MemoryPermission test_perm = (process->GetId() == m_owner_process_id) ? m_owner_perm : m_remote_perm;
|
||||
if (test_perm == ams::svc::MemoryPermission_DontCare) {
|
||||
MESOSPHERE_ASSERT(map_perm == ams::svc::MemoryPermission_Read || map_perm == ams::svc::MemoryPermission_ReadWrite);
|
||||
} else {
|
||||
R_UNLESS(map_perm == test_perm, svc::ResultInvalidNewMemoryPermission());
|
||||
}
|
||||
|
||||
/* Map the memory. */
|
||||
R_RETURN(table->MapPageGroup(address, m_page_group, KMemoryState_Shared, ConvertToKMemoryPermission(map_perm)));
|
||||
}
|
||||
|
||||
Result KSharedMemory::Unmap(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
MESOSPHERE_UNUSED(process);
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(m_page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Unmap the memory. */
|
||||
R_RETURN(table->UnmapPageGroup(address, m_page_group, KMemoryState_Shared));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace {
|
||||
|
||||
class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait {
|
||||
private:
|
||||
using ThreadListNode = KSynchronizationObject::ThreadListNode;
|
||||
private:
|
||||
KSynchronizationObject **m_objects;
|
||||
ThreadListNode *m_nodes;
|
||||
s32 m_count;
|
||||
public:
|
||||
constexpr ThreadQueueImplForKSynchronizationObjectWait(KSynchronizationObject **o, ThreadListNode *n, s32 c) : m_objects(o), m_nodes(n), m_count(c) { /* ... */ }
|
||||
|
||||
virtual void NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result) override {
|
||||
/* Determine the sync index, and unlink all nodes. */
|
||||
s32 sync_index = -1;
|
||||
for (auto i = 0; i < m_count; ++i) {
|
||||
/* Check if this is the signaled object. */
|
||||
if (m_objects[i] == signaled_object && sync_index == -1) {
|
||||
sync_index = i;
|
||||
}
|
||||
|
||||
/* Unlink the current node from the current object. */
|
||||
m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
|
||||
}
|
||||
|
||||
/* Set the waiting thread's sync index. */
|
||||
waiting_thread->SetSyncedIndex(sync_index);
|
||||
|
||||
/* Set the waiting thread as not cancellable. */
|
||||
waiting_thread->ClearCancellable();
|
||||
|
||||
/* Invoke the base end wait handler. */
|
||||
KThreadQueue::EndWait(waiting_thread, wait_result);
|
||||
}
|
||||
|
||||
virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override {
|
||||
/* Remove all nodes from our list. */
|
||||
for (auto i = 0; i < m_count; ++i) {
|
||||
m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
|
||||
}
|
||||
|
||||
/* Set the waiting thread as not cancellable. */
|
||||
waiting_thread->ClearCancellable();
|
||||
|
||||
/* Invoke the base cancel wait handler. */
|
||||
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void KSynchronizationObject::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* If auditing, ensure that the object has no waiters. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_AUDITING)
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
|
||||
KThread *thread = cur_node->thread;
|
||||
MESOSPHERE_LOG("KSynchronizationObject::Finalize(%p) with %p (id=%ld) waiting.\n", this, thread, thread->GetId());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* NOTE: In Nintendo's kernel, the following is virtual and called here. */
|
||||
/* this->OnFinalizeSynchronizationObject(); */
|
||||
}
|
||||
|
||||
Result KSynchronizationObject::Wait(s32 *out_index, KSynchronizationObject **objects, const s32 num_objects, s64 timeout) {
|
||||
/* Allocate space on stack for thread nodes. */
|
||||
ThreadListNode *thread_nodes = static_cast<ThreadListNode *>(__builtin_alloca(sizeof(ThreadListNode) * num_objects));
|
||||
|
||||
/* Prepare for wait. */
|
||||
KThread *thread = GetCurrentThreadPointer();
|
||||
KHardwareTimer *timer;
|
||||
ThreadQueueImplForKSynchronizationObjectWait wait_queue(objects, thread_nodes, num_objects);
|
||||
|
||||
{
|
||||
/* Setup the scheduling lock and sleep. */
|
||||
KScopedSchedulerLockAndSleep slp(std::addressof(timer), thread, timeout);
|
||||
|
||||
/* Check if the thread should terminate. */
|
||||
if (thread->IsTerminationRequested()) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTerminationRequested());
|
||||
}
|
||||
|
||||
/* Check if any of the objects are already signaled. */
|
||||
for (auto i = 0; i < num_objects; ++i) {
|
||||
MESOSPHERE_ASSERT(objects[i] != nullptr);
|
||||
|
||||
if (objects[i]->IsSignaled()) {
|
||||
*out_index = i;
|
||||
slp.CancelSleep();
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the timeout is zero. */
|
||||
if (timeout == 0) {
|
||||
slp.CancelSleep();
|
||||
R_THROW(svc::ResultTimedOut());
|
||||
}
|
||||
|
||||
/* Check if waiting was canceled. */
|
||||
if (thread->IsWaitCancelled()) {
|
||||
slp.CancelSleep();
|
||||
thread->ClearWaitCancelled();
|
||||
R_THROW(svc::ResultCancelled());
|
||||
}
|
||||
|
||||
/* Add the waiters. */
|
||||
for (auto i = 0; i < num_objects; ++i) {
|
||||
thread_nodes[i].thread = thread;
|
||||
thread_nodes[i].next = nullptr;
|
||||
|
||||
objects[i]->LinkNode(std::addressof(thread_nodes[i]));
|
||||
}
|
||||
|
||||
/* Mark the thread as cancellable. */
|
||||
thread->SetCancellable();
|
||||
|
||||
/* Clear the thread's synced index. */
|
||||
thread->SetSyncedIndex(-1);
|
||||
|
||||
/* Wait for an object to be signaled. */
|
||||
wait_queue.SetHardwareTimer(timer);
|
||||
thread->BeginWait(std::addressof(wait_queue));
|
||||
}
|
||||
|
||||
/* Set the output index. */
|
||||
*out_index = thread->GetSyncedIndex();
|
||||
|
||||
/* Get the wait result. */
|
||||
R_RETURN(thread->GetWaitResult());
|
||||
}
|
||||
|
||||
void KSynchronizationObject::NotifyAvailable(Result result) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
/* If we're not signaled, we've nothing to notify. */
|
||||
if (!this->IsSignaled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iterate over each thread. */
|
||||
for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
|
||||
cur_node->thread->NotifyAvailable(this, result);
|
||||
}
|
||||
}
|
||||
|
||||
void KSynchronizationObject::DumpWaiters() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* If debugging, dump the list of waiters. */
|
||||
#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING)
|
||||
{
|
||||
KScopedSchedulerLock sl;
|
||||
|
||||
MESOSPHERE_RELEASE_LOG("Threads waiting on %p:\n", this);
|
||||
|
||||
for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
|
||||
KThread *thread = cur_node->thread;
|
||||
|
||||
if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG(" %p tid=%ld pid=%ld (%s)\n", thread, thread->GetId(), process->GetId(), process->GetName());
|
||||
} else {
|
||||
MESOSPHERE_RELEASE_LOG(" %p tid=%ld (Kernel)\n", thread, thread->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
/* If we didn't have any waiters, print so. */
|
||||
if (m_thread_list_head == nullptr) {
|
||||
MESOSPHERE_RELEASE_LOG(" None\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
#if defined(ATMOSPHERE_ARCH_ARM64)
|
||||
#include <mesosphere/arch/arm64/kern_secure_monitor_base.hpp>
|
||||
#endif
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
namespace init {
|
||||
|
||||
/* TODO: Is this function name architecture specific? */
|
||||
void StartOtherCore(const ams::kern::init::KInitArguments *init_args);
|
||||
|
||||
}
|
||||
|
||||
/* Initialization. */
|
||||
size_t KSystemControlBase::Init::GetRealMemorySize() {
|
||||
return ams::kern::MainMemorySize;
|
||||
}
|
||||
|
||||
size_t KSystemControlBase::Init::GetIntendedMemorySize() {
|
||||
return ams::kern::MainMemorySize;
|
||||
}
|
||||
|
||||
KPhysicalAddress KSystemControlBase::Init::GetKernelPhysicalBaseAddress(KPhysicalAddress base_address) {
|
||||
const size_t real_dram_size = KSystemControl::Init::GetRealMemorySize();
|
||||
const size_t intended_dram_size = KSystemControl::Init::GetIntendedMemorySize();
|
||||
if (intended_dram_size * 2 <= real_dram_size) {
|
||||
return base_address;
|
||||
} else {
|
||||
return base_address + ((real_dram_size - intended_dram_size) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControlBase::Init::GetInitialProcessBinaryLayout(InitialProcessBinaryLayout *out, KPhysicalAddress kern_base_address) {
|
||||
*out = {
|
||||
.address = GetInteger(KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress)) + KSystemControl::Init::GetIntendedMemorySize() - InitialProcessBinarySizeMax,
|
||||
._08 = 0,
|
||||
.kern_address = GetInteger(kern_base_address),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
bool KSystemControlBase::Init::ShouldIncreaseThreadResourceLimit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
size_t KSystemControlBase::Init::GetApplicationPoolSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t KSystemControlBase::Init::GetAppletPoolSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t KSystemControlBase::Init::GetMinimumNonSecureSystemPoolSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u8 KSystemControlBase::Init::GetDebugLogUartPort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void KSystemControlBase::Init::CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg) {
|
||||
#if defined(ATMOSPHERE_ARCH_ARM64)
|
||||
MESOSPHERE_INIT_ABORT_UNLESS((::ams::kern::arch::arm64::smc::CpuOn<0>(core_id, entrypoint, arg)) == 0);
|
||||
#else
|
||||
AMS_INFINITE_LOOP();
|
||||
#endif
|
||||
}
|
||||
|
||||
void KSystemControlBase::Init::TurnOnCpu(u64 core_id, const ams::kern::init::KInitArguments *args) {
|
||||
/* Get entrypoint. */
|
||||
KPhysicalAddress entrypoint = Null<KPhysicalAddress>;
|
||||
while (!cpu::GetPhysicalAddressReadable(std::addressof(entrypoint), reinterpret_cast<uintptr_t>(::ams::kern::init::StartOtherCore), true)) { /* ... */ }
|
||||
|
||||
/* Get arguments. */
|
||||
KPhysicalAddress args_addr = Null<KPhysicalAddress>;
|
||||
while (!cpu::GetPhysicalAddressReadable(std::addressof(args_addr), reinterpret_cast<uintptr_t>(args), true)) { /* ... */ }
|
||||
|
||||
/* Ensure cache is correct for the initial arguments. */
|
||||
cpu::StoreDataCacheForInitArguments(args, sizeof(*args));
|
||||
|
||||
/* Turn on the cpu. */
|
||||
KSystemControl::Init::CpuOnImpl(core_id, GetInteger(entrypoint), GetInteger(args_addr));
|
||||
}
|
||||
|
||||
/* Randomness for Initialization. */
|
||||
void KSystemControlBase::Init::GenerateRandom(u64 *dst, size_t count) {
|
||||
if (AMS_UNLIKELY(s_uninitialized_random_generator)) {
|
||||
const u64 seed = KHardwareTimer::GetTick();
|
||||
s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32));
|
||||
s_uninitialized_random_generator = false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dst[i] = s_random_generator.GenerateRandomU64();
|
||||
}
|
||||
}
|
||||
|
||||
u64 KSystemControlBase::Init::GenerateRandomRange(u64 min, u64 max) {
|
||||
if (AMS_UNLIKELY(s_uninitialized_random_generator)) {
|
||||
const u64 seed = KHardwareTimer::GetTick();
|
||||
s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32));
|
||||
s_uninitialized_random_generator = false;
|
||||
}
|
||||
|
||||
return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); });
|
||||
}
|
||||
|
||||
/* System Initialization. */
|
||||
void KSystemControlBase::ConfigureKTargetSystem() {
|
||||
/* By default, use the default config set in the KTargetSystem header. */
|
||||
}
|
||||
|
||||
void KSystemControlBase::InitializePhase1() {
|
||||
/* Enable KTargetSystem. */
|
||||
{
|
||||
KTargetSystem::SetInitialized();
|
||||
}
|
||||
|
||||
/* Initialize random and resource limit. */
|
||||
KSystemControlBase::InitializePhase1Base(KHardwareTimer::GetTick());
|
||||
}
|
||||
|
||||
void KSystemControlBase::InitializePhase1Base(u64 seed) {
|
||||
/* Initialize the rng, if we somehow haven't already. */
|
||||
if (AMS_UNLIKELY(s_uninitialized_random_generator)) {
|
||||
s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32));
|
||||
s_uninitialized_random_generator = false;
|
||||
}
|
||||
|
||||
/* Initialize debug logging. */
|
||||
KDebugLog::Initialize();
|
||||
|
||||
/* System ResourceLimit initialization. */
|
||||
{
|
||||
/* Construct the resource limit object. */
|
||||
KResourceLimit &sys_res_limit = Kernel::GetSystemResourceLimit();
|
||||
KAutoObject::Create<KResourceLimit>(std::addressof(sys_res_limit));
|
||||
sys_res_limit.Initialize();
|
||||
|
||||
/* Set the initial limits. */
|
||||
const auto [total_memory_size, kernel_memory_size] = KMemoryLayout::GetTotalAndKernelMemorySizes();
|
||||
|
||||
/* Update 39-bit address space infos. */
|
||||
{
|
||||
/* Heap should be equal to the total memory size, minimum 8 GB, maximum 32 GB. */
|
||||
/* Alias should be equal to 8 * heap size, maximum 128 GB. */
|
||||
const size_t heap_size = std::max(std::min(util::AlignUp(total_memory_size, 1_GB), 32_GB), 8_GB);
|
||||
const size_t alias_size = std::min(heap_size * 8, 128_GB);
|
||||
|
||||
/* Set the address space sizes. */
|
||||
KAddressSpaceInfo::SetAddressSpaceSize(39, KAddressSpaceInfo::Type_Heap, heap_size);
|
||||
KAddressSpaceInfo::SetAddressSpaceSize(39, KAddressSpaceInfo::Type_Alias, alias_size);
|
||||
}
|
||||
|
||||
const auto &slab_counts = init::GetSlabResourceCounts();
|
||||
MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax, total_memory_size));
|
||||
MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_ThreadCountMax, slab_counts.num_KThread));
|
||||
MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_EventCountMax, slab_counts.num_KEvent));
|
||||
MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_TransferMemoryCountMax, slab_counts.num_KTransferMemory));
|
||||
MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_SessionCountMax, slab_counts.num_KSession));
|
||||
|
||||
/* Reserve system memory. */
|
||||
MESOSPHERE_ABORT_UNLESS(sys_res_limit.Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, kernel_memory_size));
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControlBase::InitializePhase2() {
|
||||
/* Initialize KTrace. */
|
||||
if constexpr (IsKTraceEnabled) {
|
||||
const auto &ktrace = KMemoryLayout::GetKernelTraceBufferRegion();
|
||||
KTrace::Initialize(ktrace.GetAddress(), ktrace.GetSize());
|
||||
}
|
||||
}
|
||||
|
||||
u32 KSystemControlBase::GetCreateProcessMemoryPool() {
|
||||
return KMemoryManager::Pool_System;
|
||||
}
|
||||
|
||||
/* Privileged Access. */
|
||||
void KSystemControlBase::ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) {
|
||||
/* TODO */
|
||||
MESOSPHERE_UNUSED(out, address, mask, value);
|
||||
MESOSPHERE_UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
Result KSystemControlBase::ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) {
|
||||
MESOSPHERE_UNUSED(out, address, mask, value);
|
||||
R_THROW(svc::ResultNotImplemented());
|
||||
}
|
||||
|
||||
/* Randomness. */
|
||||
void KSystemControlBase::GenerateRandom(u64 *dst, size_t count) {
|
||||
KScopedInterruptDisable intr_disable;
|
||||
KScopedSpinLock lk(s_random_lock);
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dst[i] = s_random_generator.GenerateRandomU64();
|
||||
}
|
||||
}
|
||||
|
||||
u64 KSystemControlBase::GenerateRandomRange(u64 min, u64 max) {
|
||||
KScopedInterruptDisable intr_disable;
|
||||
KScopedSpinLock lk(s_random_lock);
|
||||
|
||||
return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); });
|
||||
}
|
||||
|
||||
u64 KSystemControlBase::GenerateRandomU64() {
|
||||
KScopedInterruptDisable intr_disable;
|
||||
KScopedSpinLock lk(s_random_lock);
|
||||
|
||||
return s_random_generator.GenerateRandomU64();
|
||||
}
|
||||
|
||||
void KSystemControlBase::SleepSystem() {
|
||||
MESOSPHERE_LOG("SleepSystem() was called\n");
|
||||
}
|
||||
|
||||
void KSystemControlBase::StopSystem(void *) {
|
||||
MESOSPHERE_LOG("KSystemControlBase::StopSystem\n");
|
||||
AMS_INFINITE_LOOP();
|
||||
}
|
||||
|
||||
/* User access. */
|
||||
#if defined(ATMOSPHERE_ARCH_ARM64)
|
||||
void KSystemControlBase::CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* Get the function id for the current call. */
|
||||
u64 function_id = args->r[0];
|
||||
|
||||
/* We'll need to map in pages if arguments are pointers. Prepare page groups to do so. */
|
||||
auto &page_table = GetCurrentProcess().GetPageTable();
|
||||
auto *bim = page_table.GetBlockInfoManager();
|
||||
|
||||
constexpr size_t MaxMappedRegisters = 7;
|
||||
std::array<KPageGroup, MaxMappedRegisters> page_groups = { KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), };
|
||||
|
||||
for (size_t i = 0; i < MaxMappedRegisters; i++) {
|
||||
const size_t reg_id = i + 1;
|
||||
if (function_id & (1ul << (8 + reg_id))) {
|
||||
/* Create and open a new page group for the address. */
|
||||
KVirtualAddress virt_addr = args->r[reg_id];
|
||||
|
||||
if (R_SUCCEEDED(page_table.MakeAndOpenPageGroup(std::addressof(page_groups[i]), util::AlignDown(GetInteger(virt_addr), PageSize), 1, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None))) {
|
||||
/* Translate the virtual address to a physical address. */
|
||||
const auto it = page_groups[i].begin();
|
||||
MESOSPHERE_ASSERT(it != page_groups[i].end());
|
||||
MESOSPHERE_ASSERT(it->GetNumPages() == 1);
|
||||
|
||||
args->r[reg_id] = GetInteger(it->GetAddress()) | (GetInteger(virt_addr) & (PageSize - 1));
|
||||
} else {
|
||||
/* If we couldn't map, we should clear the address. */
|
||||
args->r[reg_id] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Invoke the secure monitor. */
|
||||
KSystemControl::CallSecureMonitorFromUserImpl(args);
|
||||
|
||||
/* Make sure that we close any pages that we opened. */
|
||||
for (size_t i = 0; i < MaxMappedRegisters; i++) {
|
||||
page_groups[i].Close();
|
||||
}
|
||||
}
|
||||
|
||||
void KSystemControlBase::CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) {
|
||||
/* By default, we don't actually support secure monitor, so just set args to a failure code. */
|
||||
args->r[0] = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Secure Memory. */
|
||||
size_t KSystemControlBase::CalculateRequiredSecureMemorySize(size_t size, u32 pool) {
|
||||
MESOSPHERE_UNUSED(pool);
|
||||
return size;
|
||||
}
|
||||
|
||||
Result KSystemControlBase::AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool) {
|
||||
/* Ensure the size is aligned. */
|
||||
constexpr size_t Alignment = PageSize;
|
||||
R_UNLESS(util::IsAligned(size, Alignment), svc::ResultInvalidSize());
|
||||
|
||||
/* Allocate the memory. */
|
||||
const size_t num_pages = size / PageSize;
|
||||
const KPhysicalAddress paddr = Kernel::GetMemoryManager().AllocateAndOpenContinuous(num_pages, Alignment / PageSize, KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool), KMemoryManager::Direction_FromFront));
|
||||
R_UNLESS(paddr != Null<KPhysicalAddress>, svc::ResultOutOfMemory());
|
||||
|
||||
*out = KPageTable::GetHeapVirtualAddress(paddr);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KSystemControlBase::FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool) {
|
||||
/* Ensure the size is aligned. */
|
||||
constexpr size_t Alignment = PageSize;
|
||||
MESOSPHERE_UNUSED(pool);
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(address), Alignment));
|
||||
MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, Alignment));
|
||||
|
||||
/* Close the secure region's pages. */
|
||||
Kernel::GetMemoryManager().Close(KPageTable::GetHeapPhysicalAddress(address), size / PageSize);
|
||||
}
|
||||
|
||||
/* Insecure Memory. */
|
||||
KResourceLimit *KSystemControlBase::GetInsecureMemoryResourceLimit() {
|
||||
return std::addressof(Kernel::GetSystemResourceLimit());
|
||||
}
|
||||
|
||||
u32 KSystemControlBase::GetInsecureMemoryPool() {
|
||||
return KMemoryManager::Pool_SystemNonSecure;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KSecureSystemResource::Initialize(size_t size, KResourceLimit *resource_limit, KMemoryManager::Pool pool) {
|
||||
/* Set members. */
|
||||
m_resource_limit = resource_limit;
|
||||
m_resource_size = size;
|
||||
m_resource_pool = pool;
|
||||
|
||||
/* Determine required size for our secure resource. */
|
||||
const size_t secure_size = this->CalculateRequiredSecureMemorySize();
|
||||
|
||||
/* Reserve memory for our secure resource. */
|
||||
KScopedResourceReservation memory_reservation(m_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, secure_size);
|
||||
R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached());
|
||||
|
||||
/* Allocate secure memory. */
|
||||
R_TRY(KSystemControl::AllocateSecureMemory(std::addressof(m_resource_address), m_resource_size, m_resource_pool));
|
||||
MESOSPHERE_ASSERT(m_resource_address != Null<KVirtualAddress>);
|
||||
|
||||
/* Ensure we clean up the secure memory, if we fail past this point. */
|
||||
ON_RESULT_FAILURE { KSystemControl::FreeSecureMemory(m_resource_address, m_resource_size, m_resource_pool); };
|
||||
|
||||
/* Check that our allocation is bigger than the reference counts needed for it. */
|
||||
const size_t rc_size = util::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize);
|
||||
R_UNLESS(m_resource_size > rc_size, svc::ResultOutOfMemory());
|
||||
|
||||
/* Initialize slab heaps. */
|
||||
m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size, PageSize);
|
||||
m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, GetPointer<KPageTableManager::RefCount>(m_resource_address));
|
||||
m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
|
||||
m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
|
||||
|
||||
/* Initialize managers. */
|
||||
m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_page_table_heap));
|
||||
m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_memory_block_heap));
|
||||
m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_block_info_heap));
|
||||
|
||||
/* Set our managers. */
|
||||
this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager);
|
||||
|
||||
/* Commit the memory reservation. */
|
||||
memory_reservation.Commit();
|
||||
|
||||
/* Open reference to our resource limit. */
|
||||
if (m_resource_limit != nullptr) {
|
||||
m_resource_limit->Open();
|
||||
}
|
||||
|
||||
/* Set ourselves as initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KSecureSystemResource::Finalize() {
|
||||
/* Check that we have no outstanding allocations. */
|
||||
MESOSPHERE_ABORT_UNLESS(m_memory_block_slab_manager.GetUsed() == 0);
|
||||
MESOSPHERE_ABORT_UNLESS(m_block_info_manager.GetUsed() == 0);
|
||||
MESOSPHERE_ABORT_UNLESS(m_page_table_manager.GetUsed() == 0);
|
||||
|
||||
/* Free our secure memory. */
|
||||
KSystemControl::FreeSecureMemory(m_resource_address, m_resource_size, m_resource_pool);
|
||||
|
||||
/* Clean up our resource usage. */
|
||||
if (m_resource_limit != nullptr) {
|
||||
/* Release the memory reservation. */
|
||||
m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, this->CalculateRequiredSecureMemorySize());
|
||||
|
||||
/* Close reference to our resource limit. */
|
||||
m_resource_limit->Close();
|
||||
}
|
||||
}
|
||||
|
||||
size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool) {
|
||||
return KSystemControl::CalculateRequiredSecureMemorySize(size, pool);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KThreadLocalPage::Initialize(KProcess *process) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set that this process owns us. */
|
||||
m_owner = process;
|
||||
|
||||
/* Allocate a new page. */
|
||||
KPageBuffer *page_buf = KPageBuffer::AllocateChecked<PageSize>();
|
||||
R_UNLESS(page_buf != nullptr, svc::ResultOutOfMemory());
|
||||
ON_RESULT_FAILURE { KPageBuffer::Free(page_buf); };
|
||||
|
||||
/* Map the address in. */
|
||||
R_RETURN(m_owner->GetPageTable().MapPages(std::addressof(m_virt_addr), 1, PageSize, page_buf->GetPhysicalAddress(), KMemoryState_ThreadLocal, KMemoryPermission_UserReadWrite));
|
||||
}
|
||||
|
||||
Result KThreadLocalPage::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Get the physical address of the page. */
|
||||
KPhysicalAddress phys_addr = Null<KPhysicalAddress>;
|
||||
MESOSPHERE_ABORT_UNLESS(m_owner->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), this->GetAddress()));
|
||||
|
||||
/* Unmap the page. */
|
||||
R_TRY(m_owner->GetPageTable().UnmapPages(this->GetAddress(), 1, KMemoryState_ThreadLocal));
|
||||
|
||||
/* Free the page. */
|
||||
KPageBuffer::FreeChecked<PageSize>(KPageBuffer::FromPhysicalAddress(phys_addr));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
KProcessAddress KThreadLocalPage::Reserve() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
for (size_t i = 0; i < util::size(m_is_region_free); i++) {
|
||||
if (m_is_region_free[i]) {
|
||||
m_is_region_free[i] = false;
|
||||
return this->GetRegionAddress(i);
|
||||
}
|
||||
}
|
||||
|
||||
return Null<KProcessAddress>;
|
||||
}
|
||||
|
||||
void KThreadLocalPage::Release(KProcessAddress addr) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
m_is_region_free[this->GetRegionIndex(addr)] = true;
|
||||
}
|
||||
|
||||
void *KThreadLocalPage::GetPointer() const {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
KPhysicalAddress phys_addr;
|
||||
MESOSPHERE_ABORT_UNLESS(m_owner->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), this->GetAddress()));
|
||||
return static_cast<void *>(KPageBuffer::FromPhysicalAddress(phys_addr));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
void KThreadQueue::NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result) {
|
||||
MESOSPHERE_UNUSED(waiting_thread, signaled_object, wait_result);
|
||||
MESOSPHERE_PANIC("KThreadQueue::NotifyAvailable\n");
|
||||
}
|
||||
|
||||
void KThreadQueue::EndWait(KThread *waiting_thread, Result wait_result) {
|
||||
/* Set the thread's wait result. */
|
||||
waiting_thread->SetWaitResult(wait_result);
|
||||
|
||||
/* Set the thread as runnable. */
|
||||
waiting_thread->SetState(KThread::ThreadState_Runnable);
|
||||
|
||||
/* Clear the thread's wait queue. */
|
||||
waiting_thread->ClearWaitQueue();
|
||||
|
||||
/* Cancel the thread task. */
|
||||
if (m_hardware_timer != nullptr) {
|
||||
m_hardware_timer->CancelTask(waiting_thread);
|
||||
}
|
||||
}
|
||||
|
||||
void KThreadQueue::CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) {
|
||||
/* Set the thread's wait result. */
|
||||
waiting_thread->SetWaitResult(wait_result);
|
||||
|
||||
/* Set the thread as runnable. */
|
||||
waiting_thread->SetState(KThread::ThreadState_Runnable);
|
||||
|
||||
/* Clear the thread's wait queue. */
|
||||
waiting_thread->ClearWaitQueue();
|
||||
|
||||
/* Cancel the thread task. */
|
||||
if (cancel_timer_task && m_hardware_timer != nullptr) {
|
||||
m_hardware_timer->CancelTask(waiting_thread);
|
||||
}
|
||||
}
|
||||
|
||||
void KThreadQueueWithoutEndWait::EndWait(KThread *waiting_thread, Result wait_result) {
|
||||
MESOSPHERE_UNUSED(waiting_thread, wait_result);
|
||||
MESOSPHERE_PANIC("KThreadQueueWithoutEndWait::EndWait\n");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
/* Static initializations. */
|
||||
constinit bool KTrace::s_is_active = false;
|
||||
|
||||
namespace {
|
||||
|
||||
constinit KSpinLock g_ktrace_lock;
|
||||
constinit KVirtualAddress g_ktrace_buffer_address = Null<KVirtualAddress>;
|
||||
constinit size_t g_ktrace_buffer_size = 0;
|
||||
constinit u64 g_type_filter = 0;
|
||||
|
||||
struct KTraceHeader {
|
||||
u32 magic;
|
||||
u32 offset;
|
||||
u32 index;
|
||||
u32 count;
|
||||
|
||||
static constexpr u32 Magic = util::FourCC<'K','T','R','0'>::Code;
|
||||
};
|
||||
static_assert(util::is_pod<KTraceHeader>::value);
|
||||
|
||||
struct KTraceRecord {
|
||||
u8 core_id;
|
||||
u8 type;
|
||||
u16 process_id;
|
||||
u32 thread_id;
|
||||
u64 tick;
|
||||
u64 data[6];
|
||||
};
|
||||
static_assert(util::is_pod<KTraceRecord>::value);
|
||||
static_assert(sizeof(KTraceRecord) == 0x40);
|
||||
|
||||
ALWAYS_INLINE bool IsTypeFiltered(u8 type) {
|
||||
return (g_type_filter & (UINT64_C(1) << (type & (BITSIZEOF(u64) - 1)))) != 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void KTrace::Initialize(KVirtualAddress address, size_t size) {
|
||||
/* Only perform tracing when on development hardware. */
|
||||
if (KTargetSystem::IsDebugMode()) {
|
||||
const size_t offset = util::AlignUp(sizeof(KTraceHeader), sizeof(KTraceRecord));
|
||||
if (offset < size) {
|
||||
/* Clear the trace buffer. */
|
||||
std::memset(GetVoidPointer(address), 0, size);
|
||||
|
||||
/* Initialize the KTrace header. */
|
||||
KTraceHeader *header = GetPointer<KTraceHeader>(address);
|
||||
header->magic = KTraceHeader::Magic;
|
||||
header->offset = offset;
|
||||
header->index = 0;
|
||||
header->count = (size - offset) / sizeof(KTraceRecord);
|
||||
|
||||
/* Set the global data. */
|
||||
g_ktrace_buffer_address = address;
|
||||
g_ktrace_buffer_size = size;
|
||||
|
||||
/* Set the filters to defaults. */
|
||||
g_type_filter = ~(UINT64_C(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KTrace::Start() {
|
||||
if (g_ktrace_buffer_address != Null<KVirtualAddress>) {
|
||||
/* Get exclusive access to the trace buffer. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_ktrace_lock);
|
||||
|
||||
/* Reset the header. */
|
||||
KTraceHeader *header = GetPointer<KTraceHeader>(g_ktrace_buffer_address);
|
||||
header->index = 0;
|
||||
|
||||
/* Reset the records. */
|
||||
KTraceRecord *records = GetPointer<KTraceRecord>(g_ktrace_buffer_address + header->offset);
|
||||
std::memset(records, 0, sizeof(*records) * header->count);
|
||||
|
||||
/* Note that we're active. */
|
||||
s_is_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
void KTrace::Stop() {
|
||||
if (g_ktrace_buffer_address != Null<KVirtualAddress>) {
|
||||
/* Get exclusive access to the trace buffer. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_ktrace_lock);
|
||||
|
||||
/* Note that we're paused. */
|
||||
s_is_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void KTrace::PushRecord(u8 type, u64 param0, u64 param1, u64 param2, u64 param3, u64 param4, u64 param5) {
|
||||
/* Get exclusive access to the trace buffer. */
|
||||
KScopedInterruptDisable di;
|
||||
KScopedSpinLock lk(g_ktrace_lock);
|
||||
|
||||
/* Check whether we should push the record to the trace buffer. */
|
||||
if (s_is_active && IsTypeFiltered(type)) {
|
||||
/* Get the current thread and process. */
|
||||
KThread &cur_thread = GetCurrentThread();
|
||||
KProcess *cur_process = GetCurrentProcessPointer();
|
||||
|
||||
/* Get the current record index from the header. */
|
||||
KTraceHeader *header = GetPointer<KTraceHeader>(g_ktrace_buffer_address);
|
||||
u32 index = header->index;
|
||||
|
||||
/* Get the current record. */
|
||||
KTraceRecord *record = GetPointer<KTraceRecord>(g_ktrace_buffer_address + header->offset + index * sizeof(KTraceRecord));
|
||||
|
||||
/* Set the record's data. */
|
||||
*record = {
|
||||
.core_id = static_cast<u8>(GetCurrentCoreId()),
|
||||
.type = type,
|
||||
.process_id = static_cast<u16>(cur_process != nullptr ? cur_process->GetId() : ~0),
|
||||
.thread_id = static_cast<u32>(cur_thread.GetId()),
|
||||
.tick = static_cast<u64>(KHardwareTimer::GetTick()),
|
||||
.data = { param0, param1, param2, param3, param4, param5 },
|
||||
};
|
||||
|
||||
/* Advance the current index. */
|
||||
if ((++index) >= header->count) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
/* Set the next index. */
|
||||
header->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/>.
|
||||
*/
|
||||
#include <mesosphere.hpp>
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
Result KTransferMemory::Initialize(KProcessAddress addr, size_t size, ams::svc::MemoryPermission own_perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Set members. */
|
||||
m_owner = GetCurrentProcessPointer();
|
||||
|
||||
/* Get the owner page table. */
|
||||
auto &page_table = m_owner->GetPageTable();
|
||||
|
||||
/* Construct the page group, guarding to make sure our state is valid on exit. */
|
||||
auto pg_guard = util::ConstructAtGuarded(m_page_group, page_table.GetBlockInfoManager());
|
||||
|
||||
/* Lock the memory. */
|
||||
R_TRY(page_table.LockForTransferMemory(GetPointer(m_page_group), addr, size, ConvertToKMemoryPermission(own_perm)));
|
||||
|
||||
/* Set remaining tracking members. */
|
||||
m_owner->Open();
|
||||
m_owner_perm = own_perm;
|
||||
m_address = addr;
|
||||
m_is_initialized = true;
|
||||
m_is_mapped = false;
|
||||
|
||||
/* We succeeded. */
|
||||
pg_guard.Cancel();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KTransferMemory::Finalize() {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Unlock. */
|
||||
if (!m_is_mapped) {
|
||||
const size_t size = GetReference(m_page_group).GetNumPages() * PageSize;
|
||||
MESOSPHERE_R_ABORT_UNLESS(m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, GetReference(m_page_group)));
|
||||
}
|
||||
|
||||
/* Close the page group. */
|
||||
GetReference(m_page_group).Close();
|
||||
GetReference(m_page_group).Finalize();
|
||||
}
|
||||
|
||||
void KTransferMemory::PostDestroy(uintptr_t arg) {
|
||||
KProcess *owner = reinterpret_cast<KProcess *>(arg);
|
||||
owner->ReleaseResource(ams::svc::LimitableResource_TransferMemoryCountMax, 1);
|
||||
owner->Close();
|
||||
}
|
||||
|
||||
Result KTransferMemory::Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Validate the permission. */
|
||||
R_UNLESS(m_owner_perm == map_perm, svc::ResultInvalidState());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Ensure we're not already mapped. */
|
||||
R_UNLESS(!m_is_mapped, svc::ResultInvalidState());
|
||||
|
||||
/* Map the memory. */
|
||||
const KMemoryState state = (m_owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered;
|
||||
R_TRY(GetCurrentProcess().GetPageTable().MapPageGroup(address, GetReference(m_page_group), state, KMemoryPermission_UserReadWrite));
|
||||
|
||||
/* Mark ourselves as mapped. */
|
||||
m_is_mapped = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
|
||||
MESOSPHERE_ASSERT_THIS();
|
||||
|
||||
/* Validate the size. */
|
||||
R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize());
|
||||
|
||||
/* Lock ourselves. */
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
/* Unmap the memory. */
|
||||
const KMemoryState state = (m_owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered;
|
||||
R_TRY(GetCurrentProcess().GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), state));
|
||||
|
||||
/* Mark ourselves as unmapped. */
|
||||
MESOSPHERE_ASSERT(m_is_mapped);
|
||||
m_is_mapped = false;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user