@ -119,13 +119,9 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
auto MapSegment = [ & ] ( CodeSet : : Segment & segment , VMAPermission permissions ,
auto MapSegment = [ & ] ( CodeSet : : Segment & segment , VMAPermission permissions ,
MemoryState memory_state ) {
MemoryState memory_state ) {
auto vma = vm_manager
HeapAllocate ( segment . addr , segment . size , permissions , memory_state , true ) ;
. MapMemoryBlock ( segment . addr , codeset - > memory , segment . offset , segment . size ,
Memory : : WriteBlock ( * this , segment . addr , codeset - > memory - > data ( ) + segment . offset ,
memory_state )
segment . size ) ;
. Unwrap ( ) ;
vm_manager . Reprotect ( vma , permissions ) ;
misc_memory_used + = segment . size ;
memory_region - > used + = segment . size ;
} ;
} ;
// Map CodeSet segments
// Map CodeSet segments
@ -134,13 +130,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
MapSegment ( codeset - > DataSegment ( ) , VMAPermission : : ReadWrite , MemoryState : : Private ) ;
MapSegment ( codeset - > DataSegment ( ) , VMAPermission : : ReadWrite , MemoryState : : Private ) ;
// Allocate and map stack
// Allocate and map stack
vm_manager
HeapAllocate ( Memory : : HEAP_VADDR_END - stack_size , stack_size , VMAPermission : : ReadWrite ,
. MapMemoryBlock ( Memory : : HEAP_VADDR_END - stack_size ,
MemoryState : : Locked , true ) ;
std : : make_shared < std : : vector < u8 > > ( stack_size , 0 ) , 0 , stack_size ,
MemoryState : : Locked )
. Unwrap ( ) ;
misc_memory_used + = stack_size ;
memory_region - > used + = stack_size ;
// Map special address mappings
// Map special address mappings
kernel . MapSharedPages ( vm_manager ) ;
kernel . MapSharedPages ( vm_manager ) ;
@ -168,44 +159,55 @@ VAddr Process::GetLinearHeapLimit() const {
return GetLinearHeapBase ( ) + memory_region - > size ;
return GetLinearHeapBase ( ) + memory_region - > size ;
}
}
ResultVal < VAddr > Process : : HeapAllocate ( VAddr target , u32 size , VMAPermission perms ) {
ResultVal < VAddr > Process : : HeapAllocate ( VAddr target , u32 size , VMAPermission perms ,
MemoryState memory_state , bool skip_range_check ) {
LOG_DEBUG ( Kernel , " Allocate heap target={:08X}, size={:08X} " , target , size ) ;
if ( target < Memory : : HEAP_VADDR | | target + size > Memory : : HEAP_VADDR_END | |
if ( target < Memory : : HEAP_VADDR | | target + size > Memory : : HEAP_VADDR_END | |
target + size < target ) {
target + size < target ) {
if ( ! skip_range_check ) {
LOG_ERROR ( Kernel , " Invalid heap address " ) ;
return ERR_INVALID_ADDRESS ;
return ERR_INVALID_ADDRESS ;
}
}
if ( heap_memory = = nullptr ) {
// Initialize heap
heap_memory = std : : make_shared < std : : vector < u8 > > ( ) ;
heap_start = heap_end = target ;
}
}
// If necessary, expand backing vector to cover new heap extents.
auto vma = vm_manager . FindVMA ( target ) ;
if ( target < heap_start ) {
if ( vma - > second . type ! = VMAType : : Free | | vma - > second . base + vma - > second . size < target + size ) {
heap_memory - > insert ( begin ( * heap_memory ) , heap_start - target , 0 ) ;
LOG_ERROR ( Kernel , " Trying to allocate already allocated memory " ) ;
heap_start = target ;
return ERR_INVALID_ADDRESS_STATE ;
vm_manager . RefreshMemoryBlockMappings ( heap_memory . get ( ) ) ;
}
}
if ( target + size > heap_end ) {
heap_memory - > insert ( end ( * heap_memory ) , ( target + size ) - heap_end , 0 ) ;
auto allocated_fcram = memory_region - > HeapAllocate ( size ) ;
heap_end = target + size ;
if ( allocated_fcram . empty ( ) ) {
vm_manager . RefreshMemoryBlockMappings ( heap_memory . get ( ) ) ;
LOG_ERROR ( Kernel , " Not enough space " ) ;
return ERR_OUT_OF_HEAP_MEMORY ;
}
}
ASSERT ( heap_end - heap_start = = heap_memory - > size ( ) ) ;
CASCADE_RESULT ( auto vma , vm_manager . MapMemoryBlock ( target , heap_memory , target - heap_start ,
// Maps heap block by block
size , MemoryState : : Private ) ) ;
VAddr interval_target = target ;
vm_manager . Reprotect ( vma , perms ) ;
for ( const auto & interval : allocated_fcram ) {
u32 interval_size = interval . upper ( ) - interval . lower ( ) ;
LOG_DEBUG ( Kernel , " Allocated FCRAM region lower={:08X}, upper={:08X} " , interval . lower ( ) ,
interval . upper ( ) ) ;
std : : fill ( Memory : : fcram . begin ( ) + interval . lower ( ) ,
Memory : : fcram . begin ( ) + interval . upper ( ) , 0 ) ;
auto vma = vm_manager . MapBackingMemory (
interval_target , Memory : : fcram . data ( ) + interval . lower ( ) , interval_size , memory_state ) ;
ASSERT ( vma . Succeeded ( ) ) ;
vm_manager . Reprotect ( vma . Unwrap ( ) , perms ) ;
interval_target + = interval_size ;
}
heap_used + = size ;
memory _used + = size ;
memory_region - > used + = size ;
resource_limit- > current_commit + = size ;
return MakeResult < VAddr > ( heap_end - size ) ;
return MakeResult < VAddr > ( target ) ;
}
}
ResultCode Process : : HeapFree ( VAddr target , u32 size ) {
ResultCode Process : : HeapFree ( VAddr target , u32 size ) {
LOG_DEBUG ( Kernel , " Free heap target={:08X}, size={:08X} " , target , size ) ;
if ( target < Memory : : HEAP_VADDR | | target + size > Memory : : HEAP_VADDR_END | |
if ( target < Memory : : HEAP_VADDR | | target + size > Memory : : HEAP_VADDR_END | |
target + size < target ) {
target + size < target ) {
LOG_ERROR ( Kernel , " Invalid heap address " ) ;
return ERR_INVALID_ADDRESS ;
return ERR_INVALID_ADDRESS ;
}
}
@ -213,59 +215,72 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
return RESULT_SUCCESS ;
return RESULT_SUCCESS ;
}
}
ResultCode result = vm_manager . UnmapRange ( target , size ) ;
// Free heaps block by block
if ( result . IsError ( ) )
CASCADE_RESULT ( auto backing_blocks , vm_manager . GetBackingBlocksForRange ( target , size ) ) ;
return result ;
for ( const auto [ backing_memory , block_size ] : backing_blocks ) {
memory_region - > Free ( Memory : : GetFCRAMOffset ( backing_memory ) , block_size ) ;
}
heap_used - = size ;
ResultCode result = vm_manager . UnmapRange ( target , size ) ;
memory_region - > used - = size ;
ASSERT ( result . IsSuccess ( ) ) ;
memory_used - = size ;
resource_limit - > current_commit - = size ;
return RESULT_SUCCESS ;
return RESULT_SUCCESS ;
}
}
ResultVal < VAddr > Process : : LinearAllocate ( VAddr target , u32 size , VMAPermission perms ) {
ResultVal < VAddr > Process : : LinearAllocate ( VAddr target , u32 size , VMAPermission perms ) {
auto & linheap_memory = memory_region - > linear_heap_memory ;
LOG_DEBUG ( Kernel , " Allocate linear heap target={:08X}, size={:08X} " , target , size ) ;
u32 physical_offset ;
VAddr heap_end = GetLinearHeapBase ( ) + ( u32 ) linheap_memory - > size ( ) ;
// Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address),
// but explicit addresses are also accepted and respected.
if ( target = = 0 ) {
if ( target = = 0 ) {
target = heap_end ;
auto offset = memory_region - > LinearAllocate ( size ) ;
if ( ! offset ) {
LOG_ERROR ( Kernel , " Not enough space " ) ;
return ERR_OUT_OF_HEAP_MEMORY ;
}
}
physical_offset = * offset ;
if ( target < GetLinearHeapBase ( ) | | target + size > GetLinearHeapLimit ( ) | | target > heap_end | |
target = physical_offset + GetLinearHeapAreaAddress ( ) ;
} else {
if ( target < GetLinearHeapBase ( ) | | target + size > GetLinearHeapLimit ( ) | |
target + size < target ) {
target + size < target ) {
LOG_ERROR ( Kernel , " Invalid linear heap address " ) ;
return ERR_INVALID_ADDRESS ;
return ERR_INVALID_ADDRESS ;
}
}
// Expansion of the linear heap is only allowed if you do an allocation immediately at its
// Kernel would crash/return error when target doesn't meet some requirement.
// end. It's possible to free gaps in the middle of the heap and then reallocate them later,
// It seems that target is required to follow immediately after the allocated linear heap,
// but expansions are only allowed at the end.
// or cover the entire hole if there is any.
if ( target = = heap_end ) {
// Right now we just ignore these checks because they are still unclear. Further more,
linheap_memory - > insert ( linheap_memory - > end ( ) , size , 0 ) ;
// games and homebrew only ever seem to pass target = 0 here (which lets the kernel decide
vm_manager . RefreshMemoryBlockMappings ( linheap_memory . get ( ) ) ;
// the address), so this not important.
physical_offset = target - GetLinearHeapAreaAddress ( ) ; // relative to FCRAM
if ( ! memory_region - > LinearAllocate ( physical_offset , size ) ) {
LOG_ERROR ( Kernel , " Trying to allocate already allocated memory " ) ;
return ERR_INVALID_ADDRESS_STATE ;
}
}
}
// TODO(yuriks): As is, this lets processes map memory allocated by other processes from the
u8 * backing_memory = Memory : : fcram . data ( ) + physical_offset ;
// same region. It is unknown if or how the 3DS kernel checks against this.
std : : size_t offset = target - GetLinearHeapBase ( ) ;
CASCADE_RESULT ( auto vma , vm_manager . MapMemoryBlock ( target , linheap_memory , offset , size ,
MemoryState : : Continuous ) ) ;
vm_manager . Reprotect ( vma , perms ) ;
linear_heap_used + = size ;
std : : fill ( backing_memory , backing_memory + size , 0 ) ;
memory_region - > used + = size ;
auto vma = vm_manager . MapBackingMemory ( target , backing_memory , size , MemoryState : : Continuous ) ;
ASSERT ( vma . Succeeded ( ) ) ;
vm_manager . Reprotect ( vma . Unwrap ( ) , perms ) ;
memory_used + = size ;
resource_limit - > current_commit + = size ;
LOG_DEBUG ( Kernel , " Allocated at target={:08X} " , target ) ;
return MakeResult < VAddr > ( target ) ;
return MakeResult < VAddr > ( target ) ;
}
}
ResultCode Process : : LinearFree ( VAddr target , u32 size ) {
ResultCode Process : : LinearFree ( VAddr target , u32 size ) {
auto & linheap_memory = memory_region - > linear_heap_memory ;
LOG_DEBUG ( Kernel , " Free linear heap target={:08X}, size={:08X} " , target , size ) ;
if ( target < GetLinearHeapBase ( ) | | target + size > GetLinearHeapLimit ( ) | |
if ( target < GetLinearHeapBase ( ) | | target + size > GetLinearHeapLimit ( ) | |
target + size < target ) {
target + size < target ) {
LOG_ERROR ( Kernel , " Invalid linear heap address " ) ;
return ERR_INVALID_ADDRESS ;
return ERR_INVALID_ADDRESS ;
}
}
@ -273,30 +288,73 @@ ResultCode Process::LinearFree(VAddr target, u32 size) {
return RESULT_SUCCESS ;
return RESULT_SUCCESS ;
}
}
VAddr heap_end = GetLinearHeapBase ( ) + ( u32 ) linheap_memory - > size ( ) ;
ResultCode result = vm_manager . UnmapRange ( target , size ) ;
if ( target + size > heap_end ) {
if ( result . IsError ( ) ) {
LOG_ERROR ( Kernel , " Trying to free already freed memory " ) ;
return result ;
}
memory_used - = size ;
resource_limit - > current_commit - = size ;
u32 physical_offset = target - GetLinearHeapAreaAddress ( ) ; // relative to FCRAM
memory_region - > Free ( physical_offset , size ) ;
return RESULT_SUCCESS ;
}
ResultCode Process : : Map ( VAddr target , VAddr source , u32 size , VMAPermission perms ) {
LOG_DEBUG ( Kernel , " Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X} " , target ,
source , size , static_cast < u8 > ( perms ) ) ;
if ( source < Memory : : HEAP_VADDR | | source + size > Memory : : HEAP_VADDR_END | |
source + size < source ) {
LOG_ERROR ( Kernel , " Invalid source address " ) ;
return ERR_INVALID_ADDRESS ;
}
// TODO(wwylele): check target address range. Is it also restricted to heap region?
auto vma = vm_manager . FindVMA ( target ) ;
if ( vma - > second . type ! = VMAType : : Free | | vma - > second . base + vma - > second . size < target + size ) {
LOG_ERROR ( Kernel , " Trying to map to already allocated memory " ) ;
return ERR_INVALID_ADDRESS_STATE ;
return ERR_INVALID_ADDRESS_STATE ;
}
}
ResultCode result = vm_manager . UnmapRange ( target , size ) ;
// Mark source region as Aliased
if ( result . IsError ( ) )
CASCADE_CODE ( vm_manager . ChangeMemoryState ( source , size , MemoryState : : Private ,
return result ;
VMAPermission : : ReadWrite , MemoryState : : Aliased ,
VMAPermission : : ReadWrite ) ) ;
linear_heap_used - = size ;
CASCADE_RESULT ( auto backing_blocks , vm_manager . GetBackingBlocksForRange ( source , size ) ) ;
memory_region - > used - = size ;
VAddr interval_target = target ;
for ( const auto [ backing_memory , block_size ] : backing_blocks ) {
auto target_vma = vm_manager . MapBackingMemory ( interval_target , backing_memory , block_size ,
MemoryState : : Alias ) ;
interval_target + = block_size ;
}
if ( target + size = = heap_end ) {
return RESULT_SUCCESS ;
// End of linear heap has been freed, so check what's the last allocated block in it and
}
// reduce the size.
ResultCode Process : : Unmap ( VAddr target , VAddr source , u32 size , VMAPermission perms ) {
auto vma = vm_manager . FindVMA ( target ) ;
LOG_DEBUG ( Kernel , " Unmap memory target={:08X}, source={:08X}, size={:08X}, perms={:08X} " ,
ASSERT ( vma ! = vm_manager . vma_map . end ( ) ) ;
target , source , size , static_cast < u8 > ( perms ) ) ;
ASSERT ( vma - > second . type = = VMAType : : Free ) ;
if ( source < Memory : : HEAP_VADDR | | source + size > Memory : : HEAP_VADDR_END | |
VAddr new_end = vma - > second . base ;
source + size < source ) {
if ( new_end > = GetLinearHeapBase ( ) ) {
LOG_ERROR ( Kernel , " Invalid source address " ) ;
linheap_memory - > resize ( new_end - GetLinearHeapBase ( ) ) ;
return ERR_INVALID_ADDRESS ;
}
}
}
// TODO(wwylele): check target address range. Is it also restricted to heap region?
// TODO(wwylele): check that the source and the target are actually a pair created by Map
// Should return error 0xD8E007F5 in this case
CASCADE_CODE ( vm_manager . UnmapRange ( target , size ) ) ;
// Change back source region state. Note that the permission is reprotected according to param
CASCADE_CODE ( vm_manager . ChangeMemoryState ( source , size , MemoryState : : Aliased ,
VMAPermission : : None , MemoryState : : Private , perms ) ) ;
return RESULT_SUCCESS ;
return RESULT_SUCCESS ;
}
}