#include "kernel/memory.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/magicEnum.h" #include "common/stringUtils.h" #include "common/threads.h" #include "common/virtualMemory.h" #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/host_gpu/renderer/gpuResourceManager.h" #include "libs/errno.h" #include "libs/libs.h" #include #include #include #include #include #include #include #include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #include // IWYU pragma: keep #ifndef MEM_RESERVE_PLACEHOLDER #define MEM_RESERVE_PLACEHOLDER 0x00040000 #endif #ifndef MEM_REPLACE_PLACEHOLDER #define MEM_REPLACE_PLACEHOLDER 0x00004000 #endif #ifndef MEM_PRESERVE_PLACEHOLDER #define MEM_PRESERVE_PLACEHOLDER 0x00000002 #endif #ifndef MEM_COALESCE_PLACEHOLDERS #define MEM_COALESCE_PLACEHOLDERS 0x00000001 #endif #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX #include #include #include #include #include #endif namespace Libs::LibKernel::Memory { namespace VirtualMemory = Common::VirtualMemory; LIB_NAME("libkernel", "libkernel"); constexpr int PROT_CPU_READ = 0x01; constexpr int PROT_CPU_WRITE = 0x02; constexpr int PROT_CPU_EXEC = 0x04; constexpr int PROT_GPU_READ = 0x10; constexpr int PROT_GPU_WRITE = 0x20; enum class GpuAccessMode { NoAccess, Read, Write, ReadWrite }; constexpr uint64_t PAGE_TABLE_POOL_SIZE = 4ull * 1024ull * 1024ull * 1024ull; constexpr uint64_t PAGE_TABLE_GRANULARITY = 2ull * 1024ull * 1024ull; constexpr int PAGE_TABLE_POOL_ENTRIES = static_cast(PAGE_TABLE_POOL_SIZE / PAGE_TABLE_GRANULARITY); constexpr uint64_t DEFAULT_FLEXIBLE_MEMORY_SIZE = 4ull * 1024ull * 1024ull * 1024ull; static uint64_t g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE; static Graphics::GpuResourceManager* g_gpu_resources = nullptr; static Graphics::GpuResourceManager& GetGpuResources() { EXIT_IF(g_gpu_resources == nullptr); return *g_gpu_resources; } static bool IsGpuAddressRange(uint64_t vaddr, uint64_t size) { constexpr uint64_t GPU_ADDRESS_LIMIT = 1ull << 40u; return vaddr != 0 && size != 0 && vaddr < GPU_ADDRESS_LIMIT && size <= GPU_ADDRESS_LIMIT - vaddr; } static void MapGpuRange(uint64_t vaddr, uint64_t size, GpuAccessMode mode) { if (mode == GpuAccessMode::NoAccess) { return; } if (!IsGpuAddressRange(vaddr, size)) { EXIT("invalid GPU map range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } const auto access = mode == GpuAccessMode::Read ? Graphics::GpuAccess::Read : mode == GpuAccessMode::Write ? Graphics::GpuAccess::Write : Graphics::GpuAccess::ReadWrite; GetGpuResources().MapMemory(vaddr, size, access); } static void UnmapGpuRange(uint64_t vaddr, uint64_t size, GpuAccessMode mode) { if (mode == GpuAccessMode::NoAccess) { return; } if (!IsGpuAddressRange(vaddr, size)) { EXIT("invalid GPU unmap range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } const auto access = mode == GpuAccessMode::Read ? Graphics::GpuAccess::Read : mode == GpuAccessMode::Write ? Graphics::GpuAccess::Write : Graphics::GpuAccess::ReadWrite; GetGpuResources().UnmapMemory(vaddr, size, access); } static bool DecodeMemoryProtection(int prot, VirtualMemory::Mode* mode, GpuAccessMode* gpu_mode) { EXIT_IF(mode == nullptr); EXIT_IF(gpu_mode == nullptr); bool cpu_read = (prot & PROT_CPU_READ) != 0; bool cpu_write = (prot & PROT_CPU_WRITE) != 0; bool cpu_exec = (prot & PROT_CPU_EXEC) != 0; bool gpu_read = (prot & PROT_GPU_READ) != 0; bool gpu_write = (prot & PROT_GPU_WRITE) != 0; if ((prot & (PROT_CPU_READ | PROT_CPU_WRITE | PROT_CPU_EXEC | PROT_GPU_READ | PROT_GPU_WRITE)) == 0 && prot != 0) { return false; } if (gpu_read && gpu_write) { *gpu_mode = GpuAccessMode::ReadWrite; } else if (gpu_read) { *gpu_mode = GpuAccessMode::Read; } else if (gpu_write) { *gpu_mode = GpuAccessMode::Write; } else { *gpu_mode = GpuAccessMode::NoAccess; } bool host_read = cpu_read || gpu_read; bool host_write = cpu_write || gpu_write; if (host_write) { host_read = true; } *mode = VirtualMemory::Mode::NoAccess; if (cpu_exec) { *mode = (host_write ? VirtualMemory::Mode::ExecuteReadWrite : (host_read ? VirtualMemory::Mode::ExecuteRead : VirtualMemory::Mode::Execute)); } else if (host_write) { *mode = VirtualMemory::Mode::ReadWrite; } else if (host_read) { *mode = VirtualMemory::Mode::Read; } return true; } static GpuAccessMode GetGpuAccessMode(int prot) { VirtualMemory::Mode mode {}; GpuAccessMode gpu_mode {}; if (!DecodeMemoryProtection(prot, &mode, &gpu_mode)) { EXIT("unsupported GPU memory protection: 0x%08x\n", prot); } return gpu_mode; } static bool ProtectCommittedHostMemory(uint64_t start, uint64_t size, VirtualMemory::Mode mode, VirtualMemory::Mode* old_mode) { if (size == 0) { if (old_mode != nullptr) { *old_mode = VirtualMemory::Mode::NoAccess; } return true; } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS const auto end = start + size; auto cur = start; bool protected_any = false; while (cur < end) { MEMORY_BASIC_INFORMATION mbi {}; if (VirtualQuery(reinterpret_cast(cur), &mbi, sizeof(mbi)) == 0) { return false; } const auto region_start = reinterpret_cast(mbi.BaseAddress); const auto region_end = region_start + static_cast(mbi.RegionSize); const auto chunk_start = std::max(cur, region_start); const auto chunk_end = std::min(end, region_end); if (chunk_end <= chunk_start) { return false; } if (mbi.State == MEM_COMMIT) { VirtualMemory::Mode chunk_old {}; if (!VirtualMemory::Protect(chunk_start, chunk_end - chunk_start, mode, &chunk_old)) { return false; } if (!protected_any && old_mode != nullptr) { *old_mode = chunk_old; } protected_any = true; } cur = chunk_end; } if (!protected_any && old_mode != nullptr) { *old_mode = VirtualMemory::Mode::NoAccess; } return true; #else return VirtualMemory::Protect(start, size, mode, old_mode); #endif } static void CopyVirtualRangeName(char* dst, const char* name) { EXIT_IF(dst == nullptr); std::memset(dst, 0, KERNEL_MAXIMUM_NAME_LENGTH); if (name != nullptr) { std::strncpy(dst, name, KERNEL_MAXIMUM_NAME_LENGTH - 1); } } static bool VirtualRangesOverlap(uint64_t left_start, uint64_t left_size, uint64_t right_start, uint64_t right_size) { if (left_size == 0 || right_size == 0) { return false; } auto left_end = (UINT64_MAX - left_start < left_size ? UINT64_MAX : left_start + left_size); auto right_end = (UINT64_MAX - right_start < right_size ? UINT64_MAX : right_start + right_size); return left_start < right_end && right_start < left_end; } static bool CommitFixedHostRange(uint64_t start, uint64_t size, VirtualMemory::Mode mode) { constexpr uint64_t PAGE_SIZE = 0x4000; #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS // Prefer one syscall, then fall back to the per-page path. if (size > PAGE_SIZE && VirtualMemory::AllocFixed(start, size, mode)) { return true; } #endif for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(addr), &info, sizeof(info)) != 0) { if (info.State == MEM_COMMIT) { if (!VirtualMemory::Protect(addr, PAGE_SIZE, mode)) { return false; } continue; } if (info.State == MEM_RESERVE) { if (!VirtualMemory::Commit(addr, PAGE_SIZE, mode)) { return false; } continue; } } #endif if (VirtualMemory::AllocFixed(addr, PAGE_SIZE, mode)) { continue; } if (VirtualMemory::Commit(addr, PAGE_SIZE, mode)) { continue; } if (VirtualMemory::Protect(addr, PAGE_SIZE, mode)) { continue; } return false; } return true; } #include "memoryAddressSpace.inc" enum class VirtualRangeType { Reserved, PoolReserved, Direct, Flexible, Pooled, Stack, Code, }; static bool IsReservedRangeType(VirtualRangeType type) { return type == VirtualRangeType::Reserved || type == VirtualRangeType::PoolReserved; } static bool IsPooledRangeType(VirtualRangeType type) { return type == VirtualRangeType::Pooled || type == VirtualRangeType::PoolReserved; } static bool IsCommittedRangeType(VirtualRangeType type) { return !IsReservedRangeType(type); } class VirtualRanges { public: struct Range { uint64_t start = 0; uint64_t size = 0; uint64_t offset = 0; int protection = 0; int memory_type = 0; VirtualRangeType type = VirtualRangeType::Reserved; bool committed_from_reserved = false; // Reserved placeholders need OS-specific release/commit paths; plain reserves still use // Common::VirtualMemory. bool placeholder_backed = false; char name[KERNEL_MAXIMUM_NAME_LENGTH]; }; bool Add(uint64_t start, uint64_t size, uint64_t offset, int protection, int memory_type, VirtualRangeType type, const char* name, bool committed_from_reserved = false, bool placeholder_backed = false) { Common::LockGuard lock(m_mutex); if (start == 0 || size == 0) { return false; } auto position = LowerBound(start); if ((position != m_ranges.end() && VirtualRangesOverlap(start, size, position->start, position->size)) || (position != m_ranges.begin() && VirtualRangesOverlap(start, size, std::prev(position)->start, std::prev(position)->size))) { return false; } Range r {}; r.start = start; r.size = size; r.offset = offset; r.protection = protection; r.memory_type = memory_type; r.type = type; r.committed_from_reserved = committed_from_reserved; r.placeholder_backed = placeholder_backed; CopyVirtualRangeName(r.name, name); const auto index = static_cast(position - m_ranges.begin()); m_ranges.insert(position, r); MergeAroundUnlocked(index); return true; } bool Remove(uint64_t start, uint64_t size) { Common::LockGuard lock(m_mutex); auto position = LowerBound(start); if (position != m_ranges.end() && position->start == start && position->size == size) { m_ranges.erase(position); return true; } auto removed = RemoveUnlocked(start, size); MergeUnlocked(); return removed; } bool HasOverlap(uint64_t start, uint64_t size) { Common::LockGuard lock(m_mutex); return FindOverlap(start, size) != nullptr; } bool HasGpuAccess(uint64_t start, uint64_t size) { Common::LockGuard lock(m_mutex); return std::any_of(m_ranges.begin(), m_ranges.end(), [start, size](const auto& range) { return VirtualRangesOverlap(start, size, range.start, range.size) && (range.protection & (PROT_GPU_READ | PROT_GPU_WRITE)) != 0; }); } bool ReleaseReserved(uint64_t start, uint64_t size) { Common::LockGuard lock(m_mutex); for (size_t index = 0; index < m_ranges.size(); index++) { auto& r = m_ranges[index]; if (r.start == start && r.size == size && IsReservedRangeType(r.type)) { m_ranges.erase(m_ranges.begin() + static_cast(index)); return VirtualMemory::Free(start); } } return true; } bool ConsumeReserved(uint64_t start, uint64_t size, VirtualRangeType type = VirtualRangeType::Reserved) { Common::LockGuard lock(m_mutex); auto end = End(start, size); for (const auto& r: m_ranges) { if (r.type == type && start >= r.start && end <= End(r.start, r.size)) { RemoveUnlocked(start, size); MergeUnlocked(); return true; } } return false; } bool ConsumeReservedSpan(uint64_t start, uint64_t size, Range* first_range = nullptr, VirtualRangeType type = VirtualRangeType::Reserved) { Common::LockGuard lock(m_mutex); if (size == 0) { return false; } auto current = start; auto end = End(start, size); while (current < end) { const Range* candidate = nullptr; for (const auto& r: m_ranges) { if (r.type == type && current >= r.start && current < End(r.start, r.size)) { candidate = &r; break; } } if (candidate == nullptr) { return false; } if (current == start && first_range != nullptr) { *first_range = *candidate; } current = std::min(end, End(candidate->start, candidate->size)); } RemoveUnlocked(start, size); MergeUnlocked(); return true; } void Rename(uint64_t start, uint64_t size, const char* name) { Common::LockGuard lock(m_mutex); auto position = LowerBound(start); if (position != m_ranges.end() && position->start == start && position->size == size) { CopyVirtualRangeName(position->name, name); MergeAroundUnlocked(static_cast(position - m_ranges.begin())); return; } EditUnlocked(start, size, [name](Range* r) { CopyVirtualRangeName(r->name, name); }); } void Protect(uint64_t start, uint64_t size, int protection) { Common::LockGuard lock(m_mutex); EditUnlocked(start, size, [protection](Range* r) { r->protection = protection; }); } void SetMemoryType(uint64_t start, uint64_t size, int memory_type) { Common::LockGuard lock(m_mutex); EditUnlocked(start, size, [memory_type](Range* r) { r->memory_type = memory_type; }); } bool Query(uint64_t addr, int flags, Range* out) { EXIT_IF(out == nullptr); Common::LockGuard lock(m_mutex); auto next = std::upper_bound( m_ranges.begin(), m_ranges.end(), addr, [](uint64_t value, const Range& range) { return value < range.start; }); if (next != m_ranges.begin()) { auto current = std::prev(next); if (addr < End(current->start, current->size)) { *out = *current; return true; } } if (flags != 1 || next == m_ranges.end()) { return false; } *out = *next; return true; } bool QuerySpan(uint64_t start, uint64_t size, std::vector* out) { EXIT_IF(out == nullptr); Common::LockGuard lock(m_mutex); out->clear(); if (start == 0 || size == 0 || size > UINT64_MAX - start) { return false; } const auto end = start + size; auto current = start; for (const auto& range: m_ranges) { const auto range_end = End(range.start, range.size); if (range_end <= current) { continue; } if (range.start > current) { break; } Range part = range; part.start = current; part.size = std::min(end, range_end) - current; if (part.type == VirtualRangeType::Direct) { part.offset += current - range.start; } out->push_back(part); current += part.size; if (current == end) { return true; } } out->clear(); return false; } uint64_t CountPageTableEntries(bool gpu) { Common::LockGuard lock(m_mutex); uint64_t used = 0; for (const auto& r: m_ranges) { if (!IsCommittedRangeType(r.type) || r.size == 0) { continue; } const bool has_cpu_access = (r.protection & (PROT_CPU_READ | PROT_CPU_WRITE | PROT_CPU_EXEC)) != 0; const bool has_gpu_access = (r.protection & (PROT_GPU_READ | PROT_GPU_WRITE)) != 0; if (gpu ? !has_gpu_access : !has_cpu_access) { continue; } const auto end = End(r.start, r.size); const auto first_entry = r.start / PAGE_TABLE_GRANULARITY; const auto last_entry = (end - 1) / PAGE_TABLE_GRANULARITY; used += last_entry - first_entry + 1; } return used; } private: static uint64_t End(uint64_t start, uint64_t size) { return (UINT64_MAX - start < size ? UINT64_MAX : start + size); } static bool SameMergeKey(const Range& left, const Range& right) { if (left.type == VirtualRangeType::Direct || right.type == VirtualRangeType::Direct) { return false; } return left.type == right.type && left.protection == right.protection && left.memory_type == right.memory_type && left.committed_from_reserved == right.committed_from_reserved && left.placeholder_backed == right.placeholder_backed && std::strncmp(left.name, right.name, KERNEL_MAXIMUM_NAME_LENGTH) == 0; } static void AddPiece(std::vector* ranges, const Range& source, uint64_t start, uint64_t end) { EXIT_IF(ranges == nullptr); if (end <= start) { return; } Range piece = source; piece.start = start; piece.size = end - start; if (piece.type == VirtualRangeType::Direct) { piece.offset += start - source.start; } ranges->push_back(piece); } std::vector::iterator LowerBound(uint64_t start) { return std::lower_bound( m_ranges.begin(), m_ranges.end(), start, [](const Range& range, uint64_t value) { return range.start < value; }); } void MergeAroundUnlocked(size_t index) { if (index >= m_ranges.size()) { return; } if (index != 0) { auto& previous = m_ranges[index - 1]; auto& current = m_ranges[index]; if (End(previous.start, previous.size) == current.start && SameMergeKey(previous, current)) { previous.size += current.size; m_ranges.erase(m_ranges.begin() + static_cast(index)); index--; } } while (index + 1 < m_ranges.size()) { auto& current = m_ranges[index]; auto& next = m_ranges[index + 1]; if (End(current.start, current.size) != next.start || !SameMergeKey(current, next)) { break; } current.size += next.size; m_ranges.erase(m_ranges.begin() + static_cast(index + 1)); } } template void EditUnlocked(uint64_t start, uint64_t size, EditFunc edit) { if (size == 0) { return; } std::vector out; auto edit_end = End(start, size); for (const auto& r: m_ranges) { auto r_end = End(r.start, r.size); if (!VirtualRangesOverlap(start, size, r.start, r.size)) { out.push_back(r); continue; } auto mid_start = std::max(start, r.start); auto mid_end = std::min(edit_end, r_end); AddPiece(&out, r, r.start, mid_start); Range mid = r; mid.start = mid_start; mid.size = mid_end - mid_start; if (mid.type == VirtualRangeType::Direct) { mid.offset += mid_start - r.start; } edit(&mid); out.push_back(mid); AddPiece(&out, r, mid_end, r_end); } m_ranges = out; MergeUnlocked(); } bool RemoveUnlocked(uint64_t start, uint64_t size) { if (size == 0) { return false; } std::vector out; bool removed = false; auto rem_end = End(start, size); for (const auto& r: m_ranges) { auto r_end = End(r.start, r.size); if (!VirtualRangesOverlap(start, size, r.start, r.size)) { out.push_back(r); continue; } removed = true; AddPiece(&out, r, r.start, std::max(start, r.start)); AddPiece(&out, r, std::min(rem_end, r_end), r_end); } m_ranges = out; return removed; } void MergeUnlocked() { if (m_ranges.size() < 2) { return; } std::sort(m_ranges.begin(), m_ranges.end(), [](const Range& left, const Range& right) { return left.start < right.start; }); std::vector merged; for (const auto& r: m_ranges) { if (!merged.empty()) { auto& last = merged[merged.size() - 1]; if (End(last.start, last.size) == r.start && SameMergeKey(last, r)) { last.size += r.size; continue; } } merged.push_back(r); } m_ranges = merged; } Range* FindOverlap(uint64_t start, uint64_t size) { auto position = LowerBound(start); if (position != m_ranges.end() && VirtualRangesOverlap(start, size, position->start, position->size)) { return &*position; } if (position != m_ranges.begin()) { auto previous = std::prev(position); if (VirtualRangesOverlap(start, size, previous->start, previous->size)) { return &*previous; } } return nullptr; } std::vector m_ranges; Common::Mutex m_mutex; }; #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) static uint32_t g_test_physical_memory_unmaps_before_failure = UINT32_MAX; static uint32_t g_test_host_reservation_pages_before_failure = UINT32_MAX; static bool g_test_fail_next_fixed_reserve_range_add = false; #endif class PhysicalMemory { public: struct AllocatedBlock { uint64_t start_addr; uint64_t size; uint64_t map_vaddr; uint64_t map_size; uint64_t host_vaddr; uint64_t host_size; int prot; VirtualMemory::Mode mode; GpuAccessMode gpu_mode; int memory_type; bool pool_expansion; char name[KERNEL_MAXIMUM_NAME_LENGTH]; }; PhysicalMemory() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); m_free.emplace(0, Size()); } virtual ~PhysicalMemory() { KYTY_NOT_IMPLEMENTED; } KYTY_CLASS_NO_COPY(PhysicalMemory); static uint64_t Size() { return static_cast(13824) * 1024 * 1024; } bool Alloc(uint64_t search_start, uint64_t search_end, size_t len, size_t alignment, uint64_t* phys_addr_out, int memory_type, bool pool_expansion = false); bool Available(uint64_t search_start, uint64_t search_end, size_t alignment, uint64_t* phys_addr_out, uint64_t* size_out); bool Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size, GpuAccessMode* gpu_mode); bool Map(uint64_t vaddr, uint64_t phys_addr, size_t len, int prot, VirtualMemory::Mode mode, GpuAccessMode gpu_mode); bool Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode, uint64_t* host_vaddr_to_release = nullptr); bool Find(uint64_t vaddr, uint64_t* base_addr, size_t* len, int* prot, VirtualMemory::Mode* mode, GpuAccessMode* gpu_mode); bool Find(uint64_t phys_addr, bool next, PhysicalMemory::AllocatedBlock* out); bool CanMapDirect(uint64_t phys_addr, size_t len); bool ReleasePoolExpansion(uint64_t phys_addr, size_t len); std::vector FindMappings(uint64_t phys_addr, size_t len); void SetVirtualRangeName(uint64_t vaddr, uint64_t len, const char* name); void SetVirtualRangeMemoryType(uint64_t vaddr, uint64_t len, int memory_type); [[nodiscard]] Common::Mutex& GetMutex() { return m_mutex; } [[nodiscard]] const std::map& GetPhysicalBlocks() const { return m_physical; } [[nodiscard]] const std::vector& GetMappings() const { return m_mappings; } private: void ConsumeFreeRange(std::map::iterator range, uint64_t start, uint64_t size); void AddFreeRange(uint64_t start, uint64_t size); std::map m_physical; std::map m_free; std::vector m_mappings; Common::Mutex m_mutex; }; class FlexibleMemory { public: struct AllocatedBlock { uint64_t map_vaddr; uint64_t map_size; uint64_t host_vaddr; uint64_t host_size; int prot; VirtualMemory::Mode mode; GpuAccessMode gpu_mode; char name[KERNEL_MAXIMUM_NAME_LENGTH]; }; FlexibleMemory() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); } virtual ~FlexibleMemory() { KYTY_NOT_IMPLEMENTED; } KYTY_CLASS_NO_COPY(FlexibleMemory); static uint64_t Size() { return g_flexible_memory_size; } uint64_t Available(); bool Map(uint64_t vaddr, size_t len, int prot, VirtualMemory::Mode mode, GpuAccessMode gpu_mode, const char* name); bool Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode, uint64_t* host_vaddr_to_release = nullptr); bool Find(uint64_t vaddr, uint64_t* base_addr, size_t* len, int* prot, VirtualMemory::Mode* mode, GpuAccessMode* gpu_mode); void SetVirtualRangeName(uint64_t vaddr, uint64_t len, const char* name); [[nodiscard]] Common::Mutex& GetMutex() { return m_mutex; } [[nodiscard]] const std::vector& GetBlocks() const { return m_allocated; } private: std::vector m_allocated; uint64_t m_allocated_total = 0; Common::Mutex m_mutex; }; class PooledMemory { public: struct Mapping { uint64_t vaddr; uint64_t size; uint64_t phys_addr; GpuAccessMode gpu_mode; }; void Expand(uint64_t phys_addr, uint64_t size); bool ReleaseExpansion(uint64_t phys_addr, uint64_t size); bool Allocate(uint64_t vaddr, uint64_t size, GpuAccessMode gpu_mode, std::vector* mappings); bool Query(uint64_t vaddr, uint64_t size, std::vector* mappings); bool Release(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode); [[nodiscard]] uint64_t Available(); [[nodiscard]] std::vector GetMappings(); private: struct PhysicalRange { uint64_t start; uint64_t size; }; void AddFreeUnlocked(uint64_t start, uint64_t size); bool QueryUnlocked(uint64_t vaddr, uint64_t size, std::vector* mappings) const; std::vector m_free; std::vector m_expansions; std::vector m_mappings; Common::Mutex m_mutex; }; static PhysicalMemory* g_physical_memory = nullptr; static FlexibleMemory* g_flexible_memory = nullptr; static PooledMemory* g_pooled_memory = nullptr; static VirtualRanges* g_virtual_ranges = nullptr; static DirectMemoryBacking* g_direct_memory_backing = nullptr; static PlaceholderAddressSpace* g_placeholder_address_space = nullptr; static callback_func_t g_alloc_callback = nullptr; static callback_func_t g_free_callback = nullptr; static std::atomic g_memory_pool_committed = 0; static void MemoryPoolSubtractCommitted(uint64_t len); // Keep host mappings, physical blocks, placeholders, and virtual ranges in step. static std::recursive_mutex g_memory_operation_mutex; bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) { return g_direct_memory_backing != nullptr && g_direct_memory_backing->TryWriteBacking(vaddr, data, size); } bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) { return g_direct_memory_backing != nullptr && g_direct_memory_backing->TryReadBacking(vaddr, data, size); } void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept { if (!TryWriteBacking(vaddr, data, size)) { EXIT("Memory: required direct-backing write failed, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } } void PrepareHostWrite(uint64_t vaddr, uint64_t size) { if (size == 0) { return; } GetGpuResources().PrepareHostWrite(vaddr, size); } void InstallGpuResources(Graphics::GpuResourceManager* resources) noexcept { EXIT_IF(resources != nullptr && g_gpu_resources != nullptr); g_gpu_resources = resources; } bool HandleGpuFault(Graphics::PageFaultAccess access, uint64_t fault_vaddr) noexcept { return g_gpu_resources != nullptr && g_gpu_resources->HandleFault(access, fault_vaddr); } struct PrtAperture { uint64_t address = 0; uint64_t size = 0; }; constexpr int PRT_APERTURE_MAX_INDEX = 2; constexpr uint64_t PRT_PAGE_SIZE = 0x4000; constexpr uint64_t PRT_APERTURE_START = 0x0f00000000ull; constexpr uint64_t PRT_APERTURE_END = 0xfc00000000ull; static std::array g_prt_apertures {}; static Common::Mutex g_prt_aperture_mutex; static bool IsInPrtAperture(uint64_t address) { Common::LockGuard lock(g_prt_aperture_mutex); for (const auto& aperture: g_prt_apertures) { if (aperture.size != 0 && address >= aperture.address && address < aperture.address + aperture.size) { return true; } } return false; } static void SelfTestSub64SharedPlaceholderAlias() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS constexpr uint64_t PageSize = 0x4000; const auto granularity = g_placeholder_address_space->GetGranularity(); if (granularity < PageSize * 2u) { LOGF_COLOR( Log::Color::Yellow, "\t direct-memory sub-64K placeholder self-test skipped: granularity too small\n"); return; } const auto base = g_placeholder_address_space->ReserveAligned(0, granularity, granularity); if (base == 0) { LOGF_COLOR(Log::Color::Yellow, "\t direct-memory sub-64K placeholder self-test skipped: reserve unavailable\n"); return; } const auto alias = base + PageSize; bool ok = false; auto failure_reason = DirectMemoryBacking::FailureReason::None; const bool consumed = g_placeholder_address_space->Consume(alias, PageSize); if (consumed && g_direct_memory_backing->MapExistingPlaceholderFixed( alias, PageSize, PageSize, VirtualMemory::Mode::ReadWrite, &failure_reason)) { auto* ptr = reinterpret_cast(alias); *ptr = 0x4b59545953553634ull; // "KYTYSU64" ok = (*ptr == 0x4b59545953553634ull); std::memset(ptr, 0, PageSize); bool placeholder_preserved = false; ok = g_direct_memory_backing->Unmap(alias, PageSize, true, &placeholder_preserved) && placeholder_preserved && ok; if (placeholder_preserved) { g_placeholder_address_space->AddFree(alias, PageSize); } } else if (consumed) { g_placeholder_address_space->AddFree(alias, PageSize); } const bool released = g_placeholder_address_space->ReleaseFree(base, granularity); LOGF_COLOR(ok && released ? Log::Color::Green : Log::Color::Red, "\t direct-memory sub-64K placeholder self-test: %s%s%s\n", ok && released ? "ok" : "failed", ok ? "" : ", reason = ", ok ? "" : DirectMemoryBacking::GetFailureReasonName(failure_reason)); #endif } static bool RestoreCommittedPlaceholderOrProtect(uint64_t vaddr, uint64_t size) { if (g_placeholder_address_space->ReleaseCommitted(vaddr, size)) { return true; } VirtualMemory::Protect(vaddr, size, VirtualMemory::Mode::NoAccess); return false; } static bool ReleaseReservedRange(uint64_t vaddr, uint64_t size) { VirtualRanges::Range range {}; if (g_virtual_ranges->Query(vaddr, 0, &range) && range.start == vaddr && range.size == size && IsReservedRangeType(range.type) && range.placeholder_backed) { if (!g_virtual_ranges->ConsumeReserved(vaddr, size, range.type)) { return false; } if (g_placeholder_address_space->ReleaseFree(vaddr, size)) { return true; } if (VirtualMemory::Free(vaddr)) { return true; } g_virtual_ranges->Add(vaddr, size, 0, 0, 0, range.type, range.name, false, true); return false; } return g_virtual_ranges->ReleaseReserved(vaddr, size); } static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* placeholder_backed); KYTY_SUBSYSTEM_INIT(Memory) { g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE; g_physical_memory = new PhysicalMemory; g_flexible_memory = new FlexibleMemory; g_pooled_memory = new PooledMemory; g_virtual_ranges = new VirtualRanges; g_direct_memory_backing = new DirectMemoryBacking(PhysicalMemory::Size()); g_placeholder_address_space = new PlaceholderAddressSpace; VirtualMemory::Init(); EXIT_IF(!g_direct_memory_backing->SelfTest()); g_placeholder_address_space->SelfTest(); SelfTestSub64SharedPlaceholderAlias(); } KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Memory) {} KYTY_SUBSYSTEM_DESTROY(Memory) { delete g_placeholder_address_space; g_placeholder_address_space = nullptr; delete g_direct_memory_backing; g_direct_memory_backing = nullptr; delete g_pooled_memory; g_pooled_memory = nullptr; } struct AlignedPos { uint64_t value = 0; bool valid = false; }; static constexpr AlignedPos GetAlignedPos(uint64_t pos, size_t alignment) { if (alignment == 0) { return {pos, true}; } const auto remainder = pos % alignment; const auto increment = (remainder != 0 ? alignment - remainder : 0); if (increment > UINT64_MAX - pos) { return {}; } return {pos + increment, true}; } static_assert(!GetAlignedPos(UINT64_MAX - 1, 4).valid); void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func) { EXIT_IF(g_alloc_callback != nullptr || g_free_callback != nullptr); EXIT_IF(alloc_func == nullptr || free_func == nullptr); std::lock_guard memory_operation_lock(g_memory_operation_mutex); g_alloc_callback = alloc_func; g_free_callback = free_func; g_physical_memory->GetMutex().Lock(); for (const auto& b: g_physical_memory->GetMappings()) { if (b.map_vaddr != 0 && b.map_size != 0) { g_alloc_callback(b.map_vaddr, b.map_size); } } g_physical_memory->GetMutex().Unlock(); g_flexible_memory->GetMutex().Lock(); for (const auto& b: g_flexible_memory->GetBlocks()) { g_alloc_callback(b.map_vaddr, b.map_size); } g_flexible_memory->GetMutex().Unlock(); for (const auto& mapping: g_pooled_memory->GetMappings()) { g_alloc_callback(mapping.vaddr, mapping.size); } } void SetFlexibleMemorySize(uint64_t size) { g_flexible_memory_size = size; LOGF("\t flexible memory size = 0x%016" PRIx64 " (%" PRIu64 " MiB)\n", size, size / (1024ull * 1024ull)); } bool PhysicalMemory::Alloc(uint64_t search_start, uint64_t search_end, size_t len, size_t alignment, uint64_t* phys_addr_out, int memory_type, bool pool_expansion) { if (phys_addr_out == nullptr) { return false; } Common::LockGuard lock(m_mutex); search_end = std::min(search_end, Size()); if (search_start >= search_end) { return false; } auto range = m_free.upper_bound(search_start); if (range != m_free.begin()) { range--; } for (; range != m_free.end() && range->first < search_end; ++range) { const auto range_end = std::min(range->first + range->second, search_end); const auto lower_bound = std::max(range->first, search_start); const auto aligned = GetAlignedPos(lower_bound, alignment); const auto free_pos = aligned.value; if (!aligned.valid || free_pos < lower_bound || free_pos > range_end || len > range_end - free_pos) { continue; } AllocatedBlock b {}; b.size = len; b.start_addr = free_pos; b.gpu_mode = GpuAccessMode::NoAccess; b.map_size = 0; b.map_vaddr = 0; b.prot = 0; b.mode = VirtualMemory::Mode::NoAccess; b.memory_type = memory_type; b.pool_expansion = pool_expansion; ConsumeFreeRange(range, free_pos, len); EXIT_IF(!m_physical.emplace(b.start_addr, b).second); *phys_addr_out = free_pos; return true; } return false; } bool PhysicalMemory::Available(uint64_t search_start, uint64_t search_end, size_t alignment, uint64_t* phys_addr_out, uint64_t* size_out) { if (phys_addr_out == nullptr || size_out == nullptr) { return false; } Common::LockGuard lock(m_mutex); search_end = std::min(search_end, Size()); if (search_start >= search_end) { return false; } uint64_t best_addr = 0; uint64_t best_size = 0; for (const auto& [range_start, range_size]: m_free) { if (range_start >= search_end) { break; } const auto range_end = std::min(range_start + range_size, search_end); const auto lower_bound = std::max(range_start, search_start); const auto aligned = GetAlignedPos(lower_bound, alignment); const auto free_pos = aligned.value; if (aligned.valid && free_pos >= lower_bound && free_pos < range_end && range_end - free_pos > best_size) { best_addr = free_pos; best_size = range_end - free_pos; } } if (best_size == 0) { return false; } *phys_addr_out = best_addr; *size_out = best_size; return true; } void PhysicalMemory::ConsumeFreeRange(std::map::iterator range, uint64_t start, uint64_t size) { const auto range_start = range->first; const auto range_end = range->first + range->second; m_free.erase(range); if (range_start < start) { m_free.emplace(range_start, start - range_start); } if (start + size < range_end) { m_free.emplace(start + size, range_end - start - size); } } void PhysicalMemory::AddFreeRange(uint64_t start, uint64_t size) { auto end = start + size; auto next = m_free.lower_bound(start); if (next != m_free.begin()) { auto previous = std::prev(next); if (previous->first + previous->second >= start) { start = previous->first; end = std::max(end, previous->first + previous->second); next = m_free.erase(previous); } } while (next != m_free.end() && next->first <= end) { end = std::max(end, next->first + next->second); next = m_free.erase(next); } m_free.emplace(start, end - start); } bool PhysicalMemory::Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size, GpuAccessMode* gpu_mode) { EXIT_IF(vaddr == nullptr); EXIT_IF(size == nullptr); EXIT_IF(gpu_mode == nullptr); Common::LockGuard lock(m_mutex); auto next = m_physical.upper_bound(start); if (next == m_physical.begin()) { return false; } auto it = std::prev(next); auto& b = it->second; if (b.pool_expansion || start < b.start_addr || start >= b.start_addr + b.size || len > b.start_addr + b.size - start) { return false; } if (start == b.start_addr && len == b.size) { *vaddr = b.map_vaddr; *size = b.map_size; *gpu_mode = b.gpu_mode; m_physical.erase(it); AddFreeRange(start, len); return true; } if (start > b.start_addr && start + len < b.start_addr + b.size) { auto old_start = b.start_addr; auto old_end = b.start_addr + b.size; *vaddr = (b.map_vaddr != 0 ? b.map_vaddr + (start - old_start) : 0); *size = (b.map_vaddr != 0 ? len : 0); *gpu_mode = b.gpu_mode; AllocatedBlock right = b; right.start_addr = start + len; right.size = old_end - right.start_addr; if (right.map_vaddr != 0) { right.map_vaddr += right.start_addr - old_start; right.map_size = right.size; } b.size = start - old_start; if (b.map_vaddr != 0) { b.map_size = b.size; } m_physical.emplace(right.start_addr, right); AddFreeRange(start, len); return true; } if (start == b.start_addr && len < b.size) { *vaddr = b.map_vaddr; *size = (b.map_vaddr != 0 ? len : 0); *gpu_mode = b.gpu_mode; AllocatedBlock remaining = b; m_physical.erase(it); remaining.start_addr += len; remaining.size -= len; if (remaining.map_vaddr != 0) { remaining.map_vaddr += len; remaining.map_size -= len; } m_physical.emplace(remaining.start_addr, remaining); AddFreeRange(start, len); return true; } if (start > b.start_addr && start + len == b.start_addr + b.size) { *vaddr = (b.map_vaddr != 0 ? b.map_vaddr + (start - b.start_addr) : 0); *size = (b.map_vaddr != 0 ? len : 0); *gpu_mode = b.gpu_mode; b.size = start - b.start_addr; if (b.map_vaddr != 0) { b.map_size = b.size; } AddFreeRange(start, len); return true; } return false; } bool PhysicalMemory::Map(uint64_t vaddr, uint64_t phys_addr, size_t len, int prot, VirtualMemory::Mode mode, GpuAccessMode gpu_mode) { Common::LockGuard lock(m_mutex); auto next = m_physical.upper_bound(phys_addr); if (next == m_physical.begin()) { return false; } const auto& block = std::prev(next)->second; if (block.pool_expansion || phys_addr < block.start_addr || phys_addr >= block.start_addr + block.size || len > block.start_addr + block.size - phys_addr) { return false; } AllocatedBlock mapping = block; mapping.start_addr = phys_addr; mapping.size = len; mapping.map_vaddr = vaddr; mapping.map_size = len; mapping.host_vaddr = vaddr; mapping.host_size = len; mapping.prot = prot; mapping.mode = mode; mapping.gpu_mode = gpu_mode; m_mappings.push_back(mapping); return true; } bool PhysicalMemory::CanMapDirect(uint64_t phys_addr, size_t len) { Common::LockGuard lock(m_mutex); auto next = m_physical.upper_bound(phys_addr); if (next == m_physical.begin()) { return false; } const auto& block = std::prev(next)->second; return !block.pool_expansion && phys_addr >= block.start_addr && phys_addr < block.start_addr + block.size && len <= block.start_addr + block.size - phys_addr; } bool PhysicalMemory::ReleasePoolExpansion(uint64_t phys_addr, size_t len) { Common::LockGuard lock(m_mutex); const auto it = m_physical.find(phys_addr); if (it == m_physical.end() || !it->second.pool_expansion || it->second.size != len) { return false; } m_physical.erase(it); AddFreeRange(phys_addr, len); return true; } bool PhysicalMemory::Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode, uint64_t* host_vaddr_to_release) { #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) if (g_test_physical_memory_unmaps_before_failure == 0) { g_test_physical_memory_unmaps_before_failure = UINT32_MAX; return false; } if (g_test_physical_memory_unmaps_before_failure != UINT32_MAX) { g_test_physical_memory_unmaps_before_failure--; } #endif EXIT_IF(gpu_mode == nullptr); Common::LockGuard lock(m_mutex); if (host_vaddr_to_release != nullptr) { *host_vaddr_to_release = 0; } auto set_host_release_if_last = [this, host_vaddr_to_release](uint64_t host_vaddr, uint64_t host_size) { if (host_vaddr_to_release == nullptr || host_vaddr == 0 || host_size == 0) { return; } const bool still_mapped = std::any_of( m_mappings.begin(), m_mappings.end(), [host_vaddr, host_size](const auto& block) { return block.host_vaddr == host_vaddr && block.host_size == host_size; }); if (!still_mapped) { *host_vaddr_to_release = host_vaddr; } }; size_t index = 0; for (auto& b: m_mappings) { if (b.map_vaddr == vaddr && b.map_size == size) { *gpu_mode = b.gpu_mode; const auto host_vaddr = b.host_vaddr; const auto host_size = b.host_size; m_mappings.erase(m_mappings.begin() + static_cast(index)); set_host_release_if_last(host_vaddr, host_size); return true; } if (vaddr > b.map_vaddr && vaddr + size < b.map_vaddr + b.map_size) { *gpu_mode = b.gpu_mode; AllocatedBlock right = b; right.start_addr += (vaddr + size) - b.map_vaddr; right.size = b.map_vaddr + b.map_size - (vaddr + size); right.map_size = right.size; right.map_vaddr = vaddr + size; b.size = vaddr - b.map_vaddr; b.map_size = b.size; m_mappings.push_back(right); return true; } if (vaddr == b.map_vaddr && size < b.map_size) { *gpu_mode = b.gpu_mode; b.start_addr += size; b.size -= size; b.map_vaddr += size; b.map_size -= size; return true; } if (vaddr > b.map_vaddr && vaddr + size == b.map_vaddr + b.map_size) { *gpu_mode = b.gpu_mode; b.size = vaddr - b.map_vaddr; b.map_size = b.size; return true; } index++; } return false; } bool PhysicalMemory::Find(uint64_t phys_addr, bool next, AllocatedBlock* out) { EXIT_IF(out == nullptr); Common::LockGuard lock(m_mutex); auto following = m_physical.upper_bound(phys_addr); if (following != m_physical.begin()) { const auto& block = std::prev(following)->second; if (phys_addr < block.start_addr + block.size) { *out = block; return true; } } if (next && following != m_physical.end()) { *out = following->second; return true; } return false; } std::vector PhysicalMemory::FindMappings(uint64_t phys_addr, size_t len) { Common::LockGuard lock(m_mutex); std::vector mappings; for (const auto& m: m_mappings) { if (!VirtualRangesOverlap(phys_addr, len, m.start_addr, m.size)) { continue; } const uint64_t overlap_start = std::max(phys_addr, m.start_addr); const uint64_t overlap_end = std::min(phys_addr + len, m.start_addr + m.size); AllocatedBlock part = m; part.start_addr = overlap_start; part.size = overlap_end - overlap_start; part.map_vaddr += overlap_start - m.start_addr; part.map_size = part.size; mappings.push_back(part); } std::sort(mappings.begin(), mappings.end(), [](const auto& a, const auto& b) { return a.map_vaddr < b.map_vaddr; }); return mappings; } bool PhysicalMemory::Find(uint64_t vaddr, uint64_t* base_addr, size_t* len, int* prot, VirtualMemory::Mode* mode, GpuAccessMode* gpu_mode) { Common::LockGuard lock(m_mutex); return std::any_of(m_mappings.begin(), m_mappings.end(), [vaddr, base_addr, len, prot, mode, gpu_mode](auto& b) { if (vaddr >= b.map_vaddr && vaddr < b.map_vaddr + b.map_size) { if (base_addr != nullptr) { *base_addr = b.map_vaddr; } if (len != nullptr) { *len = b.map_size; } if (prot != nullptr) { *prot = b.prot; } if (mode != nullptr) { *mode = b.mode; } if (gpu_mode != nullptr) { *gpu_mode = b.gpu_mode; } return true; } return false; }); } void PhysicalMemory::SetVirtualRangeName(uint64_t vaddr, uint64_t len, const char* name) { Common::LockGuard lock(m_mutex); for (auto& b: m_mappings) { if (VirtualRangesOverlap(vaddr, len, b.map_vaddr, b.map_size)) { CopyVirtualRangeName(b.name, name); } } } bool FlexibleMemory::Map(uint64_t vaddr, size_t len, int prot, VirtualMemory::Mode mode, GpuAccessMode gpu_mode, const char* name) { Common::LockGuard lock(m_mutex); const auto available = (Size() >= m_allocated_total ? Size() - m_allocated_total : 0); if (len == 0 || len > available) { LOGF_COLOR(Log::Color::Red, "\t flexible memory exhausted: configured = 0x%016" PRIx64 ", allocated = 0x%016" PRIx64 ", available = 0x%016" PRIx64 ", requested = 0x%016" PRIx64 "\n", Size(), m_allocated_total, available, static_cast(len)); return false; } AllocatedBlock b {}; b.map_vaddr = vaddr; b.map_size = len; b.host_vaddr = vaddr; b.host_size = len; b.prot = prot; b.mode = mode; b.gpu_mode = gpu_mode; CopyVirtualRangeName(b.name, name); m_allocated.push_back(b); m_allocated_total += len; return true; } bool FlexibleMemory::Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode, uint64_t* host_vaddr_to_release) { EXIT_IF(gpu_mode == nullptr); Common::LockGuard lock(m_mutex); if (host_vaddr_to_release != nullptr) { *host_vaddr_to_release = 0; } auto set_host_release_if_last = [this, host_vaddr_to_release](uint64_t host_vaddr, uint64_t host_size) { if (host_vaddr_to_release == nullptr || host_vaddr == 0 || host_size == 0) { return; } const bool still_mapped = std::any_of( m_allocated.begin(), m_allocated.end(), [host_vaddr, host_size](const auto& block) { return block.host_vaddr == host_vaddr && block.host_size == host_size; }); if (!still_mapped) { *host_vaddr_to_release = host_vaddr; } }; size_t index = 0; for (auto& b: m_allocated) { if (b.map_vaddr == vaddr && b.map_size == size) { *gpu_mode = b.gpu_mode; const auto host_vaddr = b.host_vaddr; const auto host_size = b.host_size; m_allocated.erase(m_allocated.begin() + static_cast(index)); m_allocated_total -= size; set_host_release_if_last(host_vaddr, host_size); return true; } if (vaddr > b.map_vaddr && vaddr + size < b.map_vaddr + b.map_size) { *gpu_mode = b.gpu_mode; AllocatedBlock right = b; right.map_size = b.map_vaddr + b.map_size - (vaddr + size); right.map_vaddr = vaddr + size; b.map_size = vaddr - b.map_vaddr; m_allocated.push_back(right); m_allocated_total -= size; return true; } if (vaddr == b.map_vaddr && size < b.map_size) { *gpu_mode = b.gpu_mode; b.map_vaddr += size; b.map_size -= size; m_allocated_total -= size; return true; } if (vaddr > b.map_vaddr && vaddr + size == b.map_vaddr + b.map_size) { *gpu_mode = b.gpu_mode; b.map_size = vaddr - b.map_vaddr; m_allocated_total -= size; return true; } index++; } return false; } bool FlexibleMemory::Find(uint64_t vaddr, uint64_t* base_addr, size_t* len, int* prot, VirtualMemory::Mode* mode, GpuAccessMode* gpu_mode) { Common::LockGuard lock(m_mutex); return std::any_of(m_allocated.begin(), m_allocated.end(), [vaddr, base_addr, len, prot, mode, gpu_mode](auto& b) { if (vaddr >= b.map_vaddr && vaddr < b.map_vaddr + b.map_size) { if (base_addr != nullptr) { *base_addr = b.map_vaddr; } if (len != nullptr) { *len = b.map_size; } if (prot != nullptr) { *prot = b.prot; } if (mode != nullptr) { *mode = b.mode; } if (gpu_mode != nullptr) { *gpu_mode = b.gpu_mode; } return true; } return false; }); } void FlexibleMemory::SetVirtualRangeName(uint64_t vaddr, uint64_t len, const char* name) { Common::LockGuard lock(m_mutex); for (auto& b: m_allocated) { if (VirtualRangesOverlap(vaddr, len, b.map_vaddr, b.map_size)) { CopyVirtualRangeName(b.name, name); } } } void PhysicalMemory::SetVirtualRangeMemoryType(uint64_t vaddr, uint64_t len, int memory_type) { Common::LockGuard lock(m_mutex); for (auto& b: m_mappings) { if (VirtualRangesOverlap(vaddr, len, b.map_vaddr, b.map_size)) { b.memory_type = memory_type; } } } uint64_t FlexibleMemory::Available() { Common::LockGuard lock(m_mutex); return (Size() >= m_allocated_total ? Size() - m_allocated_total : 0); } void PooledMemory::AddFreeUnlocked(uint64_t start, uint64_t size) { if (size == 0) { return; } m_free.push_back({start, size}); std::sort(m_free.begin(), m_free.end(), [](const auto& left, const auto& right) { return left.start < right.start; }); std::vector merged; for (const auto& range: m_free) { if (!merged.empty() && range.start <= merged.back().start + merged.back().size) { const auto end = std::max(merged.back().start + merged.back().size, range.start + range.size); merged.back().size = end - merged.back().start; } else { merged.push_back(range); } } m_free = std::move(merged); } void PooledMemory::Expand(uint64_t phys_addr, uint64_t size) { Common::LockGuard lock(m_mutex); m_expansions.push_back({phys_addr, size}); AddFreeUnlocked(phys_addr, size); } bool PooledMemory::ReleaseExpansion(uint64_t phys_addr, uint64_t size) { Common::LockGuard lock(m_mutex); const auto expansion = std::find_if(m_expansions.begin(), m_expansions.end(), [phys_addr, size](const auto& range) { return range.start == phys_addr && range.size == size; }); if (expansion == m_expansions.end()) { return false; } if (std::any_of(m_mappings.begin(), m_mappings.end(), [phys_addr, size](const auto& mapping) { return VirtualRangesOverlap(phys_addr, size, mapping.phys_addr, mapping.size); })) { return false; } const auto end = phys_addr + size; const auto free_range = std::find_if(m_free.begin(), m_free.end(), [phys_addr, end](const auto& r) { return phys_addr >= r.start && end <= r.start + r.size; }); if (free_range == m_free.end()) { return false; } const auto old = *free_range; const auto old_end = old.start + old.size; const auto left_size = phys_addr - old.start; const auto right_size = old_end - end; m_free.erase(free_range); if (left_size != 0) { m_free.push_back({old.start, left_size}); } if (right_size != 0) { m_free.push_back({end, right_size}); } std::sort(m_free.begin(), m_free.end(), [](const auto& left, const auto& right) { return left.start < right.start; }); m_expansions.erase(expansion); return true; } bool PooledMemory::Allocate(uint64_t vaddr, uint64_t size, GpuAccessMode gpu_mode, std::vector* mappings) { EXIT_IF(mappings == nullptr); mappings->clear(); if (vaddr == 0 || size == 0 || UINT64_MAX - vaddr < size) { return false; } Common::LockGuard lock(m_mutex); auto free = m_free; auto current = vaddr; auto remaining = size; for (auto& range: free) { if (remaining == 0 || range.size == 0) { continue; } const auto part_size = std::min(range.size, remaining); mappings->push_back({current, part_size, range.start, gpu_mode}); range.start += part_size; range.size -= part_size; current += part_size; remaining -= part_size; } if (remaining != 0) { mappings->clear(); return false; } free.erase( std::remove_if(free.begin(), free.end(), [](const auto& range) { return range.size == 0; }), free.end()); m_free = std::move(free); m_mappings.insert(m_mappings.end(), mappings->begin(), mappings->end()); return true; } bool PooledMemory::QueryUnlocked(uint64_t vaddr, uint64_t size, std::vector* mappings) const { EXIT_IF(mappings == nullptr); mappings->clear(); if (vaddr == 0 || size == 0 || UINT64_MAX - vaddr < size) { return false; } const auto end = vaddr + size; auto current = vaddr; while (current < end) { const auto it = std::find_if(m_mappings.begin(), m_mappings.end(), [current](const auto& m) { return current >= m.vaddr && current < m.vaddr + m.size; }); if (it == m_mappings.end()) { mappings->clear(); return false; } const auto part_size = std::min(end, it->vaddr + it->size) - current; mappings->push_back( {current, part_size, it->phys_addr + (current - it->vaddr), it->gpu_mode}); current += part_size; } return true; } bool PooledMemory::Query(uint64_t vaddr, uint64_t size, std::vector* mappings) { Common::LockGuard lock(m_mutex); return QueryUnlocked(vaddr, size, mappings); } bool PooledMemory::Release(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode) { EXIT_IF(gpu_mode == nullptr); Common::LockGuard lock(m_mutex); std::vector released; if (!QueryUnlocked(vaddr, size, &released)) { return false; } *gpu_mode = released.front().gpu_mode; const auto end = vaddr + size; std::vector kept; for (const auto& mapping: m_mappings) { const auto mapping_end = mapping.vaddr + mapping.size; const auto cut_start = std::max(vaddr, mapping.vaddr); const auto cut_end = std::min(end, mapping_end); if (cut_start >= cut_end) { kept.push_back(mapping); continue; } if (mapping.vaddr < cut_start) { auto left = mapping; left.size = cut_start - mapping.vaddr; kept.push_back(left); } if (cut_end < mapping_end) { auto right = mapping; right.vaddr = cut_end; right.size = mapping_end - cut_end; right.phys_addr = mapping.phys_addr + (cut_end - mapping.vaddr); kept.push_back(right); } } m_mappings = std::move(kept); for (const auto& mapping: released) { AddFreeUnlocked(mapping.phys_addr, mapping.size); } return true; } uint64_t PooledMemory::Available() { Common::LockGuard lock(m_mutex); uint64_t available = 0; for (const auto& range: m_free) { available += range.size; } return available; } std::vector PooledMemory::GetMappings() { Common::LockGuard lock(m_mutex); return m_mappings; } static bool UnmapPooledBackingTransactional(const std::vector& mappings, VirtualMemory::Mode mode) { struct RemovedMapping { PooledMemory::Mapping mapping; bool placeholder_preserved; }; std::vector removed; for (const auto& mapping: mappings) { bool placeholder_preserved = false; if (!g_direct_memory_backing->Unmap(mapping.vaddr, mapping.size, true, &placeholder_preserved)) { for (auto it = removed.rbegin(); it != removed.rend(); ++it) { auto failure_reason = DirectMemoryBacking::FailureReason::None; const bool restored = it->placeholder_preserved ? g_direct_memory_backing->MapExistingPlaceholderFixed( it->mapping.vaddr, it->mapping.size, it->mapping.phys_addr, mode, &failure_reason) : g_direct_memory_backing->MapFixed( it->mapping.vaddr, it->mapping.size, it->mapping.phys_addr, mode, &failure_reason); if (!restored) { EXIT("pooled-memory unmap rollback failed: %s\n", DirectMemoryBacking::GetFailureReasonName(failure_reason)); } } return false; } removed.push_back({mapping, placeholder_preserved}); } for (const auto& entry: removed) { if (entry.placeholder_preserved) { g_placeholder_address_space->AddFree(entry.mapping.vaddr, entry.mapping.size); } else if (!g_placeholder_address_space->ReserveFixed(entry.mapping.vaddr, entry.mapping.size)) { EXIT("failed to reserve pooled-memory placeholder\n"); } } return true; } int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags, const char* name) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); EXIT_NOT_IMPLEMENTED(addr_in_out == nullptr); constexpr size_t PAGE_SIZE = 0x4000; constexpr size_t MAXIMUM_NAME_SIZE = 32; constexpr uint64_t DEFAULT_PS5_BASE = 0x200000000; constexpr int GUEST_MAP_FIXED = 0x10; constexpr int GUEST_MAP_SHARED = 0x01; constexpr int GUEST_MAP_PRIVATE = 0x02; constexpr int GUEST_MAP_NO_OVERWRITE = 0x80; constexpr int GUEST_MAP_VOID = 0x100; constexpr int GUEST_MAP_STACK = 0x400; constexpr int GUEST_MAP_NO_SYNC = 0x800; constexpr int GUEST_MAP_ANON = 0x1000; constexpr int GUEST_MAP_UNKNOWN_8000 = 0x8000; constexpr int GUEST_MAP_NO_CORE = 0x20000; constexpr int GUEST_MAP_NO_COALESCE = 0x400000; constexpr int SUPPORTED_MAP_BITS = GUEST_MAP_SHARED | GUEST_MAP_PRIVATE | GUEST_MAP_FIXED | GUEST_MAP_NO_OVERWRITE | GUEST_MAP_VOID | GUEST_MAP_STACK | GUEST_MAP_NO_SYNC | GUEST_MAP_ANON | GUEST_MAP_UNKNOWN_8000 | GUEST_MAP_NO_CORE | GUEST_MAP_NO_COALESCE; if (len == 0 || (len & (PAGE_SIZE - 1)) != 0) { return KERNEL_ERROR_EINVAL; } if (name == nullptr) { return KERNEL_ERROR_EFAULT; } if (std::strlen(name) >= MAXIMUM_NAME_SIZE) { return KERNEL_ERROR_ENAMETOOLONG; } if ((flags & ~SUPPORTED_MAP_BITS) != 0) { LOGF_COLOR(Log::Color::Red, "\t unsupported flags = 0x%08" PRIx32 "\n", static_cast(flags & ~SUPPORTED_MAP_BITS)); return KERNEL_ERROR_EINVAL; } VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; if (!DecodeMemoryProtection(prot, &mode, &gpu_mode)) { EXIT("unknown prot: %d\n", prot); } auto in_addr = reinterpret_cast(*addr_in_out); uint64_t out_addr = 0; bool committed_from_reserved = false; bool consumed_reserved = false; VirtualRanges::Range consumed_range {}; if ((flags & GUEST_MAP_FIXED) != 0) { if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1)) != 0) { return KERNEL_ERROR_EINVAL; } if ((flags & GUEST_MAP_NO_OVERWRITE) != 0 && g_virtual_ranges->HasOverlap(in_addr, len)) { return KERNEL_ERROR_ENOMEM; } if (g_virtual_ranges->Query(in_addr, 0, &consumed_range) && consumed_range.type == VirtualRangeType::Reserved && g_virtual_ranges->ConsumeReserved(in_addr, len)) { consumed_reserved = true; if (g_placeholder_address_space->Commit(in_addr, len, mode) || CommitFixedHostRange(in_addr, len, mode)) { out_addr = in_addr; committed_from_reserved = true; } } else if (!ReleaseReservedRange(in_addr, len)) { return KERNEL_ERROR_ENOMEM; } else if (VirtualMemory::AllocFixed(in_addr, len, mode)) { out_addr = in_addr; } } else { const auto search_addr = (in_addr != 0 ? in_addr : DEFAULT_PS5_BASE); out_addr = VirtualMemory::AllocAligned(search_addr, len, mode, PAGE_SIZE); } *addr_in_out = reinterpret_cast(out_addr); if (out_addr == 0) { if (consumed_reserved) { g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved, consumed_range.name, false, consumed_range.placeholder_backed); } return KERNEL_ERROR_ENOMEM; } if (!g_flexible_memory->Map(out_addr, len, prot, mode, gpu_mode, name)) { LOGF_COLOR(Log::Color::Red, "\t [Fail]\n"); if (committed_from_reserved) { const bool placeholder_backed = RestoreCommittedPlaceholderOrProtect(out_addr, len); g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved, name, false, placeholder_backed); } else { VirtualMemory::Free(out_addr); } return KERNEL_ERROR_ENOMEM; } const auto range_type = ((flags & GUEST_MAP_STACK) != 0 ? VirtualRangeType::Stack : VirtualRangeType::Flexible); if (!g_virtual_ranges->Add(out_addr, len, 0, prot, 0, range_type, name, committed_from_reserved)) { GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess; g_flexible_memory->Unmap(out_addr, len, &rollback_gpu_mode); if (committed_from_reserved) { const bool placeholder_backed = RestoreCommittedPlaceholderOrProtect(out_addr, len); g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved, name, false, placeholder_backed); } else { VirtualMemory::Free(out_addr); } return KERNEL_ERROR_EBUSY; } LOGF("\t in_addr = 0x%016" PRIx64 "\n" "\t out_addr = 0x%016" PRIx64 "\n" "\t size = %" PRIu64 "\n" "\t mode = %s\n" "\t flags = 0x%08" PRIx32 "\n" "\t name = %s\n" "\t gpu_mode = %s\n", in_addr, out_addr, len, Common::EnumName(mode).c_str(), static_cast(flags), name, Common::EnumName(gpu_mode).c_str()); MapGpuRange(out_addr, len, gpu_mode); if (g_alloc_callback != nullptr) { g_alloc_callback(out_addr, len); } return OK; } int KYTY_SYSV_ABI KernelMapFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags) { return KernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, ""); } int KYTY_SYSV_ABI KernelSetPrtAperture(int index, void* addr, size_t len) { PRINT_NAME(); const auto address = reinterpret_cast(addr); LOGF("\t index = %d\n" "\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n", index, address, static_cast(len)); if (index < 0 || index > PRT_APERTURE_MAX_INDEX) { return KERNEL_ERROR_EINVAL; } if (len == 0) { Common::LockGuard lock(g_prt_aperture_mutex); const auto old = g_prt_apertures[static_cast(index)]; if (old.size != 0) { UnmapGpuRange(old.address, old.size, GpuAccessMode::ReadWrite); } g_prt_apertures[static_cast(index)] = {}; LOGF_COLOR(Log::Color::Green, "\t[Ok]\n"); return OK; } if (address == 0 || (address & (PRT_PAGE_SIZE - 1u)) != 0 || (len & (PRT_PAGE_SIZE - 1u)) != 0) { return KERNEL_ERROR_EINVAL; } if (address < PRT_APERTURE_START || len > PRT_APERTURE_END - address) { return KERNEL_ERROR_EINVAL; } { Common::LockGuard lock(g_prt_aperture_mutex); const auto old = g_prt_apertures[static_cast(index)]; if (old.size != 0) { UnmapGpuRange(old.address, old.size, GpuAccessMode::ReadWrite); } MapGpuRange(address, len, GpuAccessMode::ReadWrite); g_prt_apertures[static_cast(index)] = {address, static_cast(len)}; } LOGF_COLOR(Log::Color::Green, "\t[Ok]\n"); return OK; } int KYTY_SYSV_ABI KernelGetPrtAperture(int index, void** addr, size_t* len) { PRINT_NAME(); LOGF("\t index = %d\n" "\t addr = %p\n" "\t len = %p\n", index, static_cast(addr), static_cast(len)); if (index < 0 || index > PRT_APERTURE_MAX_INDEX) { return KERNEL_ERROR_EINVAL; } if (addr == nullptr || len == nullptr) { return KERNEL_ERROR_EFAULT; } PrtAperture aperture {}; { Common::LockGuard lock(g_prt_aperture_mutex); aperture = g_prt_apertures[static_cast(index)]; } *addr = reinterpret_cast(aperture.address); *len = static_cast(aperture.size); LOGF_COLOR(Log::Color::Green, "\t *addr = 0x%016" PRIx64 "\n" "\t *len = 0x%016" PRIx64 "\n" "\t[Ok]\n", aperture.address, aperture.size); return OK; } int KYTY_SYSV_ABI KernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); auto vaddr = reinterpret_cast(addr); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t name = %s\n", vaddr, len, name != nullptr ? name : "(null)"); if (name == nullptr) { return KERNEL_ERROR_EFAULT; } if (std::strlen(name) >= KERNEL_MAXIMUM_NAME_LENGTH) { return KERNEL_ERROR_ENAMETOOLONG; } g_physical_memory->SetVirtualRangeName(vaddr, len, name); g_flexible_memory->SetVirtualRangeName(vaddr, len, name); g_virtual_ranges->Rename(vaddr, len, name); return OK; } int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t start = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n", vaddr, len); if (len == 0 || UINT64_MAX - vaddr < len) { return KERNEL_ERROR_EINVAL; } VirtualRanges::Range range {}; if (!g_virtual_ranges->Query(vaddr, 0, &range)) { return KERNEL_ERROR_EACCES; } const auto chunk_len = std::min(len, range.size - (vaddr - range.start)); if (chunk_len < len) { const int ret = KernelMunmap(vaddr, chunk_len); return ret == OK ? KernelMunmap(vaddr + chunk_len, len - chunk_len) : ret; } if (IsReservedRangeType(range.type)) { const bool released = range.placeholder_backed ? g_placeholder_address_space->ReleaseFree(vaddr, len) : VirtualMemory::FreeRange(vaddr, len); if (!released) { return KERNEL_ERROR_EACCES; } g_virtual_ranges->Remove(vaddr, len); return OK; } UnmapGpuRange(vaddr, len, GetGpuAccessMode(range.protection)); size_t backend_len = 0; uint64_t backend_base = 0; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; const bool pooled_backend = range.type == VirtualRangeType::Pooled; bool backend_found = false; bool exact_host_range = backend_found && backend_base == vaddr && backend_len == len; bool backend_unmapped = false; bool shared_unmapped = false; bool placeholder_restored = false; bool flexible_backend = false; uint64_t physical_host_to_release = 0; uint64_t flexible_host_to_release = 0; const bool can_restore_placeholder = range.committed_from_reserved && range.start == vaddr && range.size == len; if (pooled_backend) { std::vector mappings; backend_found = g_pooled_memory->Query(vaddr, len, &mappings); exact_host_range = backend_found; if (backend_found) { VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode decoded_gpu = GpuAccessMode::NoAccess; shared_unmapped = DecodeMemoryProtection(range.protection, &mode, &decoded_gpu) && UnmapPooledBackingTransactional(mappings, mode); if (shared_unmapped && !g_pooled_memory->Release(vaddr, len, &gpu_mode)) { EXIT("failed to release unmapped pooled-memory range\n"); } backend_unmapped = shared_unmapped; placeholder_restored = shared_unmapped; } if (!backend_unmapped) { EXIT("pooled-memory unmap failed: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, len); return KERNEL_ERROR_EACCES; } } else if ((backend_found = g_physical_memory->Find(vaddr, &backend_base, &backend_len, nullptr, nullptr, &gpu_mode))) { exact_host_range = backend_base == vaddr && backend_len == len; backend_unmapped = g_physical_memory->Unmap(vaddr, len, &gpu_mode, &physical_host_to_release); if (backend_unmapped) { const bool direct_contains = g_direct_memory_backing->Contains(vaddr, len); if (direct_contains) { shared_unmapped = g_direct_memory_backing->Unmap( vaddr, len, can_restore_placeholder, &placeholder_restored); } if (placeholder_restored) { g_placeholder_address_space->AddFree(vaddr, len); } } } else { flexible_backend = true; backend_found = g_flexible_memory->Find(vaddr, &backend_base, &backend_len, nullptr, nullptr, &gpu_mode); exact_host_range = backend_found && backend_base == vaddr && backend_len == len; backend_unmapped = (backend_found ? g_flexible_memory->Unmap(vaddr, len, &gpu_mode, &flexible_host_to_release) : false); if (backend_unmapped && can_restore_placeholder) { placeholder_restored = g_placeholder_address_space->ReleaseCommitted(vaddr, len); } } if (!backend_unmapped) { EXIT("memory backend unmap failed: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, len); } g_virtual_ranges->Remove(vaddr, len); const bool host_range_released_or_shared = flexible_backend ? flexible_host_to_release != 0 : (shared_unmapped || physical_host_to_release != 0); const bool can_release_host_range = exact_host_range && backend_unmapped && !range.committed_from_reserved && host_range_released_or_shared; if (can_release_host_range && (vaddr != 0 || len != 0)) { if (!shared_unmapped) { VirtualMemory::Free(flexible_backend ? flexible_host_to_release : physical_host_to_release); } } else if (IsCommittedRangeType(range.type)) { constexpr uint64_t PAGE_SIZE = 0x4000; auto aligned_addr = vaddr & ~(PAGE_SIZE - 1); auto aligned_len = (len + (vaddr - aligned_addr) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (placeholder_restored) { // Exact placeholder-backed unmaps already returned this range to the placeholder // manager. } else if (!shared_unmapped) { VirtualMemory::Protect(aligned_addr, aligned_len, VirtualMemory::Mode::NoAccess); } else if (range.committed_from_reserved) { VirtualMemory::ReserveFixed(aligned_addr, aligned_len); } if (range.committed_from_reserved) { g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::Reserved, range.name, false, placeholder_restored); } } if (g_free_callback != nullptr && IsCommittedRangeType(range.type)) { g_free_callback(vaddr, len); } if (pooled_backend) { MemoryPoolSubtractCommitted(len); } return OK; } size_t KYTY_SYSV_ABI KernelGetDirectMemorySize() { PRINT_NAME(); return PhysicalMemory::Size(); } int KYTY_SYSV_ABI KernelAvailableDirectMemorySize(int64_t search_start, int64_t search_end, size_t alignment, int64_t* phys_addr_out, size_t* size_out) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t search_start = 0x%016" PRIx64 "\n" "\t search_end = 0x%016" PRIx64 "\n" "\t alignment = 0x%016" PRIx64 "\n", search_start, search_end, static_cast(alignment)); if (phys_addr_out == nullptr || size_out == nullptr) { return KERNEL_ERROR_EINVAL; } *phys_addr_out = 0; *size_out = 0; if (search_start < 0 || search_end < 0) { return KERNEL_ERROR_EINVAL; } if (search_end <= search_start) { LOGF_COLOR(Log::Color::Red, "\t[Fail]\n"); return KERNEL_ERROR_ENOMEM; } uint64_t phys_addr = 0; uint64_t size = 0; if (!g_physical_memory->Available(static_cast(search_start), static_cast(search_end), alignment, &phys_addr, &size)) { LOGF_COLOR(Log::Color::Red, "\t[Fail]\n"); return KERNEL_ERROR_ENOMEM; } *phys_addr_out = static_cast(phys_addr); *size_out = static_cast(size); LOGF_COLOR(Log::Color::Green, "\t phys_addr = 0x%016" PRIx64 "\n" "\t size = 0x%016" PRIx64 "\n" "\t[Ok]\n", phys_addr, size); return OK; } int KYTY_SYSV_ABI KernelGetPageTableStats(int* cpu_total, int* cpu_available, int* gpu_total, int* gpu_available) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); if (cpu_total == nullptr || cpu_available == nullptr || gpu_total == nullptr || gpu_available == nullptr) { return KERNEL_ERROR_EFAULT; } const auto cpu_used = (g_virtual_ranges != nullptr ? g_virtual_ranges->CountPageTableEntries(false) : 0); const auto gpu_used = (g_virtual_ranges != nullptr ? g_virtual_ranges->CountPageTableEntries(true) : 0); *cpu_total = PAGE_TABLE_POOL_ENTRIES; *gpu_total = PAGE_TABLE_POOL_ENTRIES; *cpu_available = PAGE_TABLE_POOL_ENTRIES - static_cast(std::min(cpu_used, PAGE_TABLE_POOL_ENTRIES)); *gpu_available = PAGE_TABLE_POOL_ENTRIES - static_cast(std::min(gpu_used, PAGE_TABLE_POOL_ENTRIES)); LOGF_COLOR(Log::Color::Green, "\t cpu_total = %d\n" "\t cpu_available = %d\n" "\t gpu_total = %d\n" "\t gpu_available = %d\n" "\t[Ok]\n", *cpu_total, *cpu_available, *gpu_total, *gpu_available); return OK; } int KYTY_SYSV_ABI KernelDirectMemoryQuery(int64_t offset, int flags, void* info, size_t info_size) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t offset = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n" "\t info_size = 0x%016" PRIx64 "\n", offset, flags, info_size); struct QueryInfo { int64_t start; int64_t end; int memory_type; }; if (offset < 0 || (flags != 0 && flags != 1) || info_size != sizeof(QueryInfo) || info == nullptr) { return KERNEL_ERROR_EINVAL; } auto* query_info = static_cast(info); PhysicalMemory::AllocatedBlock block {}; { Common::LockGuard lock(g_physical_memory->GetMutex()); const auto& blocks = g_physical_memory->GetPhysicalBlocks(); auto current = blocks.upper_bound(static_cast(offset)); if (current != blocks.begin()) { auto previous = std::prev(current); if (static_cast(offset) < previous->second.start_addr + previous->second.size) { current = previous; } } if (current == blocks.end() || (flags == 0 && (static_cast(offset) < current->second.start_addr || static_cast(offset) >= current->second.start_addr + current->second.size))) { if (flags == 1 && static_cast(offset) < PhysicalMemory::Size()) { query_info->start = static_cast(PhysicalMemory::Size()); query_info->end = static_cast(PhysicalMemory::Size()); query_info->memory_type = 0; LOGF_COLOR(Log::Color::Green, "\t terminal = true\n\t[Ok]\n"); return OK; } LOGF_COLOR(Log::Color::Red, "\t[Fail]\n"); return KERNEL_ERROR_EACCES; } block = current->second; uint64_t end = block.start_addr + block.size; for (auto following = std::next(current); following != blocks.end() && following->second.start_addr == end && following->second.memory_type == block.memory_type; ++following) { end += following->second.size; } block.size = end - block.start_addr; } query_info->start = static_cast(block.start_addr); query_info->end = static_cast(block.start_addr + block.size); query_info->memory_type = block.memory_type; LOGF_COLOR(Log::Color::Green, "\t start = %016" PRIx64 "\n" "\t end = %016" PRIx64 "\n" "\t memory_type = %d\n" "\t[Ok]\n", query_info->start, query_info->end, query_info->memory_type); return OK; } int KYTY_SYSV_ABI KernelAllocateDirectMemory(int64_t search_start, int64_t search_end, size_t len, size_t alignment, int memory_type, int64_t* phys_addr_out) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t search_start = 0x%016" PRIx64 "\n" "\t search_end = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t alignment = 0x%016" PRIx64 "\n" "\t memory_type = %d\n", search_start, search_end, len, alignment, memory_type); if (search_start < 0 || search_end <= search_start || len == 0 || phys_addr_out == nullptr) { return KERNEL_ERROR_EINVAL; } uint64_t addr = 0; if (!g_physical_memory->Alloc(search_start, search_end, len, alignment, &addr, memory_type)) { LOGF_COLOR(Log::Color::Red, "\t[Fail]\n"); return KERNEL_ERROR_EAGAIN; } *phys_addr_out = static_cast(addr); LOGF_COLOR(Log::Color::Green, "\tphys_addr = %016" PRIx64 "\n\t[Ok]\n", addr); return OK; } int KYTY_SYSV_ABI KernelAllocateMainDirectMemory(size_t len, size_t alignment, int memory_type, int64_t* phys_addr_out) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t len = 0x%016" PRIx64 "\n" "\t alignment = 0x%016" PRIx64 "\n" "\t memory_type = %d\n", len, alignment, memory_type); if (len == 0 || phys_addr_out == nullptr) { return KERNEL_ERROR_EINVAL; } uint64_t addr = 0; if (!g_physical_memory->Alloc(0, PhysicalMemory::Size(), len, alignment, &addr, memory_type)) { LOGF_COLOR(Log::Color::Red, "\t[Fail]\n"); return KERNEL_ERROR_EAGAIN; } *phys_addr_out = static_cast(addr); LOGF_COLOR(Log::Color::Green, "\tphys_addr = %016" PRIx64 "\n\t[Ok]\n", addr); return OK; } int KYTY_SYSV_ABI KernelReleaseDirectMemory(int64_t start, size_t len) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t start = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n", start, len); if (start < 0 || len == 0) { return KERNEL_ERROR_EINVAL; } if (g_pooled_memory->ReleaseExpansion(static_cast(start), len)) { if (!g_physical_memory->ReleasePoolExpansion(static_cast(start), len)) { EXIT("failed to release physical pool expansion\n"); } return OK; } uint64_t vaddr = 0; uint64_t size = 0; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; PhysicalMemory::AllocatedBlock block {}; bool found = g_physical_memory->Find(start, false, &block); bool exact_host_range = found && block.start_addr == static_cast(start) && block.size == len; VirtualRanges::Range range {}; const bool range_found = (found ? g_virtual_ranges->Query(block.map_vaddr, 0, &range) : false); bool shared_unmapped = false; bool placeholder_restored = false; const auto mapped_aliases = g_physical_memory->FindMappings(start, len); for (const auto& alias: mapped_aliases) { UnmapGpuRange(alias.map_vaddr, alias.map_size, alias.gpu_mode); } bool result = g_physical_memory->Release(start, len, &vaddr, &size, &gpu_mode); if (!result) { if (!mapped_aliases.empty()) { EXIT("physical-memory release failed after GPU alias unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 " aliases=%zu\n", start, len, mapped_aliases.size()); } return KERNEL_ERROR_EACCES; } for (const auto& alias: mapped_aliases) { GpuAccessMode alias_gpu_mode = GpuAccessMode::NoAccess; uint64_t alias_host_to_release = 0; if (!g_physical_memory->Unmap(alias.map_vaddr, alias.map_size, &alias_gpu_mode, &alias_host_to_release)) { EXIT("physical-memory alias unmap failed: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", alias.map_vaddr, alias.map_size); } VirtualRanges::Range alias_range {}; const bool alias_range_found = g_virtual_ranges->Query(alias.map_vaddr, 0, &alias_range); g_virtual_ranges->Remove(alias.map_vaddr, alias.map_size); bool alias_shared_unmapped = false; bool alias_placeholder_restored = false; if (g_direct_memory_backing->Contains(alias.map_vaddr, alias.map_size)) { const bool can_restore_placeholder = alias_range_found && alias_range.committed_from_reserved && alias_range.start == alias.map_vaddr && alias_range.size == alias.map_size; alias_shared_unmapped = g_direct_memory_backing->Unmap(alias.map_vaddr, alias.map_size, can_restore_placeholder, &alias_placeholder_restored); if (alias_placeholder_restored) { g_placeholder_address_space->AddFree(alias.map_vaddr, alias.map_size); } } if (alias_range_found && alias_range.committed_from_reserved) { g_virtual_ranges->Add(alias.map_vaddr, alias.map_size, 0, 0, 0, VirtualRangeType::Reserved, alias_range.name, false, alias_placeholder_restored); } else if (!alias_shared_unmapped && alias_host_to_release != 0) { VirtualMemory::Free(alias_host_to_release); } else if (!alias_shared_unmapped) { VirtualMemory::Protect(alias.map_vaddr, alias.map_size, VirtualMemory::Mode::NoAccess); } } if (vaddr != 0 || size != 0) { g_virtual_ranges->Remove(vaddr, size); const bool can_restore_placeholder = range_found && range.committed_from_reserved && range.start == vaddr && range.size == size; shared_unmapped = g_direct_memory_backing->Unmap(vaddr, size, can_restore_placeholder, &placeholder_restored); if (placeholder_restored) { g_placeholder_address_space->AddFree(vaddr, size); } } if (exact_host_range && (!range_found || !range.committed_from_reserved) && (vaddr != 0 || size != 0)) { if (!shared_unmapped) { VirtualMemory::Free(vaddr); } } else if (vaddr != 0 || size != 0) { if (placeholder_restored) { // Exact placeholder-backed unmaps already returned this range to the placeholder // manager. } else if (!shared_unmapped) { VirtualMemory::Protect(vaddr, size, VirtualMemory::Mode::NoAccess); } else if (range_found && range.committed_from_reserved) { VirtualMemory::ReserveFixed(vaddr, size); } if (range_found && range.committed_from_reserved) { g_virtual_ranges->Add(vaddr, size, 0, 0, 0, VirtualRangeType::Reserved, range.name, false, placeholder_restored); } } if (g_free_callback != nullptr) { g_free_callback(vaddr, len); } return OK; } int KYTY_SYSV_ABI KernelCheckedReleaseDirectMemory(int64_t start, size_t len) { PRINT_NAME(); LOGF("\t start = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n", start, len); if (start < 0) { return KERNEL_ERROR_EINVAL; } constexpr size_t PAGE_SIZE = 0x4000; if ((static_cast(start) & (PAGE_SIZE - 1)) != 0 || (len & (PAGE_SIZE - 1)) != 0) { return KERNEL_ERROR_EINVAL; } if (len == 0) { return OK; } return KernelReleaseDirectMemory(start, len); } int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); EXIT_NOT_IMPLEMENTED(addr == nullptr); constexpr int GUEST_MAP_FIXED = 0x10; constexpr int GUEST_MAP_NO_OVERWRITE = 0x80; bool fixed = ((flags & GUEST_MAP_FIXED) != 0); bool no_overwrite = ((flags & GUEST_MAP_NO_OVERWRITE) != 0); VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; if (!DecodeMemoryProtection(prot, &mode, &gpu_mode)) { EXIT("unknown prot: %d\n", prot); } if (direct_memory_start < 0 || len == 0 || !g_physical_memory->CanMapDirect(static_cast(direct_memory_start), len)) { return KERNEL_ERROR_ENOMEM; } auto in_addr = reinterpret_cast(*addr); uint64_t out_addr = 0; bool committed_from_reserved = false; bool shared_backing = false; bool consumed_reserved = false; VirtualRanges::Range consumed_range {}; auto shared_failure = DirectMemoryBacking::FailureReason::None; // Direct mappings must remain views of the single backing object. Anonymous fallbacks break // aliasing and lose direct-memory contents when a range is unmapped and mapped again. auto map_shared_fixed = [&](uint64_t target_addr) -> bool { const bool placeholder_ready = g_placeholder_address_space->Consume(target_addr, len); if (placeholder_ready) { if (g_direct_memory_backing->MapExistingPlaceholderFixed( target_addr, len, direct_memory_start, mode, &shared_failure)) { return true; } g_placeholder_address_space->AddFree(target_addr, len); return false; } return g_direct_memory_backing->MapFixed(target_addr, len, direct_memory_start, mode, &shared_failure); }; auto map_consumed_reserved_fixed = [&]() { consumed_reserved = true; if (map_shared_fixed(in_addr)) { out_addr = in_addr; committed_from_reserved = true; shared_backing = true; } }; if (fixed) { EXIT_NOT_IMPLEMENTED(in_addr == 0); EXIT_NOT_IMPLEMENTED(alignment != 0 && (in_addr & (alignment - 1)) != 0); if (no_overwrite && g_virtual_ranges->HasOverlap(in_addr, len)) { return KERNEL_ERROR_ENOMEM; } VirtualRanges::Range same_range {}; if (g_virtual_ranges->Query(in_addr, 0, &same_range) && same_range.type == VirtualRangeType::Direct && len <= same_range.start + same_range.size - in_addr && same_range.offset + (in_addr - same_range.start) == static_cast(direct_memory_start) && same_range.protection == prot) { *addr = reinterpret_cast(in_addr); LOGF_COLOR(Log::Color::Green, "\t in_addr = 0x%016" PRIx64 "\n" "\t out_addr = 0x%016" PRIx64 "\n" "\t dmem = 0x%016" PRIx64 "\n" "\t size = 0x%016" PRIx64 "\n" "\t mode = %s\n" "\t flags = 0x%08" PRIx32 "\n" "\t align = 0x%016" PRIx64 "\n" "\t gpu_mode = %s\n" "\t shared = %s\n" "\t reason = already-mapped\n" "\t [Ok]\n", in_addr, in_addr, static_cast(direct_memory_start), len, Common::EnumName(mode).c_str(), static_cast(flags), alignment, Common::EnumName(gpu_mode).c_str(), g_direct_memory_backing->Contains(in_addr, len) ? "yes" : "no"); return OK; } if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) { map_consumed_reserved_fixed(); } else { bool placeholder_backed = false; if (ReplaceFixedRangeWithReserved(in_addr, len, &placeholder_backed) && g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) { map_consumed_reserved_fixed(); } else { VirtualRanges::Range existing_range {}; if (g_virtual_ranges->Query(in_addr, 0, &existing_range)) { GpuAccessMode old_gpu_mode = GpuAccessMode::NoAccess; g_physical_memory->Unmap(in_addr, len, &old_gpu_mode); g_flexible_memory->Unmap(in_addr, len, &old_gpu_mode); g_virtual_ranges->Remove(in_addr, len); const bool old_shared = g_direct_memory_backing->Contains(in_addr, len); bool old_placeholder_restored = false; g_direct_memory_backing->Unmap(in_addr, len, existing_range.committed_from_reserved, &old_placeholder_restored); if (old_placeholder_restored) { g_placeholder_address_space->AddFree(in_addr, len); } if (old_shared && g_direct_memory_backing->Contains(in_addr, len)) { out_addr = 0; } else if (map_shared_fixed(in_addr)) { out_addr = in_addr; committed_from_reserved = existing_range.committed_from_reserved || existing_range.start != in_addr || existing_range.size != len; shared_backing = true; } } else if (!ReleaseReservedRange(in_addr, len)) { return KERNEL_ERROR_ENOMEM; } else if (map_shared_fixed(in_addr)) { out_addr = in_addr; shared_backing = true; } } } } else { constexpr size_t DEFAULT_ALIGNMENT = 0x4000; alignment = (alignment != 0 ? alignment : DEFAULT_ALIGNMENT); if (in_addr != 0 && g_virtual_ranges->Query(in_addr, 0, &consumed_range) && consumed_range.type == VirtualRangeType::Reserved && g_virtual_ranges->ConsumeReserved(in_addr, len)) { consumed_reserved = true; if (map_shared_fixed(in_addr)) { out_addr = in_addr; committed_from_reserved = true; shared_backing = true; } } else { out_addr = g_direct_memory_backing->MapAligned(in_addr, len, direct_memory_start, mode, alignment, &shared_failure); shared_backing = out_addr != 0; } } *addr = reinterpret_cast(out_addr); const char* shared_reason = "n/a"; if (!shared_backing) { shared_reason = DirectMemoryBacking::GetFailureReasonName(shared_failure); } LOGF("\t in_addr = 0x%016" PRIx64 "\n" "\t out_addr = 0x%016" PRIx64 "\n" "\t dmem = 0x%016" PRIx64 "\n" "\t size = 0x%016" PRIx64 "\n" "\t mode = %s\n" "\t flags = 0x%08" PRIx32 "\n" "\t align = 0x%016" PRIx64 "\n" "\t gpu_mode = %s\n" "\t shared = %s\n" "\t reason = %s\n", in_addr, out_addr, static_cast(direct_memory_start), len, Common::EnumName(mode).c_str(), static_cast(flags), alignment, Common::EnumName(gpu_mode).c_str(), shared_backing ? "yes" : "no", shared_reason); if (out_addr == 0) { if (consumed_reserved) { g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved, consumed_range.name, false, consumed_range.placeholder_backed); } return KERNEL_ERROR_ENOMEM; } if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode)) { LOGF_COLOR(Log::Color::Red, "\t [Fail]\n"); bool placeholder_restored = false; g_direct_memory_backing->Unmap(out_addr, len, committed_from_reserved, &placeholder_restored); if (placeholder_restored) { g_placeholder_address_space->AddFree(out_addr, len); } KYTY_NOT_IMPLEMENTED; return KERNEL_ERROR_EBUSY; } PhysicalMemory::AllocatedBlock mapped_block {}; g_physical_memory->Find(direct_memory_start, false, &mapped_block); if (!g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot, mapped_block.memory_type, VirtualRangeType::Direct, "", committed_from_reserved)) { GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess; g_physical_memory->Unmap(out_addr, len, &rollback_gpu_mode); bool placeholder_restored = false; g_direct_memory_backing->Unmap(out_addr, len, committed_from_reserved, &placeholder_restored); if (placeholder_restored) { g_placeholder_address_space->AddFree(out_addr, len); } return KERNEL_ERROR_EBUSY; } MapGpuRange(out_addr, len, gpu_mode); if (g_alloc_callback != nullptr) { g_alloc_callback(out_addr, len); } LOGF_COLOR(Log::Color::Green, "\t [Ok]\n"); return OK; } int KYTY_SYSV_ABI KernelMapDirectMemory2(void** addr, size_t len, int type, int prot, int flags, int64_t direct_memory_start, size_t alignment) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t type = %d\n", type); auto ret = KernelMapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment); if (ret == OK && addr != nullptr && *addr != nullptr) { const auto out_addr = reinterpret_cast(*addr); g_physical_memory->SetVirtualRangeMemoryType(out_addr, len, type); g_virtual_ranges->SetMemoryType(out_addr, len, type); } return ret; } int KYTY_SYSV_ABI KernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment, const char* name) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t name = %s\n", name != nullptr ? name : "(null)"); if (name == nullptr) { return KERNEL_ERROR_EFAULT; } if (std::strlen(name) >= KERNEL_MAXIMUM_NAME_LENGTH) { return KERNEL_ERROR_ENAMETOOLONG; } auto ret = KernelMapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment); if (ret == OK && addr != nullptr) { g_physical_memory->SetVirtualRangeName(reinterpret_cast(*addr), len, name); g_virtual_ranges->Rename(reinterpret_cast(*addr), len, name); } return ret; } int KYTY_SYSV_ABI KernelIsAddressSanitizerEnabled() { PRINT_NAME(); return 0; } int KYTY_SYSV_ABI KernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); EXIT_NOT_IMPLEMENTED(addr == nullptr); VirtualRanges::Range range {}; if (!g_virtual_ranges->Query(reinterpret_cast(addr), 0, &range)) { return KERNEL_ERROR_EACCES; } if (start != nullptr) { *start = reinterpret_cast(range.start); } if (end != nullptr) { *end = reinterpret_cast(range.start + range.size); } if (prot != nullptr) { *prot = range.protection; } return OK; } static bool ReserveFixedHostRange(uint64_t start, uint64_t size) { constexpr uint64_t PAGE_SIZE = 0x4000; #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS SYSTEM_INFO system_info {}; GetSystemInfo(&system_info); const uint64_t granularity = system_info.dwAllocationGranularity; if ((start & (granularity - 1u)) == 0 && (size & (granularity - 1u)) == 0) { MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(start), &info, sizeof(info)) != 0 && info.State == MEM_FREE && info.RegionSize >= size && VirtualMemory::ReserveFixed(start, size)) { return true; } } #endif #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) { if (g_test_host_reservation_pages_before_failure == 0) { g_test_host_reservation_pages_before_failure = UINT32_MAX; return false; } if (g_test_host_reservation_pages_before_failure != UINT32_MAX) { g_test_host_reservation_pages_before_failure--; } } g_test_host_reservation_pages_before_failure = UINT32_MAX; #endif #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS // Prefer one syscall, then fall back to the per-page path. if (size > PAGE_SIZE && VirtualMemory::ReserveFixed(start, size)) { return true; } #endif bool host_mutated = false; for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(addr), &info, sizeof(info)) != 0) { if (info.State == MEM_COMMIT) { if (!VirtualMemory::Decommit(addr, PAGE_SIZE)) { if (host_mutated) { EXIT("reserve-fixed partial host decommit cannot be rolled back safely\n"); } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: decommit failed at 0x%016" PRIx64 "\n", addr); return false; } host_mutated = true; continue; } if (info.State == MEM_RESERVE) { continue; } } #endif if (!VirtualMemory::ReserveFixed(addr, PAGE_SIZE)) { if (host_mutated) { EXIT("reserve-fixed partial host reservation cannot be rolled back safely\n"); } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: reserve failed at 0x%016" PRIx64 "\n", addr); return false; } host_mutated = true; } return true; } static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* placeholder_backed) { EXIT_IF(placeholder_backed == nullptr); *placeholder_backed = false; struct ReplacedChunk { VirtualRanges::Range range {}; VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; bool shared_backing = false; bool placeholder_restored = false; bool host_unmapped = false; bool backend_unmapped = false; }; std::vector chunks; const auto end = start + size; auto current = start; while (current < end) { VirtualRanges::Range range {}; if (!g_virtual_ranges->Query(current, 1, &range)) { break; } if (range.start >= end) { break; } if (current < range.start) { current = std::min(end, range.start); continue; } const auto range_end = range.start + range.size; const auto chunk = std::min(end, range_end) - current; ReplacedChunk replaced {}; replaced.range = range; replaced.range.start = current; replaced.range.size = chunk; if (range.type == VirtualRangeType::Direct) { replaced.range.offset += current - range.start; } DecodeMemoryProtection(replaced.range.protection, &replaced.mode, &replaced.gpu_mode); replaced.shared_backing = (range.type == VirtualRangeType::Direct && g_direct_memory_backing->Contains(current, chunk)); chunks.push_back(replaced); current += chunk; } auto restore_chunks = [&chunks]() -> bool { bool ok = true; for (auto it = chunks.rbegin(); it != chunks.rend(); ++it) { const auto& chunk = *it; bool host_restored = true; bool backend_restored = true; if (chunk.range.type == VirtualRangeType::Direct) { if (chunk.host_unmapped && chunk.placeholder_restored) { const bool consumed = g_placeholder_address_space->Consume(chunk.range.start, chunk.range.size); host_restored = consumed && g_direct_memory_backing->MapExistingPlaceholderFixed( chunk.range.start, chunk.range.size, chunk.range.offset, chunk.mode); if (consumed && !host_restored) { g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); } } else if (chunk.host_unmapped || !chunk.shared_backing) { host_restored = (chunk.shared_backing ? g_direct_memory_backing->MapFixed( chunk.range.start, chunk.range.size, chunk.range.offset, chunk.mode) : CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode)); } if (chunk.backend_unmapped) { backend_restored = g_physical_memory->Map( chunk.range.start, chunk.range.offset, chunk.range.size, chunk.range.protection, chunk.mode, chunk.gpu_mode); } } else if (chunk.range.type == VirtualRangeType::Flexible || chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Pooled) { host_restored = CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode); if (chunk.backend_unmapped) { backend_restored = g_flexible_memory->Map(chunk.range.start, chunk.range.size, chunk.range.protection, chunk.mode, chunk.gpu_mode, chunk.range.name); } } const bool range_restored = g_virtual_ranges->Add( chunk.range.start, chunk.range.size, chunk.range.offset, chunk.range.protection, chunk.range.memory_type, chunk.range.type, chunk.range.name, chunk.range.committed_from_reserved, chunk.range.placeholder_backed); ok = host_restored && backend_restored && range_restored && ok; } return ok; }; for (const auto& chunk: chunks) { if (chunk.range.type == VirtualRangeType::Pooled) { EXIT("reserve-fixed replacement of pooled memory is unsupported: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", chunk.range.start, chunk.range.size); } } const bool gpu_unmapped = std::any_of(chunks.begin(), chunks.end(), [](const auto& chunk) { return IsCommittedRangeType(chunk.range.type) && chunk.gpu_mode != GpuAccessMode::NoAccess && IsGpuAddressRange(chunk.range.start, chunk.range.size); }); for (const auto& chunk: chunks) { if (IsCommittedRangeType(chunk.range.type)) { UnmapGpuRange(chunk.range.start, chunk.range.size, chunk.gpu_mode); } } g_virtual_ranges->Remove(start, size); for (auto& chunk: chunks) { GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; bool unmapped = true; if (chunk.range.type == VirtualRangeType::Direct) { if (chunk.shared_backing) { bool chunk_placeholder_restored = false; if (!g_direct_memory_backing->Unmap(chunk.range.start, chunk.range.size, true, &chunk_placeholder_restored)) { unmapped = false; } else { chunk.host_unmapped = true; if (chunk_placeholder_restored) { g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); chunk.placeholder_restored = true; } } } if (unmapped) { unmapped = g_physical_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); chunk.gpu_mode = gpu_mode; chunk.backend_unmapped = unmapped; } } else if (chunk.range.type == VirtualRangeType::Flexible || chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Pooled) { unmapped = g_flexible_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); chunk.gpu_mode = gpu_mode; chunk.backend_unmapped = unmapped; } if (!unmapped) { if (gpu_unmapped) { EXIT("reserve-fixed backend unmap failed after GPU unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", chunk.range.start, chunk.range.size); } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: backend unmap failed at 0x%016" PRIx64 ", size=0x%016" PRIx64 ", type=%s\n", chunk.range.start, chunk.range.size, Common::EnumName(chunk.range.type).c_str()); if (!restore_chunks()) { EXIT("reserve-fixed backend-unmap rollback failed\n"); } return false; } } if (g_placeholder_address_space->ReserveFixed(start, size)) { *placeholder_backed = true; } else if (!ReserveFixedHostRange(start, size)) { if (gpu_unmapped) { EXIT("reserve-fixed host reservation failed after GPU unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", start, size); } if (!restore_chunks()) { EXIT("reserve-fixed host-reservation rollback failed\n"); } return false; } bool range_added = false; #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) if (g_test_fail_next_fixed_reserve_range_add) { g_test_fail_next_fixed_reserve_range_add = false; } else #endif { range_added = g_virtual_ranges->Add(start, size, 0, 0, 0, VirtualRangeType::Reserved, "anon", false, *placeholder_backed); } if (!range_added) { if (gpu_unmapped) { EXIT("reserve-fixed range registration failed after GPU unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", start, size); } if (!*placeholder_backed) { VirtualMemory::Free(start); } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: range add failed at 0x%016" PRIx64 ", size=0x%016" PRIx64 "\n", start, size); if (!restore_chunks()) { EXIT("reserve-fixed range-registration rollback failed\n"); } if (*placeholder_backed) { auto free_start = start; for (const auto& chunk: chunks) { if (free_start < chunk.range.start && !g_placeholder_address_space->ReleaseFree(free_start, chunk.range.start - free_start)) { EXIT("reserve-fixed range-registration gap cleanup failed\n"); } free_start = chunk.range.start + chunk.range.size; } if (free_start < start + size && !g_placeholder_address_space->ReleaseFree(free_start, start + size - free_start)) { EXIT("reserve-fixed range-registration tail cleanup failed\n"); } } return false; } for (const auto& chunk: chunks) { if (g_free_callback != nullptr && IsCommittedRangeType(chunk.range.type)) { g_free_callback(chunk.range.start, chunk.range.size); } } return true; } int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags, size_t alignment) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); const auto in_addr = (addr != nullptr ? reinterpret_cast(*addr) : 0); LOGF("\t in_addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n" "\t alignment = 0x%016" PRIx64 "\n", in_addr, len, flags, alignment); constexpr size_t PAGE_SIZE = 0x4000; constexpr int GUEST_MAP_FIXED = 0x10; constexpr int GUEST_MAP_NO_OVERWRITE = 0x80; if (addr == nullptr || len == 0 || (len & (PAGE_SIZE - 1)) != 0) { return KERNEL_ERROR_EINVAL; } if (alignment != 0 && (alignment & (alignment - 1)) != 0) { return KERNEL_ERROR_EINVAL; } uint64_t out_addr = 0; bool range_already_added = false; bool placeholder_backed = false; if ((flags & GUEST_MAP_FIXED) != 0) { if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1)) != 0) { return KERNEL_ERROR_EINVAL; } if ((flags & GUEST_MAP_NO_OVERWRITE) != 0 && g_virtual_ranges->HasOverlap(in_addr, len)) { return KERNEL_ERROR_ENOMEM; } if (ReplaceFixedRangeWithReserved(in_addr, len, &placeholder_backed)) { out_addr = in_addr; range_already_added = true; } } else { alignment = (alignment != 0 ? alignment : PAGE_SIZE); out_addr = g_placeholder_address_space->ReserveAligned(in_addr, len, alignment); placeholder_backed = (out_addr != 0); if (out_addr == 0) { out_addr = VirtualMemory::ReserveAligned(in_addr, len, alignment); placeholder_backed = false; } } if (out_addr == 0) { return KERNEL_ERROR_ENOMEM; } if (!range_already_added && !g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved, "anon", false, placeholder_backed)) { if (!placeholder_backed || !g_placeholder_address_space->ReleaseFree(out_addr, len)) { VirtualMemory::Free(out_addr); } return KERNEL_ERROR_EBUSY; } *addr = reinterpret_cast(out_addr); LOGF("\t out_addr = 0x%016" PRIx64 "\n" "\t placeholder = %s\n", out_addr, placeholder_backed ? "yes" : "no"); return OK; } #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) void TestFailNextPhysicalMemoryUnmap() { TestFailPhysicalMemoryUnmapAfter(0); } void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps) { g_test_physical_memory_unmaps_before_failure = successful_unmaps; } void TestFailHostReservationAfter(uint32_t successful_pages) { g_test_host_reservation_pages_before_failure = successful_pages; } void TestFailNextFixedReserveRangeRegistration() { g_test_fail_next_fixed_reserve_range_add = true; } bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size) { return g_placeholder_address_space->TestContainsFree(vaddr, size); } #endif bool KernelHandleReservedRangeAccessViolation(uint64_t vaddr) { std::lock_guard memory_operation_lock(g_memory_operation_mutex); VirtualRanges::Range range {}; if (!g_virtual_ranges->Query(vaddr, 0, &range) || std::strncmp(range.name, "AMM", KERNEL_MAXIMUM_NAME_LENGTH) != 0) { return false; } EXIT("AMM virtual-memory unmap is unsupported: addr=0x%016" PRIx64 "\n", vaddr); } int KYTY_SYSV_ABI KernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t info_size) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); auto vaddr = reinterpret_cast(addr); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n" "\t info_size = 0x%016" PRIx64 "\n", vaddr, flags, info_size); if (info == nullptr || info_size != sizeof(VirtualQueryInfo) || (flags != 0 && flags != 1)) { return KERNEL_ERROR_EINVAL; } VirtualRanges::Range candidate {}; if (!g_virtual_ranges->Query(vaddr, flags, &candidate)) { return KERNEL_ERROR_EACCES; } std::memset(info, 0, sizeof(VirtualQueryInfo)); info->start = candidate.start; info->end = candidate.start + candidate.size; info->offset = candidate.offset; info->protection = candidate.protection; info->memory_type = candidate.memory_type; info->is_flexible = (candidate.type == VirtualRangeType::Flexible ? 1 : 0); info->is_direct = (candidate.type == VirtualRangeType::Direct ? 1 : 0); info->is_stack = (candidate.type == VirtualRangeType::Stack ? 1 : 0); info->is_pooled = (IsPooledRangeType(candidate.type) ? 1 : 0); info->is_committed = (IsCommittedRangeType(candidate.type) ? 1 : 0); info->is_gpu_prt = IsInPrtAperture(vaddr); CopyVirtualRangeName(info->name, candidate.name); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t start = 0x%016" PRIx64 "\n" "\t end = 0x%016" PRIx64 "\n" "\t offset = 0x%016" PRIx64 "\n" "\t protection = 0x%08" PRIx32 "\n" "\t memory_type = %d\n" "\t flexible = %d\n" "\t direct = %d\n" "\t name = %s\n", static_cast(info->start), static_cast(info->end), info->offset, info->protection, info->memory_type, static_cast(info->is_flexible), static_cast(info->is_direct), info->name); } return OK; } int KYTY_SYSV_ABI KernelIsStack(void* addr, void** start, void** end) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); auto vaddr = reinterpret_cast(addr); LOGF("\t addr = 0x%016" PRIx64 "\n", vaddr); VirtualRanges::Range candidate {}; if (!g_virtual_ranges->Query(vaddr, 0, &candidate)) { return KERNEL_ERROR_EACCES; } uint64_t stack_start = 0; uint64_t stack_end = 0; if (candidate.type == VirtualRangeType::Stack) { stack_start = candidate.start; stack_end = candidate.start + candidate.size; } if (start != nullptr) { *start = reinterpret_cast(stack_start); } if (end != nullptr) { *end = reinterpret_cast(stack_end); } LOGF("\t start = 0x%016" PRIx64 "\n" "\t end = 0x%016" PRIx64 "\n", stack_start, stack_end); return OK; } int KYTY_SYSV_ABI KernelAvailableFlexibleMemorySize(size_t* size) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); EXIT_NOT_IMPLEMENTED(size == nullptr); *size = g_flexible_memory->Available(); LOGF("\t *size = 0x%016" PRIx64 "\n", *size); return OK; } int KYTY_SYSV_ABI KernelConfiguredFlexibleMemorySize(uint64_t* size) { PRINT_NAME(); if (size == nullptr) { return KERNEL_ERROR_EINVAL; } *size = FlexibleMemory::Size(); LOGF("\t *size = 0x%016" PRIx64 "\n", *size); return OK; } static int ProgramProtection(VirtualMemory::Mode mode) { const auto protection = static_cast(mode); if ((protection & ~(PROT_CPU_READ | PROT_CPU_WRITE | PROT_CPU_EXEC)) != 0) { EXIT("unsupported program-memory protection: 0x%08x\n", protection); } return protection; } static std::vector RequireProgramMemory(uint64_t vaddr, uint64_t size) { std::vector ranges; if (g_virtual_ranges == nullptr || !g_virtual_ranges->QuerySpan(vaddr, size, &ranges) || std::any_of(ranges.begin(), ranges.end(), [](const auto& range) { return range.type != VirtualRangeType::Code; })) { EXIT("program-memory range is not fully mapped: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } return ranges; } void RegisterProgramMemory(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode, const char* name) { std::lock_guard memory_operation_lock(g_memory_operation_mutex); if (g_virtual_ranges == nullptr || vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr || name == nullptr || !g_virtual_ranges->Add(vaddr, size, 0, ProgramProtection(mode), 0, VirtualRangeType::Code, name)) { EXIT("failed to register program memory: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } } void UpdateProgramMemoryProtection(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) { std::lock_guard memory_operation_lock(g_memory_operation_mutex); RequireProgramMemory(vaddr, size); g_virtual_ranges->Protect(vaddr, size, ProgramProtection(mode)); } void UnregisterProgramMemory(uint64_t vaddr, uint64_t size) { std::lock_guard memory_operation_lock(g_memory_operation_mutex); for (const auto& range: RequireProgramMemory(vaddr, size)) { const auto gpu_mode = GetGpuAccessMode(range.protection); if (gpu_mode != GpuAccessMode::NoAccess) { if (!GetGpuResources().IsMapped(range.start, range.size)) { EXIT("program GPU range is not tracked: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", range.start, range.size); } UnmapGpuRange(range.start, range.size, gpu_mode); } } if (!g_virtual_ranges->Remove(vaddr, size)) { EXIT("failed to unregister program memory: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, size); } } int KYTY_SYSV_ABI KernelMprotect(const void* addr, size_t len, int prot) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); auto vaddr = reinterpret_cast(addr); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n", vaddr, static_cast(len)); constexpr uint64_t PAGE_SIZE = 0x4000; auto aligned_addr = vaddr & ~(PAGE_SIZE - 1); const auto page_offset = vaddr - aligned_addr; if (len > UINT64_MAX - page_offset || len + page_offset > UINT64_MAX - (PAGE_SIZE - 1)) { EXIT("memory-protection range overflows: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, static_cast(len)); } auto aligned_len = (len + page_offset + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (aligned_len == 0) { return OK; } VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; if (!DecodeMemoryProtection(prot, &mode, &gpu_mode)) { return KERNEL_ERROR_EINVAL; } std::vector old_ranges; if (!g_virtual_ranges->QuerySpan(aligned_addr, aligned_len, &old_ranges) || std::any_of(old_ranges.begin(), old_ranges.end(), [](const auto& range) { return !IsCommittedRangeType(range.type); })) { EXIT("memory-protection range is not fully mapped: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", aligned_addr, aligned_len); } for (const auto& old_range: old_ranges) { const auto old_gpu_mode = GetGpuAccessMode(old_range.protection); if (old_gpu_mode == GpuAccessMode::NoAccess) { continue; } if (!GetGpuResources().IsMapped(old_range.start, old_range.size)) { EXIT("GPU protection transition requires tracked memory: addr=0x%016" PRIx64 " size=0x%016" PRIx64 " type=%s\n", old_range.start, old_range.size, Common::EnumName(old_range.type).c_str()); } UnmapGpuRange(old_range.start, old_range.size, old_gpu_mode); } VirtualMemory::Mode old_mode {}; bool ok = ProtectCommittedHostMemory(aligned_addr, aligned_len, mode, &old_mode); if (!ok) { EXIT("host memory-protection update failed: addr=0x%016" PRIx64 " size=0x%016" PRIx64 " prot=0x%08x\n", aligned_addr, aligned_len, prot); } g_virtual_ranges->Protect(aligned_addr, aligned_len, prot); g_direct_memory_backing->UpdateProtection(aligned_addr, aligned_len, mode); MapGpuRange(aligned_addr, aligned_len, gpu_mode); LOGF("\t prot: %s -> %s\n", Common::EnumName(old_mode).c_str(), Common::EnumName(mode).c_str()); return OK; } int KYTY_SYSV_ABI KernelMtypeprotect(const void* addr, size_t len, int type, int prot) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t type = 0x%08" PRIx32 "\n" "\t prot = 0x%08" PRIx32 "\n", reinterpret_cast(addr), static_cast(len), static_cast(type), static_cast(prot)); return KernelMprotect(addr, len, prot); } int KYTY_SYSV_ABI KernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, int* num_entries_out, int flags) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t entries = %p\n" "\t num_entries = %d\n" "\t num_entries_out = %p\n" "\t flags = 0x%08" PRIx32 "\n", static_cast(entries), num_entries, static_cast(num_entries_out), static_cast(flags)); enum MemoryOpTypes { MAP_OP_MAP_DIRECT = 0, MAP_OP_UNMAP = 1, MAP_OP_PROTECT = 2, MAP_OP_MAP_FLEXIBLE = 3, MAP_OP_TYPE_PROTECT = 4, }; if (entries == nullptr || num_entries < 0) { return KERNEL_ERROR_EINVAL; } int processed = 0; for (int i = 0; i < num_entries; i++, processed++) { auto* entry = &entries[i]; LOGF("\t [%d] start = %p, offset = 0x%016" PRIx64 ", length = 0x%016" PRIx64 ", prot = 0x%02" PRIx32 ", type = 0x%02" PRIx32 ", op = %d\n", i, entry->start, entry->offset, entry->length, static_cast(static_cast(entry->protection)), static_cast(static_cast(entry->type)), entry->operation); if (entry->length == 0 || entry->operation < MAP_OP_MAP_DIRECT || entry->operation > MAP_OP_TYPE_PROTECT) { break; } int result = OK; switch (entry->operation) { case MAP_OP_MAP_DIRECT: result = KernelMapNamedDirectMemory(&entry->start, entry->length, entry->protection, flags, static_cast(entry->offset), 0, "anon"); break; case MAP_OP_UNMAP: result = KernelMunmap(reinterpret_cast(entry->start), entry->length); break; case MAP_OP_PROTECT: result = KernelMprotect(entry->start, entry->length, entry->protection); break; case MAP_OP_MAP_FLEXIBLE: result = KernelMapNamedFlexibleMemory(&entry->start, entry->length, entry->protection, flags, "anon"); break; case MAP_OP_TYPE_PROTECT: result = KernelMtypeprotect(entry->start, entry->length, entry->type, entry->protection); break; default: result = KERNEL_ERROR_EINVAL; break; } if (result != OK) { if (num_entries_out != nullptr) { *num_entries_out = processed; } return result; } } if (num_entries_out != nullptr) { *num_entries_out = processed; } return (processed == num_entries ? OK : KERNEL_ERROR_EINVAL); } int KYTY_SYSV_ABI KernelBatchMap(KernelBatchMapEntry* entries, int num_entries, int* num_entries_out) { constexpr int GUEST_MAP_FIXED = 0x10; return KernelBatchMap2(entries, num_entries, num_entries_out, GUEST_MAP_FIXED); } static bool IsAligned(uint64_t value, uint64_t alignment) { return alignment == 0 || (value & (alignment - 1u)) == 0; } static bool IsPowerOfTwo(uint64_t value) { return value != 0 && (value & (value - 1u)) == 0; } static void MemoryPoolSubtractCommitted(uint64_t len) { auto current = g_memory_pool_committed.load(std::memory_order_relaxed); while (current != 0) { const auto next = (current > len ? current - len : 0); if (g_memory_pool_committed.compare_exchange_weak(current, next, std::memory_order_relaxed)) { return; } } } int KYTY_SYSV_ABI KernelMemoryPoolExpand(int64_t search_start, int64_t search_end, size_t len, size_t alignment, int64_t* phys_addr_out) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); constexpr uint64_t POOL_PAGE_SIZE = 0x10000; if (search_start < 0 || search_end <= search_start || len == 0 || (len & (POOL_PAGE_SIZE - 1u)) != 0 || phys_addr_out == nullptr || (alignment != 0 && (!IsPowerOfTwo(alignment) || (alignment & (POOL_PAGE_SIZE - 1u)) != 0))) { return KERNEL_ERROR_EINVAL; } if (static_cast(search_end - search_start) < len) { return KERNEL_ERROR_ENOMEM; } const auto effective_alignment = (alignment != 0 ? alignment : POOL_PAGE_SIZE); uint64_t phys_addr = 0; if (!g_physical_memory->Alloc(static_cast(search_start), static_cast(search_end), len, effective_alignment, &phys_addr, 0, true)) { return KERNEL_ERROR_ENOMEM; } g_pooled_memory->Expand(phys_addr, len); *phys_addr_out = static_cast(phys_addr); LOGF("\t search_start = 0x%016" PRIx64 "\n" "\t search_end = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t alignment = 0x%016" PRIx64 "\n" "\t phys_addr = 0x%016" PRIx64 "\n", search_start, search_end, static_cast(len), static_cast(alignment), phys_addr); return OK; } int KYTY_SYSV_ABI KernelMemoryPoolReserve(void* addr_in, size_t len, size_t alignment, int flags, void** addr_out) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t addr_in = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t alignment = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n" "\t addr_out = %p\n", reinterpret_cast(addr_in), static_cast(len), static_cast(alignment), static_cast(flags), static_cast(addr_out)); constexpr uint64_t POOL_RESERVE_ALIGNMENT = 0x200000; if (addr_out == nullptr || len == 0 || !IsAligned(len, POOL_RESERVE_ALIGNMENT)) { return KERNEL_ERROR_EINVAL; } if (alignment != 0 && (!IsPowerOfTwo(alignment) || !IsAligned(alignment, POOL_RESERVE_ALIGNMENT))) { return KERNEL_ERROR_EINVAL; } void* out_addr = addr_in; const auto reserve_alignment = (alignment != 0 ? alignment : POOL_RESERVE_ALIGNMENT); const int ret = KernelReserveVirtualRange(&out_addr, len, flags, reserve_alignment); if (ret == OK) { const auto out_vaddr = reinterpret_cast(out_addr); VirtualRanges::Range reserved_range {}; if (!g_virtual_ranges->Query(out_vaddr, 0, &reserved_range) || reserved_range.start != out_vaddr || reserved_range.size != len || !IsReservedRangeType(reserved_range.type)) { return KERNEL_ERROR_EBUSY; } g_virtual_ranges->Remove(out_vaddr, len); if (!g_virtual_ranges->Add(out_vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, reserved_range.name, false, reserved_range.placeholder_backed)) { g_virtual_ranges->Add(out_vaddr, len, reserved_range.offset, reserved_range.protection, reserved_range.memory_type, reserved_range.type, reserved_range.name, reserved_range.committed_from_reserved, reserved_range.placeholder_backed); return KERNEL_ERROR_EBUSY; } *addr_out = out_addr; LOGF("\t out_addr = 0x%016" PRIx64 "\n", reinterpret_cast(out_addr)); } return ret; } int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t type = 0x%08" PRIx32 "\n" "\t prot = 0x%08" PRIx32 "\n" "\t flags = 0x%08" PRIx32 "\n", reinterpret_cast(addr), static_cast(len), static_cast(type), static_cast(prot), static_cast(flags)); constexpr uint64_t POOL_COMMIT_ALIGNMENT = 0x10000; constexpr int PROT_CPU_EXEC = 0x04; if (addr == nullptr || len == 0 || !IsAligned(len, POOL_COMMIT_ALIGNMENT) || (prot & PROT_CPU_EXEC) != 0) { return KERNEL_ERROR_EINVAL; } VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; if (!DecodeMemoryProtection(prot, &mode, &gpu_mode)) { return KERNEL_ERROR_EINVAL; } const auto vaddr = reinterpret_cast(addr); VirtualRanges::Range old_range {}; if (!g_virtual_ranges->Query(vaddr, 0, &old_range) || old_range.type != VirtualRangeType::PoolReserved) { return KERNEL_ERROR_EACCES; } if (!g_virtual_ranges->ConsumeReserved(vaddr, len, VirtualRangeType::PoolReserved)) { return KERNEL_ERROR_EACCES; } std::vector mappings; if (!g_pooled_memory->Allocate(vaddr, len, gpu_mode, &mappings)) { g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name, false, old_range.placeholder_backed); return KERNEL_ERROR_ENOMEM; } std::vector mapped; auto rollback = [&]() { if (!UnmapPooledBackingTransactional(mapped, mode)) { EXIT("failed to roll back pooled-memory backing maps\n"); } GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess; if (!g_pooled_memory->Release(vaddr, len, &rollback_gpu_mode)) { EXIT("failed to release pooled-memory rollback allocation\n"); } g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name, false, old_range.placeholder_backed); }; for (const auto& mapping: mappings) { auto failure_reason = DirectMemoryBacking::FailureReason::None; const bool placeholder_ready = g_placeholder_address_space->Consume(mapping.vaddr, mapping.size); const bool ok = placeholder_ready ? g_direct_memory_backing->MapExistingPlaceholderFixed( mapping.vaddr, mapping.size, mapping.phys_addr, mode, &failure_reason) : g_direct_memory_backing->MapFixed(mapping.vaddr, mapping.size, mapping.phys_addr, mode, &failure_reason); if (!ok) { if (placeholder_ready) { g_placeholder_address_space->AddFree(mapping.vaddr, mapping.size); } LOGF_COLOR(Log::Color::Red, "\t pool backing map failed: %s\n", DirectMemoryBacking::GetFailureReasonName(failure_reason)); rollback(); return KERNEL_ERROR_ENOMEM; } mapped.push_back(mapping); } if (!g_virtual_ranges->Add(vaddr, len, 0, prot, type, VirtualRangeType::Pooled, old_range.name, true, old_range.placeholder_backed)) { rollback(); return KERNEL_ERROR_EBUSY; } MapGpuRange(vaddr, len, gpu_mode); if (g_alloc_callback != nullptr) { g_alloc_callback(vaddr, len); } g_memory_pool_committed.fetch_add(len, std::memory_order_relaxed); return OK; } int KYTY_SYSV_ABI KernelMemoryPoolDecommit(void* addr, size_t len, int flags) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); LOGF("\t addr = 0x%016" PRIx64 "\n" "\t len = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n", reinterpret_cast(addr), static_cast(len), static_cast(flags)); constexpr uint64_t POOL_COMMIT_ALIGNMENT = 0x10000; if (addr == nullptr || len == 0 || !IsAligned(len, POOL_COMMIT_ALIGNMENT)) { return KERNEL_ERROR_EINVAL; } const auto vaddr = reinterpret_cast(addr); if (UINT64_MAX - vaddr < len) { return KERNEL_ERROR_EINVAL; } const auto end = vaddr + len; auto scan = vaddr; while (scan < end) { VirtualRanges::Range scan_range {}; if (!g_virtual_ranges->Query(scan, 0, &scan_range) || (scan_range.type != VirtualRangeType::Pooled && scan_range.type != VirtualRangeType::PoolReserved)) { return KERNEL_ERROR_EACCES; } const auto next = std::min(end, scan_range.start + scan_range.size); if (next <= scan) { return KERNEL_ERROR_EACCES; } scan = next; } VirtualRanges::Range old_range {}; if (!g_virtual_ranges->Query(vaddr, 0, &old_range)) { return KERNEL_ERROR_EACCES; } const auto chunk_len = std::min(len, old_range.size - (vaddr - old_range.start)); if (old_range.type == VirtualRangeType::PoolReserved) { return chunk_len < len ? KernelMemoryPoolDecommit(reinterpret_cast(vaddr + chunk_len), len - chunk_len, flags) : OK; } if (old_range.type != VirtualRangeType::Pooled) { return KERNEL_ERROR_EACCES; } if (chunk_len < len) { const int ret = KernelMemoryPoolDecommit(addr, chunk_len, flags); return ret == OK ? KernelMemoryPoolDecommit(reinterpret_cast(vaddr + chunk_len), len - chunk_len, flags) : ret; } std::vector mappings; if (!g_pooled_memory->Query(vaddr, len, &mappings)) { return KERNEL_ERROR_EACCES; } VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; GpuAccessMode decoded_gpu = GpuAccessMode::NoAccess; if (!DecodeMemoryProtection(old_range.protection, &mode, &decoded_gpu)) { return KERNEL_ERROR_EACCES; } UnmapGpuRange(vaddr, len, decoded_gpu); if (!UnmapPooledBackingTransactional(mappings, mode)) { EXIT("pooled-memory backing transaction failed after GPU unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", vaddr, len); } GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; if (!g_pooled_memory->Release(vaddr, len, &gpu_mode)) { EXIT("failed to release decommitted pooled-memory range\n"); } g_virtual_ranges->Remove(vaddr, len); old_range.placeholder_backed = true; g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name, false, old_range.placeholder_backed); if (g_free_callback != nullptr) { g_free_callback(vaddr, len); } MemoryPoolSubtractCommitted(len); return OK; } int KYTY_SYSV_ABI KernelMemoryPoolBatch(const KernelMemoryPoolBatchEntry* entries, int num_entries, int* num_entries_out, int flags) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); if (entries == nullptr || num_entries < 0) { return KERNEL_ERROR_EINVAL; } enum MemoryPoolOp { POOL_OP_COMMIT = 1, POOL_OP_DECOMMIT = 2, POOL_OP_PROTECT = 3, POOL_OP_TYPE_PROTECT = 4, POOL_OP_MOVE = 5, }; int processed = 0; int result = OK; for (int i = 0; i < num_entries; i++, processed++) { const auto& entry = entries[i]; switch (entry.op) { case POOL_OP_COMMIT: result = KernelMemoryPoolCommit(entry.commit.addr, entry.commit.len, entry.commit.type, entry.commit.prot, entry.flags); break; case POOL_OP_DECOMMIT: result = KernelMemoryPoolDecommit(entry.decommit.addr, entry.decommit.len, entry.flags); break; case POOL_OP_PROTECT: result = KernelMprotect(entry.protect.addr, entry.protect.len, entry.protect.prot); break; case POOL_OP_TYPE_PROTECT: result = KernelMtypeprotect(entry.type_protect.addr, entry.type_protect.len, entry.type_protect.type, entry.type_protect.prot); break; case POOL_OP_MOVE: default: result = KERNEL_ERROR_EINVAL; break; } if (result != OK) { break; } } if (num_entries_out != nullptr) { *num_entries_out = processed; } (void)flags; return result; } int KYTY_SYSV_ABI KernelMemoryPoolGetBlockStats(KernelMemoryPoolBlockStats* output, size_t output_size) { PRINT_NAME(); std::lock_guard memory_operation_lock(g_memory_operation_mutex); if (output == nullptr && output_size != 0) { return KERNEL_ERROR_EFAULT; } KernelMemoryPoolBlockStats stats {}; constexpr uint64_t BLOCK_SIZE = 0x10000; const uint64_t committed = g_memory_pool_committed.load(std::memory_order_relaxed); const uint64_t available = (g_pooled_memory != nullptr ? g_pooled_memory->Available() : 0); stats.available_flushed_blocks = static_cast(available / BLOCK_SIZE); stats.available_cached_blocks = 0; stats.allocated_flushed_blocks = static_cast(committed / BLOCK_SIZE); stats.allocated_cached_blocks = 0; const auto copy_size = std::min(output_size, sizeof(stats)); if (copy_size != 0) { std::memcpy(output, &stats, copy_size); } return OK; } } // namespace Libs::LibKernel::Memory