[Kernel] Stub MapMemoryBlock, hopefully touch the memory allocator for the last time in a while

This commit is contained in:
wheremyfoodat 2022-09-19 14:15:24 +03:00
parent 45c016d12e
commit 765b51696e
6 changed files with 233 additions and 109 deletions

View file

@ -17,7 +17,9 @@ namespace KernelHandles {
LCD = 0xFFFF8006, // LCD service
MinServiceHandle = APT,
MaxServiceHandle = LCD
MaxServiceHandle = LCD,
GSPSharedMemHandle = MaxServiceHandle + 1 // Handle for the GSP shared memory
};
// Returns whether "handle" belongs to one of the OS services

View file

@ -51,13 +51,13 @@ namespace KernelMemoryTypes {
// I assume this is referring to a single piece of allocated memory? If it's for pages, it makes no sense.
// If it's for multiple allocations, it also makes no sense
struct MemoryInfo {
u32 baseVaddr; // Base process virtual address. TODO: What even is this
u32 baseAddr; // Base process virtual address. Used as a paddr in lockedMemoryInfo instead
u32 size; // Of what?
u32 perms; // Is this referring to a single page or?
u32 state;
u32 end() { return baseVaddr + size; }
MemoryInfo(u32 baseVaddr, u32 size, u32 perms, u32 state) : baseVaddr(baseVaddr), size(size)
u32 end() { return baseAddr + size; }
MemoryInfo(u32 baseAddr, u32 size, u32 perms, u32 state) : baseAddr(baseAddr), size(size)
, perms(perms), state(state) {}
};
}
@ -67,7 +67,11 @@ class Memory {
// Our dynarmic core uses page tables for reads and writes with 4096 byte pages
std::vector<uintptr_t> readTable, writeTable;
// This tracks our OS' memory allocations
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
// This tracks our physical memory reservations when the memory is not actually mapped to a vaddr
std::vector<KernelMemoryTypes::MemoryInfo> lockedMemoryInfo;
static constexpr u32 pageShift = 12;
static constexpr u32 pageSize = 1 << pageShift;
@ -84,6 +88,7 @@ class Memory {
public:
u32 usedUserMemory = 0;
std::optional<int> gspMemIndex; // Index of GSP shared mem in lockedMemoryInfo or nullopt if it's already reserved
Memory();
void reset();
@ -95,24 +100,37 @@ public:
u16 read16(u32 vaddr);
u32 read32(u32 vaddr);
u64 read64(u32 vaddr);
std::string readString(u32 vaddr, u32 maxCharacters);
void write8(u32 vaddr, u8 value);
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;
}
std::string readString(u32 vaddr, u32 maxCharacters);
// 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
// adjustAddrs: If it's true paddr == 0 or vaddr == 0 tell the allocator to pick its own addresses. Used for eg svc ControlMemory
// 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,
bool adjustsAddrs = false);
KernelMemoryTypes::MemoryInfo queryMemory(u32 vaddr);
// For internal use:
// Reserve FCRAM linearly starting from physical address "paddr" (paddr == 0 is NOT special) with a size of "size"
// Without actually mapping the memory to a vaddr
// r, w, x: Permissions for the reserved memory
// Returns the index of the allocation in lockedMemoryInfo if allocation succeeded and nullopt if it failed
std::optional<int> reserveMemory(u32 paddr, u32 size, bool r, bool w, bool x);
// Map GSP shared memory to virtual address vaddr with permissions "myPerms"
// The kernel has a second permission parameter in MapMemoryBlock but not sure what's used for
// TODO: Find out
void mapGSPSharedMemory(u32 vaddr, u32 myPerms, u32 otherPerms);
};

View file

@ -7,6 +7,7 @@ void Kernel::serviceSVC(u32 svc) {
case 0x01: controlMemory(); break;
case 0x02: queryMemory(); break;
case 0x17: createEvent(); break;
case 0x1F: mapMemoryBlock(); break;
case 0x21: createAddressArbiter(); break;
case 0x23: svcCloseHandle(); break;
case 0x2D: connectToPort(); break;

View file

@ -55,7 +55,7 @@ void Kernel::controlMemory() {
switch (operation & 0xFF) {
case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x);
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x, true);
if (!address.has_value())
Helpers::panic("ControlMemory: Failed to allocate memory");
@ -70,6 +70,7 @@ void Kernel::controlMemory() {
}
// Result QueryMemory(MemoryInfo* memInfo, PageInfo* pageInfo, u32 addr)
// TODO: Is this SVC supposed to write to memory or...?
void Kernel::queryMemory() {
const u32 memInfo = regs[0];
const u32 pageInfo = regs[1];
@ -83,10 +84,38 @@ void Kernel::queryMemory() {
const auto info = mem.queryMemory(addr);
regs[0] = SVCResult::Success;
regs[1] = info.baseAddr;
regs[2] = info.size;
regs[3] = info.perms;
regs[4] = info.state;
regs[5] = 0; // page flags
mem.write32(memInfo, info.baseVaddr); // Set memInfo->baseVaddr
/*
mem.write32(memInfo, info.baseAddr); // Set memInfo->baseVaddr
mem.write32(memInfo + 4, info.size); // Set memInfo->size
mem.write32(memInfo + 8, info.baseVaddr); // Set memInfo->perms
mem.write32(memInfo + 8, info.perms); // Set memInfo->perms
mem.write32(memInfo + 12, info.state); // Set memInfo->state
mem.write32(pageInfo, 0); // Set pageInfo->flags to 0
*/
}
// Result MapMemoryBlock(Handle memblock, u32 addr, MemoryPermission myPermissions, MemoryPermission otherPermission)
void Kernel::mapMemoryBlock() {
const Handle block = regs[0];
const u32 addr = regs[1];
const u32 myPerms = regs[2];
const u32 otherPerms = regs[3];
printf("MapMemoryBlock(block = %d, addr = %08X, myPerms = %X, otherPerms = %X\n", block, addr, myPerms, otherPerms);
if (!isAligned(addr)) [[unlikely]] {
Helpers::panic("MapMemoryBlock: Unaligned address");
}
if (block == KernelHandles::GSPSharedMemHandle) {
mem.mapGSPSharedMemory(addr, myPerms, otherPerms);
} else {
Helpers::panic("MapMemoryBlock where the handle does not refer to GSP memory");
}
regs[0] = SVCResult::Success;
}

View file

@ -30,96 +30,15 @@ void Memory::reset() {
// 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);
}
usedUserMemory += size;
// 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++;
}
// Back up the info for this allocation in our memoryInfo vector
u32 perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0);
memoryInfo.push_back(std::move(MemoryInfo(vaddr, size, perms, KernelMemoryTypes::Reserved)));
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;
// Reserve 4KB of memory for GSP shared memory
constexpr u32 gspMemSize = 0x1000;
const std::optional<u32> gspMemPaddr = findPaddr(gspMemSize);
if (!gspMemPaddr.has_value()) Helpers::panic("Couldn't find paddr for GSP shared memory");
auto temp = reserveMemory(gspMemPaddr.value(), gspMemSize, true, true, false);
if (!temp.has_value()) Helpers::panic("Couldn't reserve FCRAM for GSP shared memory");
gspMemIndex = temp.value();
}
u8 Memory::read8(u32 vaddr) {
@ -174,7 +93,15 @@ void Memory::write8(u32 vaddr, u8 value) {
}
void Memory::write16(u32 vaddr, u16 value) {
Helpers::panic("Unimplemented 16-bit write, addr: %08X, val: %04X", vaddr, value);
const u32 page = vaddr >> pageShift;
const u32 offset = vaddr & pageMask;
uintptr_t pointer = writeTable[page];
if (pointer != 0) [[likely]] {
*(u16*)(pointer + offset) = value;
} else {
Helpers::panic("Unimplemented 16-bit write, addr: %08X, val: %08X", vaddr, value);
}
}
void Memory::write32(u32 vaddr, u32 value) {
@ -228,6 +155,123 @@ std::string Memory::readString(u32 address, u32 maxSize) {
return string;
}
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x,
bool adjustAddrs) {
// 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 && adjustAddrs) {
vaddr = usedUserMemory + (linear ? VirtualAddrs::LinearHeapStart : VirtualAddrs::NormalHeapStart);
}
usedUserMemory += size;
// 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 && adjustAddrs) {
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++;
}
// Back up the info for this allocation in our memoryInfo vector
u32 perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0);
memoryInfo.push_back(std::move(MemoryInfo(vaddr, size, perms, KernelMemoryTypes::Reserved)));
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_APPLICATION_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;
}
std::optional<int> Memory::reserveMemory(u32 paddr, u32 size, bool r, bool w, bool x) {
if (!isAligned(paddr) || !isAligned(size)) {
Helpers::panic("Memory::reserveMemory: Physical address or size is not page aligned. Paddr: %08X, size: %08X", paddr, size);
; }
const u32 pageCount = size / pageSize; // Number of pages we need to reserve
const u32 startingPage = paddr / pageSize; // The first page of FCRAM we'll start allocating from
// Assert that all of the pages are not yet reserved. TODO: Smarter memory allocator
for (u32 i = 0; i < pageCount; i++) {
if (usedFCRAMPages[startingPage + i])
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
usedFCRAMPages[i] = true;
}
// Back up the info for this allocation in our memoryInfo vector
u32 perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0);
u32 ret = lockedMemoryInfo.size();
// When we reserve but don't map memory, we store the alloc info in lockedMemoryInfo instead of memoryInfo
lockedMemoryInfo.push_back(std::move(MemoryInfo(paddr, size, perms, KernelMemoryTypes::Locked)));
usedUserMemory += size;
return ret;
}
// The way I understand how the kernel's QueryMemory is supposed to work is that you give it a vaddr
// And the kernel looks up the memory allocations it's performed, finds which one it belongs in and returns its info?
// TODO: Verify this
@ -235,11 +279,41 @@ MemoryInfo Memory::queryMemory(u32 vaddr) {
// Check each allocation
for (auto& alloc : memoryInfo) {
// Check if the memory address belongs in this allocation and return the info if so
if (vaddr >= alloc.baseVaddr && vaddr < alloc.end()) {
if (vaddr >= alloc.baseAddr && vaddr < alloc.end()) {
return alloc;
}
}
// Otherwise, if this vaddr was never allocated
return MemoryInfo(vaddr, 0, 0, KernelMemoryTypes::Free);
// TODO: I think this is meant to return how much memory starting here is free as the size?
return MemoryInfo(vaddr, pageSize, 0, KernelMemoryTypes::Free);
}
void Memory::mapGSPSharedMemory(u32 vaddr, u32 myPerms, u32 otherPerms) {
if (!gspMemIndex.has_value())
Helpers::panic("Tried to map already mapped GSP memory");
const u32 index = gspMemIndex.value(); // Index of GSP shared memory in lockedMemoryInfo
const u32 paddr = lockedMemoryInfo[index].baseAddr;
const u32 size = lockedMemoryInfo[index].size;
// This memory was not actually used, we just didn't want QueryMemory, getResourceLimitCurrentValues and such
// To report memory sizes wrongly. We subtract the size from the usedUserMemory size so allocateMemory won't break
usedUserMemory -= size;
// Wipe the GSP memory allocation from existence
gspMemIndex = std::nullopt;
//std::erase(lockedMemoryInfo, index);
if (myPerms == 0x10000000) {
myPerms = 3;
Helpers::panic("Memory::mapGSPSharedMemory with DONTCARE perms");
}
bool r = myPerms & 0b001;
bool w = myPerms & 0b010;
bool x = myPerms & 0b100;
const auto result = allocateMemory(vaddr, paddr, size, true, r, w, x);
if (!result.has_value())
Helpers::panic("Failed to allocated GSP shared memory");
}

View file

@ -58,9 +58,9 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
printf("GSP::GPU::RegisterInterruptRelayQueue (flags = %X, event handle = %X)\n", flags, eventHandle);
mem.write32(messagePointer + 4, Result::SuccessRegisterIRQ);
mem.write32(messagePointer + 8, 0); // TODO: GSP module thread index
mem.write32(messagePointer + 8, 79797979); // TODO: GSP module thread index
mem.write32(messagePointer + 12, 0); // Translation descriptor
mem.write32(messagePointer + 16, 0); // TODO: shared memory handle
mem.write32(messagePointer + 16, KernelHandles::GSPSharedMemHandle); // TODO: GSP shared memory handle
}
void GPUService::writeHwRegs(u32 messagePointer) {