mirror of
https://github.com/mikage-emu/mikage-dev.git
synced 2025-01-07 22:10:59 +01:00
OS: Fix edge cases around buffer mapping
* Pages must be wholly mapped, starting and ending at 4K boundaries * PXI buffer mappings may be zero-sized * Intra-page offsets must be preserved * Generate multiple PXI buffer regions when crossing virtual memory mapping boundaries
This commit is contained in:
parent
b262167f58
commit
29c3ffabb1
1 changed files with 55 additions and 34 deletions
|
@ -3539,9 +3539,27 @@ void OS::TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply) {
|
||||||
|
|
||||||
auto base_physical_chunk = ResolveVirtualAddrWithSize(source.GetParentProcess(), buffer_address);
|
auto base_physical_chunk = ResolveVirtualAddrWithSize(source.GetParentProcess(), buffer_address);
|
||||||
if (!base_physical_chunk) {
|
if (!base_physical_chunk) {
|
||||||
source.GetLogger()->error("{}Failed to resolve virtual address {:#x}",
|
if (buffer_size == 0) {
|
||||||
ThreadPrinter{source}, buffer_address);
|
// NOTE: Some games use zero-length buffers. Naively we
|
||||||
SVCBreak(source, BreakReason::Panic);
|
// could just not map any buffer in the target
|
||||||
|
// process in that case, but services may expect to
|
||||||
|
// be able to forward the buffers to other services.
|
||||||
|
// A notable example of this is Super Mario 3D Land,
|
||||||
|
// which sends zero-byte file read requests to FS,
|
||||||
|
// which in turn forwards those requests to PXI.
|
||||||
|
// Usually, games provide valid buffer addresses
|
||||||
|
// despite the zero buffer size. However for am:net's
|
||||||
|
// ImportCertificates, a typical usage pattern is
|
||||||
|
// zero-size with a nullptr.
|
||||||
|
// TODO: Review if LLE FS avoids PXI calls for null buffers.
|
||||||
|
// If it does, HLE FS should copy this behavior so
|
||||||
|
// that an error can be raised here instead.
|
||||||
|
base_physical_chunk.emplace(0, 0);
|
||||||
|
} else {
|
||||||
|
throw Mikage::Exceptions::NotImplemented(
|
||||||
|
"{}Failed to resolve virtual address {:#x}",
|
||||||
|
ThreadPrinter{source}, buffer_address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (base_physical_chunk->second >= buffer_size) {
|
if (base_physical_chunk->second >= buffer_size) {
|
||||||
// Create one single PXI entry covering the entire buffer
|
// Create one single PXI entry covering the entire buffer
|
||||||
|
@ -3609,30 +3627,26 @@ void OS::TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply) {
|
||||||
case IPC::TranslationDescriptor::Type::MapReadWrite:
|
case IPC::TranslationDescriptor::Type::MapReadWrite:
|
||||||
{
|
{
|
||||||
// NOTE: Mapped buffers may be of size zero, in which case we
|
// NOTE: Mapped buffers may be of size zero, in which case we
|
||||||
// don't actually map any memory - so this lookup may be
|
// don't actually map any memory.
|
||||||
// expected to fail in that case
|
// TODO: Test if the kernel should still map at least one page in
|
||||||
// TODO: If the mapped buffer size is zero, chances are the kernel
|
// the target process in this case.
|
||||||
// is still expected to map at least one page in the target
|
const VAddr buffer_source_addr = source.ReadTLS(tls_offset);
|
||||||
// process. This needs to be tested!
|
|
||||||
VAddr buffer_source_addr = source.ReadTLS(tls_offset);
|
if (!descriptor.map_buffer.size) {
|
||||||
auto buffer_paddr_opt = source.GetParentProcess().ResolveVirtualAddr(buffer_source_addr);
|
|
||||||
if (!buffer_paddr_opt) {
|
|
||||||
tls_offset += 4;
|
tls_offset += 4;
|
||||||
break; // TODO: Remove. Needed to open HOME Menu in system version 9.0.0 after updating from 4.5.0
|
break;
|
||||||
throw std::runtime_error(fmt::format("Failed to resolve IPC buffer address {:#x}", buffer_source_addr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Some games use zero-length buffers. Naively we could just
|
// Extend buffer size to partially covered pages
|
||||||
// not map any buffer in the target process in that case,
|
// NOTE: Two alignment considerations are made here:
|
||||||
// but services may expect to be able to forward the buffers
|
// 1. The offset of the dest pointer into the first page must match the source pointer
|
||||||
// to other services. Hence we always map at least the first
|
// 2. The mapped data must end on a page boundary.
|
||||||
// byte of the input buffer.
|
// This is particularly important for the sub-page
|
||||||
// A notable example of this is Super Mario 3D Land, which
|
// allocations done in HLE modules, due to which e.g.
|
||||||
// sends zero-byte file read requests to FS, which in turn
|
// APT::Wrap fails to properly forward its buffers to PS
|
||||||
// forwards those requests to PXI.
|
// if they're allocated within the same virtual memory page
|
||||||
// Note that all games this was observed in do provide valid
|
// Side TODO: Stop sub-page allocating in HLE modules
|
||||||
// buffer addresses despite the zero buffer size.
|
uint32_t mapped_buffer_size = ((buffer_source_addr + descriptor.map_buffer.size + 0xfff) & ~0xfff) - (buffer_source_addr & ~0xfff);
|
||||||
/*const */uint32_t mapped_buffer_size = std::max<uint32_t>(1, descriptor.map_buffer.size);
|
|
||||||
|
|
||||||
if (is_reply) {
|
if (is_reply) {
|
||||||
// Unmap buffer from the source process. It seems that the
|
// Unmap buffer from the source process. It seems that the
|
||||||
|
@ -3654,7 +3668,7 @@ void OS::TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply) {
|
||||||
source.GetLogger()->info("{}{}", data, (descriptor.map_buffer.size > max_size) ? "..." : "");
|
source.GetLogger()->info("{}{}", data, (descriptor.map_buffer.size > max_size) ? "..." : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = source.GetParentProcess().UnmapVirtualMemory(buffer_source_addr, mapped_buffer_size);
|
bool success = source.GetParentProcess().UnmapVirtualMemory(buffer_source_addr & ~0xfff, mapped_buffer_size);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw std::runtime_error("Couldn't unmap IPC buffer memory");
|
throw std::runtime_error("Couldn't unmap IPC buffer memory");
|
||||||
}
|
}
|
||||||
|
@ -3666,10 +3680,6 @@ void OS::TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply) {
|
||||||
// TODO: Setup proper permissions
|
// TODO: Setup proper permissions
|
||||||
// TODO: Page-align the mappings??
|
// TODO: Page-align the mappings??
|
||||||
|
|
||||||
// Align buffer size up to the nearest page. This is needed since e.g. APT::Wrap fails to properly forward its buffers to PS if they're allocated within the same virtual memory page
|
|
||||||
// TODO: Stop sub-page allocating instead.
|
|
||||||
mapped_buffer_size = (mapped_buffer_size + 0xfff) & ~0xfff;
|
|
||||||
|
|
||||||
auto buffer_dest_vaddr_opt = dest.GetParentProcess().FindAvailableVirtualMemory(mapped_buffer_size, 0x10000000, 0x20000000);
|
auto buffer_dest_vaddr_opt = dest.GetParentProcess().FindAvailableVirtualMemory(mapped_buffer_size, 0x10000000, 0x20000000);
|
||||||
if (!buffer_dest_vaddr_opt) {
|
if (!buffer_dest_vaddr_opt) {
|
||||||
throw std::runtime_error("Couldn't find any free virtual memory to map IPC buffer");
|
throw std::runtime_error("Couldn't find any free virtual memory to map IPC buffer");
|
||||||
|
@ -3681,13 +3691,24 @@ void OS::TranslateIPCMessage(Thread& source, Thread& dest, bool is_reply) {
|
||||||
? MemoryPermissions::Write
|
? MemoryPermissions::Write
|
||||||
: MemoryPermissions::ReadWrite;
|
: MemoryPermissions::ReadWrite;
|
||||||
|
|
||||||
// TODO: Currently, we can have mappings like "VAddr [0x10000000;0x100036c0] to PAddr [0x245cc9a0;0x245d0060]", but we really shouldn't change alignment of data like this...
|
for (uint32_t bytes_mapped = 0; bytes_mapped < mapped_buffer_size;) {
|
||||||
bool success = dest.GetParentProcess().MapVirtualMemory(*buffer_paddr_opt, mapped_buffer_size, *buffer_dest_vaddr_opt, permissions);
|
auto buffer_paddr_pair_opt = ResolveVirtualAddrWithSize(source.GetParentProcess(), ((buffer_source_addr + bytes_mapped) & ~0xfff));
|
||||||
if (!success) {
|
if (!buffer_paddr_pair_opt) {
|
||||||
throw std::runtime_error("Couldn't map virtual memory for IPC buffer");
|
throw std::runtime_error(fmt::format("Failed to resolve IPC buffer address {:#x}", buffer_source_addr));
|
||||||
|
}
|
||||||
|
uint32_t bytes_to_map = std::min<uint32_t>(buffer_paddr_pair_opt->second, mapped_buffer_size - bytes_mapped);
|
||||||
|
|
||||||
|
// TODO: Currently, we can have mappings like "VAddr [0x10000000;0x100036c0] to PAddr [0x245cc9a0;0x245d0060]", but we really shouldn't change alignment of data like this...
|
||||||
|
bool success = dest.GetParentProcess().MapVirtualMemory(buffer_paddr_pair_opt->first, bytes_to_map, *buffer_dest_vaddr_opt + bytes_mapped, permissions);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Couldn't map virtual memory for IPC buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_mapped += bytes_to_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest.WriteTLS(tls_offset, *buffer_dest_vaddr_opt);
|
// Write target address. Care must be taken to preserve the page offset into the source data
|
||||||
|
dest.WriteTLS(tls_offset, *buffer_dest_vaddr_opt + (buffer_source_addr & 0xfff));
|
||||||
|
|
||||||
source.GetLogger()->info( "{}Mapping buffer for input data at {:#010x} in thread {}:",
|
source.GetLogger()->info( "{}Mapping buffer for input data at {:#010x} in thread {}:",
|
||||||
ThreadPrinter{source}, buffer_source_addr, ThreadPrinter{dest});
|
ThreadPrinter{source}, buffer_source_addr, ThreadPrinter{dest});
|
||||||
|
|
Loading…
Reference in a new issue