Sort of working OS allocator, except freeing is impossible

This commit is contained in:
wheremyfoodat 2022-09-16 23:36:55 +03:00
parent 2128d5060b
commit 93f5ec7bb4
8 changed files with 186 additions and 40 deletions

View file

@ -5,9 +5,9 @@
using namespace ELFIO;
std::optional<u32> Memory::loadELF(std::filesystem::path& path) {
std::optional<u32> Memory::loadELF(std::ifstream& file) {
elfio reader;
if (!reader.load(path.string())) {
if (!file.good() || !reader.load(file)) {
printf("Woops failed to load ELF\n");
return std::nullopt;
}
@ -39,8 +39,17 @@ std::optional<u32> Memory::loadELF(std::filesystem::path& path) {
return std::nullopt;
}
u32 fcramAddr = vaddr - VirtualAddrs::ExecutableStart;
if (memorySize & pageMask) {
// Round up the size of the ELF segment to a page (4KB) boundary, as the OS can only alloc this way
memorySize = (memorySize + pageSize - 1) & -pageSize;
Helpers::warn("Rounding ELF segment size to %08X\n", memorySize);
}
u32 fcramAddr = findPaddr(memorySize).value();
std::memcpy(&fcram[fcramAddr], data, fileSize);
// Allocate the segment on the OS side
allocateMemory(vaddr, fcramAddr, memorySize, true);
}
return static_cast<u32>(reader.get_entry());

View file

@ -23,6 +23,7 @@ static constexpr bool isAligned(u32 value) {
// Result ControlMemory(u32* outaddr, u32 addr0, u32 addr1, u32 size,
// MemoryOperation operation, MemoryPermission permissions)
// This has a weird ABI documented here https://www.3dbrew.org/wiki/Kernel_ABI
// TODO: Does this need to write to outaddr?
void Kernel::controlMemory() {
u32 operation = regs[0]; // The base address is written here
u32 addr0 = regs[1];
@ -48,17 +49,22 @@ void Kernel::controlMemory() {
Helpers::panic("ControlMemory: Unaligned parameters\nAddr0: %08X\nAddr1: %08X\nSize: %08X", addr0, addr1, size);
}
printf("ControlMemory(addr0 = %08X, addr1 = %08X, size = %X, operation = %X (%c%c%c)%s\n",
printf("ControlMemory(addr0 = %08X, addr1 = %08X, size = %08X, operation = %X (%c%c%c)%s\n",
addr0, addr1, size, operation, r ? 'r' : '-', w ? 'w' : '-', x ? 'x' : '-', linear ? ", linear" : ""
);
switch (operation & 0xFF) {
case Operation::Commit:
case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear);
if (!address.has_value())
Helpers::panic("ControlMemory: Failed to allocate memory");
regs[1] = address.value();
break;
}
default: Helpers::panic("ControlMemory: unknown operation %X\n", operation);
}
regs[0] = SVCResult::Success;
regs[1] = 0xF3180131;
}

View file

@ -83,7 +83,7 @@ void Kernel::getResourceLimitCurrentValues() {
s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName) {
const auto data = static_cast<ResourceLimits*>(limit->data);
switch (resourceName) {
case ResourceType::Commit: return data->currentCommit;
case ResourceType::Commit: return mem.usedUserMemory;
default: Helpers::panic("Attempted to get current value of unknown kernel resource: %d\n", resourceName);
}
}

View file

@ -1,43 +1,116 @@
#include "memory.hpp"
#include <cassert>
Memory::Memory() {
fcram = new uint8_t[128_MB]();
fcram = new uint8_t[FCRAM_SIZE]();
readTable.resize(totalPageCount, 0);
writeTable.resize(totalPageCount, 0);
}
constexpr u32 fcramPageCount = 128_MB / pageSize;
void Memory::reset() {
// Unallocate all memory
usedFCRAMPages.reset();
usedUserMemory = 0_MB;
// Map 63MB of FCRAM in the executable section as read/write
// TODO: This should probably be read-only, but making it r/w shouldn't hurt?
// Because if that were the case then writes would cause data aborts, so games wouldn't write to read-only memory
u32 executablePageCount = VirtualAddrs::MaxExeSize / pageSize;
u32 page = VirtualAddrs::ExecutableStart / pageSize;
for (u32 i = 0; i < executablePageCount; i++) {
const auto pointer = (uintptr_t)&fcram[i * pageSize];
readTable[page] = pointer;
writeTable[page++] = pointer;
for (u32 i = 0; i < totalPageCount; i++) {
readTable[i] = 0;
writeTable[i] = 0;
}
// Map stack pages as R/W
// We have 16KB for the stack, so we allocate the last 4 pages of FCRAM for the stack
u32 stackPageCount = VirtualAddrs::StackSize / pageSize;
u32 fcramStackPage = fcramPageCount - 4;
page = VirtualAddrs::StackBottom / pageSize;
// We have 16KB for the stack, so we allocate the last 16KB of APPLICATION FCRAM for the stack
u32 basePaddrForStack = FCRAM_APPLICATION_SIZE - VirtualAddrs::StackSize;
allocateMemory(VirtualAddrs::StackBottom, basePaddrForStack, VirtualAddrs::StackSize, true);
for (u32 i = 0; i < 4; i++) {
auto pointer = (uintptr_t)&fcram[fcramStackPage * pageSize];
readTable[page] = pointer;
writeTable[page++] = pointer;
fcramStackPage++;
// And map 4KB of FCRAM before the stack for TLS
u32 basePaddrForTLS = basePaddrForStack - VirtualAddrs::TLSSize;
allocateMemory(VirtualAddrs::TLSBase, basePaddrForTLS, VirtualAddrs::TLSSize, true);
}
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x) {
// Kernel-allocated memory & size must always be aligned to a page boundary
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland
assert(isAligned(vaddr) && isAligned(paddr) && isAligned(size));
assert(size <= FCRAM_APPLICATION_SIZE);
assert(usedUserMemory + size <= FCRAM_APPLICATION_SIZE);
assert(paddr + size <= FCRAM_APPLICATION_SIZE);
// Amount of available user FCRAM pages and FCRAM pages to allocate respectively
const u32 availablePageCount = (FCRAM_APPLICATION_SIZE - usedUserMemory) / pageSize;
const u32 neededPageCount = size / pageSize;
assert(availablePageCount >= neededPageCount);
// If the vaddr is 0 that means we need to select our own
// Depending on whether our mapping should be linear or not we allocate from one of the 2 typical heap spaces
// We don't plan on implementing freeing any time soon, so we can pick added userUserMemory to the vaddr base to
// Get the full vaddr.
// TODO: Fix this
if (vaddr == 0) {
vaddr = usedUserMemory + (linear ? VirtualAddrs::LinearHeapStart : VirtualAddrs::NormalHeapStart);
}
// Map 1 page of FCRAM for thread-local storage
u32 fcramTLSPage = fcramPageCount - 5;
page = VirtualAddrs::TLSBase / pageSize;
usedUserMemory += size;
auto pointer = (uintptr_t)&fcram[fcramTLSPage * pageSize];
readTable[page] = pointer;
writeTable[page++] = pointer;
// If the paddr is 0, that means we need to select our own
// TODO: Fix. This method always tries to allocate blocks linearly.
// However, if the allocation is non-linear, the panic will trigger when it shouldn't.
// Non-linear allocation needs special handling
if (paddr == 0) {
std::optional<u32> newPaddr = findPaddr(size);
if (!newPaddr.has_value())
Helpers::panic("Failed to find paddr");
paddr = newPaddr.value();
assert(paddr + size <= FCRAM_APPLICATION_SIZE);
}
// Do linear mapping
u32 virtualPage = vaddr >> pageShift;
u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary
for (u32 i = 0; i < neededPageCount; i++) {
if (r) {
readTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
}
if (w) {
writeTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
}
// Mark FCRAM page as allocated and go on
usedFCRAMPages[physPage] = true;
virtualPage++;
physPage++;
}
return vaddr;
}
// Find a paddr which we can use for allocating "size" bytes
std::optional<u32> Memory::findPaddr(u32 size) {
const u32 neededPages = size / pageSize;
// The FCRAM page we're testing to see if it's appropriate to use
u32 candidatePage = 0;
// The number of linear available pages we could find starting from this candidate page.
// If this ends up >= than neededPages then the paddr is good (ie we can use the candidate page as a base address)
u32 counter = 0;
for (u32 i = 0; i < FCRAM_PAGE_COUNT; i++) {
if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate
candidatePage = i + 1;
counter = 0;
}
else { // Our candidate page has 1 mor
counter++;
// Check if there's enough free memory to use this page
if (counter == neededPages) {
return candidatePage * pageSize;
}
}
}
// Couldn't find any page :(
return std::nullopt;
}
u8 Memory::read8(u32 vaddr) {