mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 11:11:49 +01:00
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:
parent
cc3c869b9f
commit
81f3ff348c
@ -387,7 +387,7 @@ namespace skyline::gpu {
|
||||
SynchronizeHost(true); // Will transition the Buffer to Clean
|
||||
|
||||
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();
|
||||
AdvanceSequence(); // The GPU will modify buffer contents so advance to the next sequence
|
||||
|
@ -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
|
||||
dirtyState = DirtyState::GpuDirty;
|
||||
gpu.state.nce->TrapRegions(*trapHandle, false);
|
||||
gpu.state.nce->PageOutRegions(*trapHandle);
|
||||
gpu.state.process->memory.FreeMemory(mirror);
|
||||
return;
|
||||
} else if (dirtyState != DirtyState::CpuDirty) {
|
||||
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};
|
||||
|
||||
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) {
|
||||
dirtyState = DirtyState::GpuDirty;
|
||||
gpu.state.nce->TrapRegions(*trapHandle, false);
|
||||
gpu.state.nce->PageOutRegions(*trapHandle);
|
||||
gpu.state.process->memory.FreeMemory(mirror);
|
||||
return;
|
||||
} else if (dirtyState != DirtyState::CpuDirty) {
|
||||
return;
|
||||
@ -793,7 +793,7 @@ namespace skyline::gpu {
|
||||
std::scoped_lock lock{stateMutex};
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace skyline::kernel {
|
||||
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)
|
||||
|
||||
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{};
|
||||
size_t size{minSize};
|
||||
|
||||
@ -47,18 +47,11 @@ namespace skyline::kernel {
|
||||
if (!region.valid())
|
||||
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
|
||||
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)};
|
||||
auto result{mmap(reinterpret_cast<void *>(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_SHARED, -1, 0)};
|
||||
if (result == MAP_FAILED)
|
||||
throw exception("Failed to mmap guest address space: {}", strerror(errno));
|
||||
|
||||
return {region, memoryFd};
|
||||
return region;
|
||||
}
|
||||
|
||||
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
|
||||
static constexpr size_t KgslReservedRegionSize{1ULL << 35};
|
||||
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 = {
|
||||
ChunkDescriptor{
|
||||
@ -110,8 +103,8 @@ namespace skyline::kernel {
|
||||
code = base;
|
||||
|
||||
} else {
|
||||
std::tie(base, memoryFd) = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false);
|
||||
std::tie(codeBase36Bit, code36BitFd) = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast<size_t>(addressSpace.data()), true);
|
||||
base = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false);
|
||||
codeBase36Bit = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast<size_t>(addressSpace.data()), true);
|
||||
|
||||
chunks = {
|
||||
ChunkDescriptor{
|
||||
@ -191,10 +184,12 @@ namespace skyline::kernel {
|
||||
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);
|
||||
|
||||
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)
|
||||
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()};
|
||||
}
|
||||
|
||||
@ -216,10 +211,12 @@ namespace skyline::kernel {
|
||||
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);
|
||||
|
||||
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)
|
||||
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();
|
||||
}
|
||||
|
||||
@ -230,16 +227,12 @@ namespace skyline::kernel {
|
||||
}
|
||||
|
||||
void MemoryManager::FreeMemory(span<u8> memory) {
|
||||
if (!base.contains(memory))
|
||||
throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", memory.data(), memory.end().base());
|
||||
u8 *alignedStart{util::AlignUp(memory.data(), constant::PageSize)};
|
||||
u8 *alignedEnd{util::AlignDown(memory.end().base(), constant::PageSize)};
|
||||
|
||||
auto offset{static_cast<size_t>(memory.data() - base.data())};
|
||||
if (!util::IsPageAligned(offset) || !util::IsPageAligned(memory.size()))
|
||||
throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", memory.data(), memory.end().base(), offset);
|
||||
|
||||
// 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));
|
||||
if (alignedStart < alignedEnd)
|
||||
if (madvise(alignedStart, static_cast<size_t>(alignedEnd - alignedStart), MADV_REMOVE) == -1)
|
||||
throw exception("Failed to free memory: {}", strerror(errno)) ;
|
||||
}
|
||||
|
||||
void MemoryManager::InsertChunk(const ChunkDescriptor &chunk) {
|
||||
|
@ -225,9 +225,6 @@ namespace skyline {
|
||||
span<u8> stack{};
|
||||
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
|
||||
|
||||
MemoryManager(const DeviceState &state);
|
||||
@ -258,7 +255,7 @@ namespace skyline {
|
||||
span<u8> CreateMirrors(const std::vector<span<u8>> ®ions);
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
void FreeMemory(span<u8> memory);
|
||||
|
@ -58,7 +58,7 @@ namespace skyline::kernel::type {
|
||||
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());
|
||||
|
||||
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));
|
||||
|
||||
guest = span<u8>{};
|
||||
@ -95,7 +95,7 @@ namespace skyline::kernel::type {
|
||||
if (state.process && guest.valid()) {
|
||||
auto &memoryManager{state.process->memory};
|
||||
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));
|
||||
|
||||
state.process->memory.InsertChunk(ChunkDescriptor{
|
||||
@ -107,7 +107,7 @@ namespace skyline::kernel::type {
|
||||
// KTransferMemory remaps the region with R/W permissions during destruction
|
||||
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));
|
||||
else if (!host.valid())
|
||||
Logger::Warn("Expected host mapping of transfer memory to be valid during KTransferMemory destruction");
|
||||
|
@ -736,19 +736,6 @@ namespace skyline::nce {
|
||||
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) {
|
||||
TRACE_EVENT("host", "NCE::RemoveTrap");
|
||||
std::scoped_lock lock{trapMutex};
|
||||
|
@ -150,13 +150,6 @@ namespace skyline::nce {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user