mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-06-21 11:02:45 +02:00
222 lines
7.8 KiB
C++
222 lines
7.8 KiB
C++
/*
|
|
* 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 <stratosphere.hpp>
|
|
#include "os_memory_heap_manager.hpp"
|
|
#include "os_thread_manager.hpp"
|
|
|
|
namespace ams::os::impl {
|
|
|
|
Result MemoryHeapManager::SetHeapSize(size_t size) {
|
|
/* Check pre-conditions. */
|
|
AMS_ASSERT(util::IsAligned(size, MemoryHeapUnitSize));
|
|
|
|
/* Acquire locks. */
|
|
std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread));
|
|
std::scoped_lock lk2(m_cs);
|
|
|
|
/* If we need to, expand the heap. */
|
|
if (size > m_heap_size) {
|
|
/* Set the new heap size. */
|
|
uintptr_t address = 0;
|
|
R_TRY(m_impl.SetHeapSize(std::addressof(address), size));
|
|
R_UNLESS(address != 0, os::ResultOutOfMemory());
|
|
|
|
/* Check that the new heap address is consistent. */
|
|
if (m_heap_size == 0) {
|
|
AMS_ASSERT(util::IsAligned(address, MemoryHeapUnitSize));
|
|
} else {
|
|
AMS_ASSERT(address == m_heap_address);
|
|
}
|
|
|
|
/* Set up the new heap address. */
|
|
this->AddToFreeSpaceUnsafe(address + m_heap_size, size - m_heap_size);
|
|
|
|
/* Set our heap address. */
|
|
m_heap_address = address;
|
|
m_heap_size = size;
|
|
} else if (size < m_heap_size) {
|
|
/* We're shrinking the heap, so we need to remove memory blocks. */
|
|
const uintptr_t end_address = m_heap_address + size;
|
|
const size_t remove_size = m_heap_size - size;
|
|
|
|
/* Get the end of the heap. */
|
|
auto it = m_free_memory_list.end();
|
|
--it;
|
|
R_UNLESS(it != m_free_memory_list.end(), os::ResultBusy());
|
|
|
|
/* Check that the block can be decommitted. */
|
|
R_UNLESS(it->GetAddress() <= end_address, os::ResultBusy());
|
|
R_UNLESS(it->GetSize() >= remove_size, os::ResultBusy());
|
|
|
|
/* Adjust the last node. */
|
|
if (const size_t node_size = it->GetSize() - remove_size; node_size == 0) {
|
|
m_free_memory_list.erase(it);
|
|
it->Clean();
|
|
} else {
|
|
it->SetSize(node_size);
|
|
}
|
|
|
|
/* Set the reduced heap size. */
|
|
uintptr_t address = 0;
|
|
R_ABORT_UNLESS(m_impl.SetHeapSize(std::addressof(address), size));
|
|
|
|
/* Set our heap address. */
|
|
m_heap_size = size;
|
|
if (size == 0) {
|
|
m_heap_address = 0;
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result MemoryHeapManager::AllocateFromHeap(uintptr_t *out_address, size_t size) {
|
|
/* Acquire locks. */
|
|
std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread));
|
|
std::scoped_lock lk2(m_cs);
|
|
|
|
/* Find free space. */
|
|
auto it = this->FindFreeSpaceUnsafe(size);
|
|
R_UNLESS(it != m_free_memory_list.end(), os::ResultOutOfMemory());
|
|
|
|
/* If necessary, split the memory block. */
|
|
if (it->GetSize() > size) {
|
|
this->SplitFreeMemoryNodeUnsafe(it, size);
|
|
}
|
|
|
|
/* Remove the block. */
|
|
m_free_memory_list.erase(it);
|
|
it->Clean();
|
|
|
|
/* Increment the used heap size. */
|
|
m_used_heap_size += size;
|
|
|
|
/* Set the output address. */
|
|
*out_address = it->GetAddress();
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
void MemoryHeapManager::ReleaseToHeap(uintptr_t address, size_t size) {
|
|
/* Acquire locks. */
|
|
std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread));
|
|
std::scoped_lock lk2(m_cs);
|
|
|
|
/* Check pre-condition. */
|
|
AMS_ABORT_UNLESS(this->IsRegionAllocatedMemoryUnsafe(address, size));
|
|
|
|
/* Restore the permissions on the memory. */
|
|
os::SetMemoryPermission(address, size, MemoryPermission_ReadWrite);
|
|
os::SetMemoryAttribute(address, size, MemoryAttribute_Normal);
|
|
|
|
/* Add the memory back to our free list. */
|
|
this->AddToFreeSpaceUnsafe(address, size);
|
|
|
|
/* Decrement the used heap size. */
|
|
m_used_heap_size -= size;
|
|
}
|
|
|
|
MemoryHeapManager::FreeMemoryList::iterator MemoryHeapManager::FindFreeSpaceUnsafe(size_t size) {
|
|
/* Find the best fit candidate. */
|
|
auto best = m_free_memory_list.end();
|
|
|
|
for (auto it = m_free_memory_list.begin(); it != m_free_memory_list.end(); ++it) {
|
|
if (const size_t node_size = it->GetSize(); node_size >= size) {
|
|
if (best == m_free_memory_list.end() || node_size < best->GetSize()) {
|
|
best = it;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
MemoryHeapManager::FreeMemoryList::iterator MemoryHeapManager::ConcatenatePreviousFreeMemoryNodeUnsafe(FreeMemoryList::iterator node) {
|
|
/* Get the previous node. */
|
|
auto prev = node;
|
|
--prev;
|
|
|
|
/* If there's no previous, we're done. */
|
|
if (prev == m_free_memory_list.end() || node == m_free_memory_list.end()) {
|
|
return node;
|
|
}
|
|
|
|
/* Otherwise, if the previous isn't contiguous, we can't merge. */
|
|
if (prev->GetAddress() + prev->GetSize() != node->GetAddress()) {
|
|
return node;
|
|
}
|
|
|
|
/* Otherwise, increase the size of the previous node, and remove the current node. */
|
|
prev->SetSize(prev->GetSize() + node->GetSize());
|
|
m_free_memory_list.erase(node);
|
|
node->Clean();
|
|
|
|
return prev;
|
|
}
|
|
|
|
void MemoryHeapManager::SplitFreeMemoryNodeUnsafe(FreeMemoryList::iterator it, size_t size) {
|
|
/* Check pre-conditions. */
|
|
AMS_ASSERT(it->GetSize() > size);
|
|
AMS_ASSERT(util::IsAligned(size, MemoryBlockUnitSize));
|
|
|
|
/* Create new node. */
|
|
auto *new_node = std::construct_at(reinterpret_cast<FreeMemoryNode *>(it->GetAddress() + size));
|
|
new_node->SetSize(it->GetSize() - size);
|
|
|
|
/* Set the old node's size. */
|
|
it->SetSize(size);
|
|
|
|
/* Insert the new node. */
|
|
m_free_memory_list.insert(++it, *new_node);
|
|
}
|
|
|
|
void MemoryHeapManager::AddToFreeSpaceUnsafe(uintptr_t address, size_t size) {
|
|
/* Create new node. */
|
|
auto *new_node = std::construct_at(reinterpret_cast<FreeMemoryNode *>(address));
|
|
new_node->SetSize(size);
|
|
|
|
/* Find the appropriate place to insert the node. */
|
|
auto it = m_free_memory_list.begin();
|
|
for (/* ... */; it != m_free_memory_list.end(); ++it) {
|
|
if (address < it->GetAddress()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Insert the new node. */
|
|
it = m_free_memory_list.insert(it, *new_node);
|
|
|
|
/* Perform coalescing as relevant. */
|
|
it = this->ConcatenatePreviousFreeMemoryNodeUnsafe(it);
|
|
this->ConcatenatePreviousFreeMemoryNodeUnsafe(++it);
|
|
}
|
|
|
|
bool MemoryHeapManager::IsRegionAllocatedMemoryUnsafe(uintptr_t address, size_t size) {
|
|
/* Look for a node containing the region. */
|
|
for (auto it = m_free_memory_list.begin(); it != m_free_memory_list.end(); ++it) {
|
|
const uintptr_t node_address = it->GetAddress();
|
|
const size_t node_size = it->GetSize();
|
|
|
|
if (node_address < address + size && address < node_address + node_size) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|