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

@ -13,6 +13,7 @@ class CP15 final : public Dynarmic::A32::Coprocessor {
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
u32 threadStoragePointer; // Pointer to thread-local storage
u32 dummy; // MCR writes here for registers whose values are ignored
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1,
CoprocReg CRd, CoprocReg CRn,
@ -22,7 +23,10 @@ class CP15 final : public Dynarmic::A32::Coprocessor {
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
CoprocReg CRm, unsigned opc2) override {
Helpers::panic("CP15: CompileSendOneWord\n");
if (!two && opc1 == 0 && CRn == CoprocReg::C7 && CRm == CoprocReg::C10 && opc2 == 5) {
return &dummy; // TODO: Find out what this is. Some sort of memory barrier reg.
}
Helpers::panic("CP15: CompileSendOneWord\nopc1: %d CRn: %d CRm: %d opc2: %d\n", opc1, (int)CRn, (int)CRm, opc2);
}
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override {
@ -36,7 +40,7 @@ class CP15 final : public Dynarmic::A32::Coprocessor {
return &threadStoragePointer;
}
Helpers::panic("CP15: CompileGetOneWord");
Helpers::panic("CP15: CompileGetOneWord\nopc1: %d CRn: %d CRm: %d opc2: %d\n", opc1, (int)CRn, (int)CRm, opc2);
}
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override {

View file

@ -1,6 +1,7 @@
#pragma once
#include <filesystem>
#include <fstream>
#include "cpu.hpp"
#include "helpers.hpp"
@ -8,6 +9,10 @@
#include "SFML/Window.hpp"
#include "SFML/Graphics.hpp"
enum class ROMType {
None, ELF, Cart
};
class Emulator {
CPU cpu;
Memory memory;
@ -16,6 +21,10 @@ class Emulator {
sf::RenderWindow window;
static constexpr u32 width = 400;
static constexpr u32 height = 240 * 2; // * 2 because 2 screens
ROMType romType = ROMType::None;
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
std::ifstream loadedROM;
public:
Emulator() : window(sf::VideoMode(width, height), "Alber", sf::Style::Default, sf::ContextSettings(0, 0, 0, 4, 3)),
@ -31,4 +40,5 @@ public:
void runFrame();
bool loadELF(std::filesystem::path& path);
bool loadELF(std::ifstream& file);
};

View file

@ -1,5 +1,6 @@
#pragma once
#include <filesystem>
#include <bitset>
#include <fstream>
#include <optional>
#include <vector>
#include "helpers.hpp"
@ -16,8 +17,12 @@ namespace VirtualAddrs {
StackBottom = 0x0FFFC000,
StackSize = 0x4000,
NormalHeapStart = 0x08000000,
LinearHeapStart = 0x14000000,
// Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on
TLSBase = 0xFF400000
TLSBase = 0xFF400000,
TLSSize = 0x1000
};
}
@ -30,12 +35,23 @@ class Memory {
static constexpr u32 pageSize = 1 << pageShift;
static constexpr u32 pageMask = pageSize - 1;
static constexpr u32 totalPageCount = 1 << (32 - pageShift);
static constexpr u32 FCRAM_SIZE = 128_MB;
static constexpr u32 FCRAM_APPLICATION_SIZE = 64_MB;
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
std::optional<u32> findPaddr(u32 size);
public:
u32 usedUserMemory = 0;
Memory();
void reset();
void* getReadPointer(u32 address);
void* getWritePointer(u32 address);
std::optional<u32> loadELF(std::filesystem::path& path);
std::optional<u32> loadELF(std::ifstream& file);
u8 read8(u32 vaddr);
u16 read16(u32 vaddr);
@ -46,4 +62,16 @@ public:
void write16(u32 vaddr, u16 value);
void write32(u32 vaddr, u32 value);
void write64(u32 vaddr, u64 value);
// Allocate "size" bytes of RAM starting from physical FCRAM address "paddr" (We pick it ourself if paddr == 0)
// And map them to virtual address "vaddr" (We also pick it ourself if vaddr == 0).
// If the "linear" flag is on, the paddr pages must be adjacent in FCRAM
// r, w, x: Permissions for the allocated memory
// Returns the vaddr the FCRAM was mapped to or nullopt if allocation failed
std::optional<u32> allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r = true, bool w = true, bool x = true);
// Returns whether "addr" is aligned to a page (4096 byte) boundary
static constexpr bool isAligned(u32 addr) {
return (addr & pageMask) == 0;
}
};

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) {

View file

@ -1,8 +1,14 @@
#include "emulator.hpp"
void Emulator::reset() {
memory.reset();
cpu.reset();
kernel.reset();
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
if (romType == ROMType::ELF) { // Reload ELF if we're using one
loadELF(loadedROM);
}
}
void Emulator::step() {}
@ -35,11 +41,21 @@ void Emulator::runFrame() {
}
bool Emulator::loadELF(std::filesystem::path& path) {
std::optional<u32> entrypoint = memory.loadELF(path);
loadedROM.open(path, std::ios_base::binary); // Open ROM in binary mode
romType = ROMType::ELF;
return loadELF(loadedROM);
}
bool Emulator::loadELF(std::ifstream& file) {
// Rewind ifstream
loadedROM.clear();
loadedROM.seekg(0);
std::optional<u32> entrypoint = memory.loadELF(loadedROM);
if (!entrypoint.has_value())
return false;
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
cpu.setReg(15, entrypoint.value()); // Set initial PC
return true;
}