Transition memory handling from memfd to anonymous shared mappings

Memfd mappings are incompatible with KGSL user memory importing on older kernels, transition to shared anon mappings to avoid this.
This commit is contained in:
Billy Laws 2022-12-28 20:25:58 +00:00
parent cc3c869b9f
commit 81f3ff348c
7 changed files with 26 additions and 56 deletions

View File

@ -387,7 +387,7 @@ namespace skyline::gpu {
SynchronizeHost(true); // Will transition the Buffer to Clean SynchronizeHost(true); // Will transition the Buffer to Clean
dirtyState = DirtyState::GpuDirty; dirtyState = DirtyState::GpuDirty;
gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used
BlockAllCpuBackingWrites(); BlockAllCpuBackingWrites();
AdvanceSequence(); // The GPU will modify buffer contents so advance to the next sequence AdvanceSequence(); // The GPU will modify buffer contents so advance to the next sequence

View File

@ -728,7 +728,7 @@ namespace skyline::gpu {
// If a texture is Clean then we can just transition it to being GPU dirty and retrap it // If a texture is Clean then we can just transition it to being GPU dirty and retrap it
dirtyState = DirtyState::GpuDirty; dirtyState = DirtyState::GpuDirty;
gpu.state.nce->TrapRegions(*trapHandle, false); gpu.state.nce->TrapRegions(*trapHandle, false);
gpu.state.nce->PageOutRegions(*trapHandle); gpu.state.process->memory.FreeMemory(mirror);
return; return;
} else if (dirtyState != DirtyState::CpuDirty) { } else if (dirtyState != DirtyState::CpuDirty) {
return; // If the texture has not been modified on the CPU, there is no need to synchronize it return; // If the texture has not been modified on the CPU, there is no need to synchronize it
@ -756,7 +756,7 @@ namespace skyline::gpu {
std::scoped_lock lock{stateMutex}; std::scoped_lock lock{stateMutex};
if (dirtyState != DirtyState::CpuDirty && gpuDirty) if (dirtyState != DirtyState::CpuDirty && gpuDirty)
gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used
} }
} }
@ -771,7 +771,7 @@ namespace skyline::gpu {
if (gpuDirty && dirtyState == DirtyState::Clean) { if (gpuDirty && dirtyState == DirtyState::Clean) {
dirtyState = DirtyState::GpuDirty; dirtyState = DirtyState::GpuDirty;
gpu.state.nce->TrapRegions(*trapHandle, false); gpu.state.nce->TrapRegions(*trapHandle, false);
gpu.state.nce->PageOutRegions(*trapHandle); gpu.state.process->memory.FreeMemory(mirror);
return; return;
} else if (dirtyState != DirtyState::CpuDirty) { } else if (dirtyState != DirtyState::CpuDirty) {
return; return;
@ -793,7 +793,7 @@ namespace skyline::gpu {
std::scoped_lock lock{stateMutex}; std::scoped_lock lock{stateMutex};
if (dirtyState != DirtyState::CpuDirty && gpuDirty) if (dirtyState != DirtyState::CpuDirty && gpuDirty)
gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used
} }
} }

View File

@ -17,7 +17,7 @@ namespace skyline::kernel {
constexpr size_t RegionAlignment{1ULL << 21}; //!< The minimum alignment of a HOS memory region constexpr size_t RegionAlignment{1ULL << 21}; //!< The minimum alignment of a HOS memory region
constexpr size_t CodeRegionSize{4ULL * 1024 * 1024 * 1024}; //!< The assumed maximum size of the code region (4GiB) constexpr size_t CodeRegionSize{4ULL * 1024 * 1024 * 1024}; //!< The assumed maximum size of the code region (4GiB)
static std::pair<span<u8>, FileDescriptor> AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) { static span<u8> AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) {
span<u8> region{}; span<u8> region{};
size_t size{minSize}; size_t size{minSize};
@ -47,18 +47,11 @@ namespace skyline::kernel {
if (!region.valid()) if (!region.valid())
throw exception("Allocation failed"); throw exception("Allocation failed");
FileDescriptor memoryFd{static_cast<int>(syscall(__NR_memfd_create, "HOS-AS", MFD_CLOEXEC))}; // We need to use memfd directly as ASharedMemory doesn't always use it while we depend on it for FreeMemory (using FALLOC_FL_PUNCH_HOLE) to work auto result{mmap(reinterpret_cast<void *>(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_SHARED, -1, 0)};
if (memoryFd == -1)
throw exception("Failed to create memfd for guest address space: {}", strerror(errno));
if (ftruncate(memoryFd, static_cast<off_t>(size)) == -1)
throw exception("Failed to resize memfd for guest address space: {}", strerror(errno));
auto result{mmap(reinterpret_cast<void *>(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_SHARED, memoryFd, 0)};
if (result == MAP_FAILED) if (result == MAP_FAILED)
throw exception("Failed to mmap guest address space: {}", strerror(errno)); throw exception("Failed to mmap guest address space: {}", strerror(errno));
return {region, memoryFd}; return region;
} }
void MemoryManager::InitializeVmm(memory::AddressSpaceType type) { void MemoryManager::InitializeVmm(memory::AddressSpaceType type) {
@ -88,7 +81,7 @@ namespace skyline::kernel {
// Qualcomm KGSL (Kernel Graphic Support Layer/Kernel GPU driver) maps below 35-bits, reserving it causes KGSL to go OOM // Qualcomm KGSL (Kernel Graphic Support Layer/Kernel GPU driver) maps below 35-bits, reserving it causes KGSL to go OOM
static constexpr size_t KgslReservedRegionSize{1ULL << 35}; static constexpr size_t KgslReservedRegionSize{1ULL << 35};
if (type != memory::AddressSpaceType::AddressSpace36Bit) { if (type != memory::AddressSpaceType::AddressSpace36Bit) {
std::tie(base, memoryFd) = AllocateMappedRange(baseSize, RegionAlignment, KgslReservedRegionSize, addressSpace.size(), false); base = AllocateMappedRange(baseSize, RegionAlignment, KgslReservedRegionSize, addressSpace.size(), false);
chunks = { chunks = {
ChunkDescriptor{ ChunkDescriptor{
@ -110,8 +103,8 @@ namespace skyline::kernel {
code = base; code = base;
} else { } else {
std::tie(base, memoryFd) = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false); base = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false);
std::tie(codeBase36Bit, code36BitFd) = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast<size_t>(addressSpace.data()), true); codeBase36Bit = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast<size_t>(addressSpace.data()), true);
chunks = { chunks = {
ChunkDescriptor{ ChunkDescriptor{
@ -191,10 +184,12 @@ namespace skyline::kernel {
if (!util::IsPageAligned(offset) || !util::IsPageAligned(mapping.size())) if (!util::IsPageAligned(offset) || !util::IsPageAligned(mapping.size()))
throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", mapping.data(), mapping.end().base(), offset); throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", mapping.data(), mapping.end().base(), offset);
auto mirror{mmap(nullptr, mapping.size(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, memoryFd, static_cast<off_t>(offset))}; auto mirror{mremap(mapping.data(), 0, mapping.size(), MREMAP_MAYMOVE)};
if (mirror == MAP_FAILED) if (mirror == MAP_FAILED)
throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", mapping.data(), mapping.end().base(), offset, strerror(errno)); throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", mapping.data(), mapping.end().base(), offset, strerror(errno));
mprotect(mirror, mapping.size(), PROT_READ | PROT_WRITE | PROT_EXEC);
return span<u8>{reinterpret_cast<u8 *>(mirror), mapping.size()}; return span<u8>{reinterpret_cast<u8 *>(mirror), mapping.size()};
} }
@ -216,10 +211,12 @@ namespace skyline::kernel {
if (!util::IsPageAligned(offset) || !util::IsPageAligned(region.size())) if (!util::IsPageAligned(offset) || !util::IsPageAligned(region.size()))
throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", region.data(), region.end().base(), offset); throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", region.data(), region.end().base(), offset);
auto mirror{mmap(reinterpret_cast<u8 *>(mirrorBase) + mirrorOffset, region.size(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, memoryFd, static_cast<off_t>(offset))}; auto mirror{mremap(region.data(), 0, region.size(), MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast<u8 *>(mirrorBase) + mirrorOffset)};
if (mirror == MAP_FAILED) if (mirror == MAP_FAILED)
throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", region.data(), region.end().base(), offset, strerror(errno)); throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", region.data(), region.end().base(), offset, strerror(errno));
mprotect(mirror, region.size(), PROT_READ | PROT_WRITE | PROT_EXEC);
mirrorOffset += region.size(); mirrorOffset += region.size();
} }
@ -230,16 +227,12 @@ namespace skyline::kernel {
} }
void MemoryManager::FreeMemory(span<u8> memory) { void MemoryManager::FreeMemory(span<u8> memory) {
if (!base.contains(memory)) u8 *alignedStart{util::AlignUp(memory.data(), constant::PageSize)};
throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", memory.data(), memory.end().base()); u8 *alignedEnd{util::AlignDown(memory.end().base(), constant::PageSize)};
auto offset{static_cast<size_t>(memory.data() - base.data())}; if (alignedStart < alignedEnd)
if (!util::IsPageAligned(offset) || !util::IsPageAligned(memory.size())) if (madvise(alignedStart, static_cast<size_t>(alignedEnd - alignedStart), MADV_REMOVE) == -1)
throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", memory.data(), memory.end().base(), offset); throw exception("Failed to free memory: {}", strerror(errno)) ;
// We need to use fallocate(FALLOC_FL_PUNCH_HOLE) to free the backing memory rather than madvise(MADV_REMOVE) as the latter fails when the memory doesn't have write permissions, we generally need to free memory after reprotecting it to disallow accesses between the two calls which would cause UB
if (fallocate(*memoryFd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, static_cast<off_t>(offset), static_cast<off_t>(memory.size())) != 0)
throw exception("Failed to free memory at 0x{:X}-0x{:X} (0x{:X}): {}", memory.data(), memory.end().base(), offset, strerror(errno));
} }
void MemoryManager::InsertChunk(const ChunkDescriptor &chunk) { void MemoryManager::InsertChunk(const ChunkDescriptor &chunk) {

View File

@ -225,9 +225,6 @@ namespace skyline {
span<u8> stack{}; span<u8> stack{};
span<u8> tlsIo{}; //!< TLS/IO span<u8> tlsIo{}; //!< TLS/IO
FileDescriptor memoryFd{}; //!< The file descriptor of the memory backing for the entire guest address space
FileDescriptor code36BitFd{}; //!< The file descriptor of the memory backing for in the lower 36 bits of the address space for mapping code and stack on 36-bit guests
std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it's locked in shared mode by readers and exclusive mode by writers std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it's locked in shared mode by readers and exclusive mode by writers
MemoryManager(const DeviceState &state); MemoryManager(const DeviceState &state);
@ -258,7 +255,7 @@ namespace skyline {
span<u8> CreateMirrors(const std::vector<span<u8>> &regions); span<u8> CreateMirrors(const std::vector<span<u8>> &regions);
/** /**
* @brief Frees the underlying physical memory for a page-aligned mapping in the guest address space * @brief Frees the underlying physical memory for all full pages in the contained mapping
* @note All subsequent accesses to freed memory will return 0s * @note All subsequent accesses to freed memory will return 0s
*/ */
void FreeMemory(span<u8> memory); void FreeMemory(span<u8> memory);

View File

@ -58,7 +58,7 @@ namespace skyline::kernel::type {
if (guest.data() != map.data() && guest.size() != map.size()) if (guest.data() != map.data() && guest.size() != map.size())
throw exception("Unmapping KSharedMemory partially is not supported: Requested Unmap: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size()); throw exception("Unmapping KSharedMemory partially is not supported: Requested Unmap: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size());
if (mmap(map.data(), map.size(), PROT_NONE, MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast<off_t>(map.data() - memoryManager.base.data())) == MAP_FAILED) if (mmap(map.data(), map.size(), PROT_NONE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED)
throw exception("An error occurred while unmapping shared memory in guest: {}", strerror(errno)); throw exception("An error occurred while unmapping shared memory in guest: {}", strerror(errno));
guest = span<u8>{}; guest = span<u8>{};
@ -95,7 +95,7 @@ namespace skyline::kernel::type {
if (state.process && guest.valid()) { if (state.process && guest.valid()) {
auto &memoryManager{state.process->memory}; auto &memoryManager{state.process->memory};
if (objectType != KType::KTransferMemory) { if (objectType != KType::KTransferMemory) {
if (mmap(guest.data(), guest.size(), PROT_NONE, MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast<off_t>(guest.data() - memoryManager.base.data())) == MAP_FAILED) if (mmap(guest.data(), guest.size(), PROT_NONE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED)
Logger::Warn("An error occurred while unmapping shared memory: {}", strerror(errno)); Logger::Warn("An error occurred while unmapping shared memory: {}", strerror(errno));
state.process->memory.InsertChunk(ChunkDescriptor{ state.process->memory.InsertChunk(ChunkDescriptor{
@ -107,7 +107,7 @@ namespace skyline::kernel::type {
// KTransferMemory remaps the region with R/W permissions during destruction // KTransferMemory remaps the region with R/W permissions during destruction
constexpr memory::Permission UnborrowPermission{true, true, false}; constexpr memory::Permission UnborrowPermission{true, true, false};
if (mmap(guest.data(), guest.size(), UnborrowPermission.Get(), MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast<off_t>(guest.data() - memoryManager.base.data())) == MAP_FAILED) if (mmap(guest.data(), guest.size(), UnborrowPermission.Get(), MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED)
Logger::Warn("An error occurred while remapping transfer memory: {}", strerror(errno)); Logger::Warn("An error occurred while remapping transfer memory: {}", strerror(errno));
else if (!host.valid()) else if (!host.valid())
Logger::Warn("Expected host mapping of transfer memory to be valid during KTransferMemory destruction"); Logger::Warn("Expected host mapping of transfer memory to be valid during KTransferMemory destruction");

View File

@ -736,19 +736,6 @@ namespace skyline::nce {
ReprotectIntervals(handle->intervals, protection); ReprotectIntervals(handle->intervals, protection);
} }
void NCE::PageOutRegions(TrapHandle handle) {
TRACE_EVENT("host", "NCE::PageOutRegions");
std::scoped_lock lock{trapMutex};
for (auto region : handle->intervals) {
auto freeStart{util::AlignUp(region.start, constant::PageSize)}, freeEnd{util::AlignDown(region.end, constant::PageSize)}; // We want to avoid the first and last page as they may contain unrelated data
ssize_t freeSize{freeEnd - freeStart};
constexpr ssize_t MinimumPageoutSize{constant::PageSize}; //!< The minimum size to page out, we don't want to page out small intervals for performance reasons
if (freeSize > MinimumPageoutSize)
state.process->memory.FreeMemory(span<u8>{freeStart, static_cast<size_t>(freeSize)});
}
}
void NCE::RemoveTrap(TrapHandle handle) { void NCE::RemoveTrap(TrapHandle handle) {
TRACE_EVENT("host", "NCE::RemoveTrap"); TRACE_EVENT("host", "NCE::RemoveTrap");
std::scoped_lock lock{trapMutex}; std::scoped_lock lock{trapMutex};

View File

@ -150,13 +150,6 @@ namespace skyline::nce {
*/ */
void TrapRegions(TrapHandle handle, bool writeOnly); void TrapRegions(TrapHandle handle, bool writeOnly);
/**
* @brief Pages out the supplied trap region of memory (except border pages), any future accesses will return 0s
* @note This function is intended to be used after trapping reads to a region where the callback pages back in the data
* @note If the region is determined to be too small, this function will not do anything and is not meant to deterministically page out the region
*/
void PageOutRegions(TrapHandle handle);
/** /**
* @brief Removes protections from a region of memory * @brief Removes protections from a region of memory
*/ */