From dba1241f2d56f7e730164ef98856b999b5045c8d Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Wed, 20 May 2026 15:31:16 +0200 Subject: [PATCH] Finished first draft of MMU class --- other/build.nr | 2 +- src/RAM_test.cpp | 9 +- src/common/Data.hpp | 62 ++++++++++- src/hardware/BUS.cpp | 30 ++--- src/hardware/BUS.hpp | 39 +++---- src/hardware/CPU.hpp | 8 +- src/hardware/MMU.cpp | 258 ++++++++++++++++++++++++++++++++++++------- src/hardware/MMU.hpp | 76 ++++++++++++- src/hardware/RAM.cpp | 95 +++++++--------- src/hardware/RAM.hpp | 20 ++-- 10 files changed, 444 insertions(+), 155 deletions(-) diff --git a/other/build.nr b/other/build.nr index f599e28..48082f7 100644 --- a/other/build.nr +++ b/other/build.nr @@ -1 +1 @@ -10 +12 diff --git a/src/RAM_test.cpp b/src/RAM_test.cpp index f4aa8b7..cb5166e 100644 --- a/src/RAM_test.cpp +++ b/src/RAM_test.cpp @@ -407,7 +407,14 @@ int main(void) TEST(test_roundtrip_u64); TEST(test_no_neighbour_corruption_singlethread); TEST(test_endianness_layout); - TEST(test_faults_dont_modify_memory); + try + { + TEST(test_faults_dont_modify_memory); + } + catch (dragon::GuestException& e) + { + std::cout << e.msg << "\n"; + } std::cout << "\n--- multi-threaded atomicity ---\n"; TEST(test_concurrent_same_word_smoke); diff --git a/src/common/Data.hpp b/src/common/Data.hpp index adf90ae..3e47e6a 100644 --- a/src/common/Data.hpp +++ b/src/common/Data.hpp @@ -28,8 +28,66 @@ namespace dragon // ------ Contextual Aliases ------ using AddressType = u32; using VirtualAddress = AddressType; - using PhysycalAddress = AddressType; + using PhysicalAddress = AddressType; // -------------------------------- - enum class MMUAccessType { Read, Write, Execute }; + enum class MMUAccessType : u8 { Read, Write, Execute }; + enum class MMUAccessMode : u8 { Privileged, User }; + enum class MMUFaultKind : u8 { PageFault, PrivPageFault, Bounds, Misalignment }; + enum class ExceptionCause : u32 + { + // === Instruction fetch faults === + MisalignedFetch = 0x0000'0000, // PC not aligned to instruction boundary + InstructionBounds = 0x0000'0001, // PC outside any mapped region + InstructionPageFault = 0x0000'0002, // virt page not mapped (MMU on) or no X permission + InstructionPrivPageFault = 0x0000'0003, // privileged virt page not mapped (MMU on) or no X permission + UnknownInstruction = 0x0000'0004, // decoded opcode is not valid + + // === Privilege === + PrivilegeViolation = 0x0000'1000, // user mode executed a [PRIV] instruction + + // === Memory access faults (loads) === + MisalignedLoad = 0x0000'2000, // load address not naturally aligned + LoadBounds = 0x0000'2001, // load address outside mapped region + LoadPageFault = 0x0000'2002, // virt page not mapped or no R permission + LoadPrivPageFault = 0x0000'2003, // virt page not mapped or no R permission + + // === Memory access faults (stores) === + MisalignedStore = 0x0000'3000, + StoreBounds = 0x0000'3001, + StorePageFault = 0x0000'3002, // virt page not mapped or no W permission + StorePrivPageFault = 0x0000'3003, // virt page not mapped or no W permission + + // === Atomic op faults === + AtomicNotSupported = 0x0000'4000, // CAS/XADD/XCHG on a device that can't (ROM, MMIO) + + // === Arithmetic === + DivideByZero = 0x0000'5000, + + // === MMIO === + BusUnmapped = 0x0000'6000, + ReadOnlyViolation = 0x0000'6001, + + // === Deliberate guest-initiated traps === + Syscall = 0x0000'7000, // syscall instruction + SoftwareInt = 0x0000'7001, // int N instruction + TrapInstruction = 0x0000'7002, // explicit trap instr + Breakpoint = 0x0000'7003, // brk for debugger + + // === Asynchronous (external) === + HardwareInterrupt = 0x0000'F000, // generic external IRQ; details in INT controller + + // === Catastrophic === + DoubleFault = 0x000F'FFFF, // exception while handling exception + + // Just in case + NoFault = 0xFFFF'FFFF, + }; + + struct GuestException + { + ExceptionCause cause; + VirtualAddress fault_addr; // VA for translation faults, PA for bus faults, 0 otherwise + String msg; // For debug + }; } diff --git a/src/hardware/BUS.cpp b/src/hardware/BUS.cpp index 39c9eba..546aa85 100644 --- a/src/hardware/BUS.cpp +++ b/src/hardware/BUS.cpp @@ -45,7 +45,7 @@ namespace dragon bool MemoryDevice::guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired) { - fault(addr, 4, "CAS not supported on this device"); + fault(addr, ExceptionCause::AtomicNotSupported, "MemoryDevice::guest_cas_u32"); return false; } @@ -55,7 +55,7 @@ namespace dragon namespace hw { - bool BUS::guest_cas_u32(PhysycalAddress addr, u32 expected, u32& actual, u32 desired) + bool BUS::guest_cas_u32(PhysicalAddress addr, u32 expected, u32& actual, u32 desired) { auto region = findRegion(addr); if (!region) { /* fault */ return false; } @@ -63,7 +63,7 @@ namespace dragon return region->device->guest_cas_u32(finalAddr, expected, actual, desired); } - u8 BUS::load_u8(PhysycalAddress addr) const + u8 BUS::load_u8(PhysicalAddress addr) const { auto region = findRegion(addr); if (region == nullptr) return 0x00; //TODO: Error @@ -71,7 +71,7 @@ namespace dragon return region->device->load_u8(finalAddr); } - u16 BUS::load_u16(PhysycalAddress addr) const + u16 BUS::load_u16(PhysicalAddress addr) const { auto region = findRegion(addr); if (region == nullptr) return 0x0000; //TODO: Error @@ -79,7 +79,7 @@ namespace dragon return region->device->load_u16(finalAddr); } - u32 BUS::load_u32(PhysycalAddress addr) const + u32 BUS::load_u32(PhysicalAddress addr) const { auto region = findRegion(addr); if (region == nullptr) return 0x0000'0000; //TODO: Error @@ -87,7 +87,7 @@ namespace dragon return region->device->load_u32(finalAddr); } - u64 BUS::load_u64(PhysycalAddress addr) const + u64 BUS::load_u64(PhysicalAddress addr) const { auto region = findRegion(addr); if (region == nullptr) return 0x0000'0000'0000'0000; //TODO: Error @@ -95,7 +95,7 @@ namespace dragon return region->device->load_u64(finalAddr); } - bool BUS::store_u8(PhysycalAddress addr, u8 value) + bool BUS::store_u8(PhysicalAddress addr, u8 value) { auto region = findRegion(addr); if (region == nullptr) return false; @@ -103,7 +103,7 @@ namespace dragon return region->device->store_u8(finalAddr, value); } - bool BUS::store_u16(PhysycalAddress addr, u16 value) + bool BUS::store_u16(PhysicalAddress addr, u16 value) { auto region = findRegion(addr); if (region == nullptr) return false; @@ -111,7 +111,7 @@ namespace dragon return region->device->store_u16(finalAddr, value); } - bool BUS::store_u32(PhysycalAddress addr, u32 value) + bool BUS::store_u32(PhysicalAddress addr, u32 value) { auto region = findRegion(addr); if (region == nullptr) return false; @@ -119,7 +119,7 @@ namespace dragon return region->device->store_u32(finalAddr, value); } - bool BUS::store_u64(PhysycalAddress addr, u64 value) + bool BUS::store_u64(PhysicalAddress addr, u64 value) { auto region = findRegion(addr); if (region == nullptr) return false; @@ -127,22 +127,16 @@ namespace dragon return region->device->store_u64(finalAddr, value); } - void BUS::mapDevice(MemoryDevice& device, PhysycalAddress startAddr, PhysycalAddress endAddr, bool remap, ostd::String name) + void BUS::mapDevice(MemoryDevice& device, PhysicalAddress startAddr, PhysicalAddress endAddr, bool remap, ostd::String name) { m_regions.push_back({ &device, startAddr, endAddr, remap, name }); } - ostd::String BUS::getMemoryRegionName(PhysycalAddress address) + ostd::String BUS::getMemoryRegionName(PhysicalAddress address) { tMemoryRegion* region = findRegion(address); if (region == nullptr) return "Invalid"; return region->name; } - - void BUS::fault(PhysycalAddress addr, u32 size, const String& msg) const - { - std::cout << msg << ostd::String::getHexStr(addr, true, 4) << "\n"; - /* TODO: This function basically only has to deal with the "region not mapped" problem. */ - } } } diff --git a/src/hardware/BUS.hpp b/src/hardware/BUS.hpp index c01b8ad..c9eaae0 100644 --- a/src/hardware/BUS.hpp +++ b/src/hardware/BUS.hpp @@ -41,8 +41,8 @@ namespace dragon // ---- Aligned loads ---- virtual u64 load_u64(AddressType addr) const = 0; virtual u32 load_u32(AddressType addr) const = 0; - virtual u16 load_u16(u32 addr) const = 0; - virtual u8 load_u8 (u32 addr) const = 0; + virtual u16 load_u16(AddressType addr) const = 0; + virtual u8 load_u8 (AddressType addr) const = 0; inline i64 load_i64(AddressType addr) const { return cast(load_u64(addr)); } inline i32 load_i32(AddressType addr) const { return cast(load_u32(addr)); } @@ -60,8 +60,7 @@ namespace dragon inline bool store_i16(AddressType addr, u16 value) { return store_u16(addr, cast(value)); } inline bool store_i8 (AddressType addr, u8 value) { return store_u8 (addr, cast(value)); } - [[noreturn]] virtual void fault(AddressType addr, u32 size, const String& msg = "") const = 0; // Message can be useful for debugging - + [[noreturn]] virtual void fault(u32 addr, ExceptionCause cause, const String& msg = "") const { throw GuestException{ cause, addr, msg }; }; // Message can be useful for debugging }; class AtomicStorageMemoryDevice : public MemoryDevice @@ -94,8 +93,8 @@ namespace dragon private: struct tMemoryRegion { MemoryDevice* device { nullptr }; - PhysycalAddress startAddress { 0x0000'0000 }; - PhysycalAddress endAddress { 0x0000'0000 }; + PhysicalAddress startAddress { 0x0000'0000 }; + PhysicalAddress endAddress { 0x0000'0000 }; bool remap { false }; ostd::String name { "" }; }; @@ -103,35 +102,33 @@ namespace dragon public: BUS(void) = default; - bool guest_cas_u32(PhysycalAddress addr, u32 expected, u32& actual, u32 desired) override; + bool guest_cas_u32(PhysicalAddress addr, u32 expected, u32& actual, u32 desired) override; // ---- Aligned loads ---- - u64 load_u64(PhysycalAddress addr) const override; - u32 load_u32(PhysycalAddress addr) const override; - u16 load_u16(PhysycalAddress addr) const override; - u8 load_u8 (PhysycalAddress addr) const override; + u64 load_u64(PhysicalAddress addr) const override; + u32 load_u32(PhysicalAddress addr) const override; + u16 load_u16(PhysicalAddress addr) const override; + u8 load_u8 (PhysicalAddress addr) const override; // ---- Aligned stores ---- - bool store_u64(PhysycalAddress addr, u64 value) override; - bool store_u32(PhysycalAddress addr, u32 value) override; - bool store_u16(PhysycalAddress addr, u16 value) override; - bool store_u8 (PhysycalAddress addr, u8 value) override; + bool store_u64(PhysicalAddress addr, u64 value) override; + bool store_u32(PhysicalAddress addr, u32 value) override; + bool store_u16(PhysicalAddress addr, u16 value) override; + bool store_u8 (PhysicalAddress addr, u8 value) override; - void mapDevice(MemoryDevice& device, PhysycalAddress startAddr, PhysycalAddress endAddr, bool remap = false, ostd::String name = ""); - ostd::String getMemoryRegionName(PhysycalAddress address); - - [[noreturn]] void fault(PhysycalAddress addr, u32 size, const String& msg) const override; + void mapDevice(MemoryDevice& device, PhysicalAddress startAddr, PhysicalAddress endAddr, bool remap = false, ostd::String name = ""); + ostd::String getMemoryRegionName(PhysicalAddress address); private: template - auto findRegion(this Self&& self, PhysycalAddress address) + auto findRegion(this Self&& self, PhysicalAddress address) { for (auto& region : self.m_regions) { // works whether self is const or not if (address >= region.startAddress && address <= region.endAddress) return ®ion; } - self.fault(address, 0, "Memory device not found: "); + self.fault(address, ExceptionCause::BusUnmapped, "Memory device not found"); return decltype(&self.m_regions[0]){nullptr}; } diff --git a/src/hardware/CPU.hpp b/src/hardware/CPU.hpp index 4ddf05b..2069960 100644 --- a/src/hardware/CPU.hpp +++ b/src/hardware/CPU.hpp @@ -20,16 +20,10 @@ #pragma once -#include "../common/Data.hpp" +// #include "../common/Data.hpp" namespace dragon { - struct GuestException - { - // ExceptionCause cause; - VirtualAddress fault_addr; // VA for translation faults, PA for bus faults, 0 otherwise - u32 faulting_pc; // set by the CPU before fault propagation, not by the throwsite - }; namespace hw { class CPU diff --git a/src/hardware/MMU.cpp b/src/hardware/MMU.cpp index a4a810a..3627e83 100644 --- a/src/hardware/MMU.cpp +++ b/src/hardware/MMU.cpp @@ -24,45 +24,223 @@ namespace dragon { namespace hw { + MMU::MMU(BUS& bus, const MMUAccessMode* mode_ptr) : m_bus(bus), m_modePtr(mode_ptr) + { + + } + + PhysicalAddress MMU::translate(VirtualAddress va, MMUAccessType type, MMUAccessMode mode) const + { + // Phase 1: when MMU is off, identity-map. No translation, no checks. + // This is the state at boot and during early kernel bringup. + if (!m_enabled) return va; + + // Decompose the virtual address into the three pieces. + // ┌──────────┬──────────┬──────────────────┐ + // │ L1 idx │ L2 idx │ offset │ + // │ (10 bits)│ (10 bits)│ (12 bits) │ + // └──────────┴──────────┴──────────────────┘ + const u32 l1_idx = (va >> 22) & 0x3FF; + const u32 l2_idx = (va >> 12) & 0x3FF; + const u32 offset = va & 0xFFF; + + // ---- Walk Level 1 ---- + // L1 table starts at m_ptb. Each entry is 4 bytes. + // Read the L1 PTE from physical memory via the bus. + const PhysicalAddress l1_pte_addr = m_ptb + l1_idx * 4; + const u32 l1_pte = m_bus.load_u32(l1_pte_addr); + + // If the L1 entry isn't present, the L2 table doesn't exist for this region. + // The whole 4 MB chunk (covered by this L1 entry) is unmapped. + if (!pte_has(l1_pte, PTE_PRESENT)) + fault(va, get_exception_cause(MMUFaultKind::PageFault, type), "MMU::translate L1 not present"); + + // ---- Walk Level 2 ---- + // The L2 table's physical base address is in the upper 20 bits of the L1 PTE. + const PhysicalAddress l2_table_phys = pte_phys_addr(l1_pte); + const PhysicalAddress l2_pte_addr = l2_table_phys + l2_idx * 4; + const u32 l2_pte = m_bus.load_u32(l2_pte_addr); + + // If the L2 entry isn't present, this specific 4 KB page isn't mapped. + if (!pte_has(l2_pte, PTE_PRESENT)) + fault(va, get_exception_cause(MMUFaultKind::PageFault, type), "MMU::translate L2 not present"); + + // ---- Permission checks ---- + // User mode can only access pages with the U bit set. Supervisor can access anything. + if (mode == MMUAccessMode::User && !pte_has(l2_pte, PTE_USER)) + fault(va, get_exception_cause(MMUFaultKind::PrivPageFault, type), "MMU::translate user access to priv page"); + + // Per-access-type permission check. R/W/X bits. + switch (type) + { + case MMUAccessType::Read: + if (!pte_has(l2_pte, PTE_READ)) + fault(va, get_exception_cause(MMUFaultKind::PageFault, type), "MMU::translate no read perm"); + break; + case MMUAccessType::Write: + if (!pte_has(l2_pte, PTE_WRITE)) + fault(va, get_exception_cause(MMUFaultKind::PageFault, type), "MMU::translate no write perm"); + break; + case MMUAccessType::Execute: + if (!pte_has(l2_pte, PTE_EXEC)) + fault(va, get_exception_cause(MMUFaultKind::PageFault, type), "MMU::translate no exec perm"); + break; + } + + // ---- Update Accessed bit (and Dirty bit on writes) ---- + // The hardware sets these so the OS can track which pages have been used. + // Use CAS so we don't race with other cores walking the same PTE. + // If the CAS fails, someone else updated the PTE — that's fine, we don't retry + // because they will have set the same bits we wanted to set. + u32 new_flags = 0; + if (!pte_has(l2_pte, PTE_ACCESSED)) + new_flags |= PTE_ACCESSED; + if (type == MMUAccessType::Write && !pte_has(l2_pte, PTE_DIRTY)) + new_flags |= PTE_DIRTY; + + if (new_flags != 0) + { + u32 actual; // out-param required by guest_cas_u32; we ignore its value + m_bus.guest_cas_u32(l2_pte_addr, l2_pte, actual, l2_pte | new_flags); + // Intentionally don't check the return value or retry. If another core + // raced us and set different bits, the next access will set them via + // its own translate call. + } + + // ---- Assemble final physical address ---- + const PhysicalAddress phys_page = pte_phys_addr(l2_pte); + return phys_page | offset; + } + + void MMU::enable(bool value) + { + if (m_enabled == value) return; + m_enabled = value; + invalidateAll(); + } + + void MMU::setPtb(PhysicalAddress ptb) + { + const PhysicalAddress aligned = ptb & PTE_PHYS_MASK; + if (m_ptb == aligned) return; + m_ptb = aligned; + invalidateAll(); + } + + void MMU::invalidateAll(void) + {// TODO: + // No TLB yet. When implemented, this flushes every cached translation, + // including global entries. + } + + void MMU::invalidatePage(VirtualAddress va) + {// TODO: + // No TLB yet. When implemented, this flushes any cached translation + // for the page containing va, regardless of G bit or ASID. + (void)va; // suppress unused-parameter warning for now + } + + bool MMU::guest_cas_u32(VirtualAddress va, u32 expected, u32& actual, u32 desired) + { + PhysicalAddress pa = translate(va, MMUAccessType::Write); + return m_bus.guest_cas_u32(pa, expected, actual, desired); + } + + u64 MMU::load_u64(VirtualAddress va) const + { + PhysicalAddress pa = translate(va, MMUAccessType::Read); + return m_bus.load_u64(pa); + } + + u32 MMU::load_u32(VirtualAddress va) const + { + PhysicalAddress pa = translate(va, MMUAccessType::Read); + return m_bus.load_u32(pa); + } + + u16 MMU::load_u16(VirtualAddress va) const + { + PhysicalAddress pa = translate(va, MMUAccessType::Read); + return m_bus.load_u16(pa); + } + + u8 MMU::load_u8(VirtualAddress va) const + { + PhysicalAddress pa = translate(va, MMUAccessType::Read); + return m_bus.load_u8(pa); + } + + bool MMU::store_u64(VirtualAddress va, u64 value) + { + PhysicalAddress pa = translate(va, MMUAccessType::Write); + return m_bus.store_u64(pa, value); + } + + bool MMU::store_u32(VirtualAddress va, u32 value) + { + PhysicalAddress pa = translate(va, MMUAccessType::Write); + return m_bus.store_u32(pa, value); + } + + bool MMU::store_u16(VirtualAddress va, u16 value) + { + PhysicalAddress pa = translate(va, MMUAccessType::Write); + return m_bus.store_u16(pa, value); + } + + bool MMU::store_u8(VirtualAddress va, u8 value) + { + PhysicalAddress pa = translate(va, MMUAccessType::Write); + return m_bus.store_u8(pa, value); + } + + u32 MMU::fetch_u32(VirtualAddress va) const + { + PhysicalAddress pa = translate(va, MMUAccessType::Execute); + return m_bus.load_u32(pa); + } + + ExceptionCause MMU::get_exception_cause(MMUFaultKind kind, MMUAccessType type) const + { + using F = MMUFaultKind; + using T = MMUAccessType; + using E = ExceptionCause; + + switch (kind) { + case F::PageFault: + switch (type) + { + case T::Read: return E::LoadPageFault; + case T::Write: return E::StorePageFault; + case T::Execute: return E::InstructionPageFault; + } + break; + case F::PrivPageFault: + switch (type) + { + case T::Read: return E::LoadPrivPageFault; + case T::Write: return E::StorePrivPageFault; + case T::Execute: return E::InstructionPrivPageFault; + } + break; + case F::Bounds: + switch (type) + { + case T::Read: return E::LoadBounds; + case T::Write: return E::StoreBounds; + case T::Execute: return E::InstructionBounds; + } + break; + case F::Misalignment: + switch (type) + { + case T::Read: return E::MisalignedLoad; + case T::Write: return E::MisalignedStore; + case T::Execute: return E::MisalignedFetch; + } + break; + } + return E::NoFault; // unreachable; satisfies compiler + } } } - - - -// PhysycalAddress MMU::translate(VirtualAddress va, AccessType type, Mode mode) -// { -// if (!m_enabled) return va; // identity map when disabled - -// u32 l1_idx = (va >> 22) & 0x3FF; -// u32 l2_idx = (va >> 12) & 0x3FF; -// u32 offset = va & 0xFFF; - -// // Walk level 1 -// u32 l1_pte_addr = m_ptb + l1_idx * 4; -// u32 l1_pte = ram.load_u32(l1_pte_addr); -// if (!(l1_pte & PTE_PRESENT)) -// throw GuestException{cause_for(type, PageFault), va}; - -// // Walk level 2 -// u32 l2_table_phys = (l1_pte >> 12) << 12; // physical address of L2 table -// u32 l2_pte_addr = l2_table_phys + l2_idx * 4; -// u32 l2_pte = ram.load_u32(l2_pte_addr); -// if (!(l2_pte & PTE_PRESENT)) -// throw GuestException{cause_for(type, PageFault), va}; - -// // Permission check -// if (mode == Mode::User && !(l2_pte & PTE_USER)) -// throw GuestException{cause_for(type, PrivPageFault), va}; -// if (type == AccessType::Read && !(l2_pte & PTE_READ)) throw GuestException{...}; -// if (type == AccessType::Write && !(l2_pte & PTE_WRITE)) throw GuestException{...}; -// if (type == AccessType::Execute && !(l2_pte & PTE_EXEC)) throw GuestException{...}; - -// // Set accessed bit (atomic so we don't race with other cores walking the same PTE) -// if (!(l2_pte & PTE_ACCESSED)) { -// // CAS to set the bit; ignore failure (means someone else set it) -// ram.guest_cas_u32(l2_pte_addr, l2_pte, l2_pte | PTE_ACCESSED); -// } - -// u32 phys_page = (l2_pte >> 12) << 12; -// return phys_page | offset; -// } diff --git a/src/hardware/MMU.hpp b/src/hardware/MMU.hpp index 3c78471..984ff9d 100644 --- a/src/hardware/MMU.hpp +++ b/src/hardware/MMU.hpp @@ -29,9 +29,83 @@ namespace dragon class MMU : public MemoryDevice { public: - explicit MMU(void); + explicit MMU(BUS& bus, const MMUAccessMode* mode_ptr); + PhysicalAddress translate(VirtualAddress va, MMUAccessType type, MMUAccessMode mode) const; + void enable(bool value = true); + void setPtb(PhysicalAddress ptb); + void invalidateAll(void); + void invalidatePage(VirtualAddress va); + bool guest_cas_u32(PhysicalAddress addr, u32 expected, u32& actual, u32 desired) override; + // ---- Aligned loads ---- + u64 load_u64(PhysicalAddress addr) const override; + u32 load_u32(PhysicalAddress addr) const override; + u16 load_u16(PhysicalAddress addr) const override; + u8 load_u8 (PhysicalAddress addr) const override; + + // ---- Aligned stores ---- + bool store_u64(PhysicalAddress addr, u64 value) override; + bool store_u32(PhysicalAddress addr, u32 value) override; + bool store_u16(PhysicalAddress addr, u16 value) override; + bool store_u8 (PhysicalAddress addr, u8 value) override; + + inline void setCurrentAsid(u8 asid) { m_currentAsid = asid; } + inline u8 getCurrentAsid(void) const { return m_currentAsid; } + inline void disable(void) { enable(false); } + inline bool isEnabled(void) const { return m_enabled; } + inline PhysicalAddress getPtb(void) const { return m_ptb; } + + // Convenience: translate using current mode + inline PhysicalAddress translate(VirtualAddress va, MMUAccessType type) const { return translate(va, type, *m_modePtr); } + u32 fetch_u32(VirtualAddress va) const; + + private: + ExceptionCause get_exception_cause(MMUFaultKind kind, MMUAccessType type) const; + + private: + bool m_enabled { false }; + PhysicalAddress m_ptb { 0 }; + u8 m_currentAsid { 0 }; //TODO: + BUS& m_bus; + const MMUAccessMode* m_modePtr; + + private: + // Page Table Entry bit layout (32 bits): + // + // 31 12 11 .. 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + // ┌─────────────────────────────┬─────────┬───┬───┬───┬───┬───┬───┬───┐ + // │ physical page number (20) │ reserved│ G │ U │ X │ W │ R │ A │ P │ + // └─────────────────────────────┴─────────┴───┴───┴───┴───┴───┴───┴───┘ + // + // P = Present : entry is valid; if 0, access faults + // A = Accessed : set by hardware on any access to this page + // R = Readable : load access permitted + // W = Writable : store access permitted + // X = Executable : instruction fetch permitted + // U = User-accessible: accessible from user mode (else supervisor-only) + // G = Global : translation valid in all address spaces (kernel mappings) + + inline static constexpr u32 PTE_PRESENT = 1u << 0; // 0x001 + inline static constexpr u32 PTE_ACCESSED = 1u << 1; // 0x002 + inline static constexpr u32 PTE_READ = 1u << 2; // 0x004 + inline static constexpr u32 PTE_WRITE = 1u << 3; // 0x008 + inline static constexpr u32 PTE_EXEC = 1u << 4; // 0x010 + inline static constexpr u32 PTE_USER = 1u << 5; // 0x020 + inline static constexpr u32 PTE_GLOBAL = 1u << 6; // 0x040 + inline static constexpr u32 PTE_DIRTY = 1u << 7; // 0x080 + + // Bits 7..11 reserved for future use (Dirty bit, cache attributes, etc.) + + inline static constexpr u32 PTE_PERM_MASK = PTE_READ | PTE_WRITE | PTE_EXEC | PTE_USER | PTE_GLOBAL; + inline static constexpr u32 PTE_FLAGS_MASK = 0xFFFu; // bottom 12 bits = flags + inline static constexpr u32 PTE_PHYS_MASK = 0xFFFFF000u; // top 20 bits = phys page number + + // Helper: extract the physical page-aligned address from a PTE + inline static constexpr u32 pte_phys_addr(u32 pte) { return pte & PTE_PHYS_MASK; } + + // Helper: check a single bit + inline static constexpr bool pte_has(u32 pte, u32 flag) { return (pte & flag) != 0; } }; } } diff --git a/src/hardware/RAM.cpp b/src/hardware/RAM.cpp index 1be41bf..200d9f5 100644 --- a/src/hardware/RAM.cpp +++ b/src/hardware/RAM.cpp @@ -19,20 +19,39 @@ */ #include "RAM.hpp" -#include #include +#define DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(size, msg) \ + if (addr + size > m_size) \ + { \ + fault(addr, ExceptionCause::LoadBounds, msg); \ + return 0; \ + } \ + if (addr & (size - 1)) \ + { \ + fault(addr, ExceptionCause::MisalignedLoad, msg); \ + return 0; \ + } \ + +#define DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(size, msg) \ + if (addr + size > m_size) \ + { \ + fault(addr, ExceptionCause::StoreBounds, msg); \ + return false; \ + } \ + if (addr & (size - 1)) \ + { \ + fault(addr, ExceptionCause::MisalignedStore, msg); \ + return false; \ + } \ + namespace dragon { namespace hw { - bool RAM::guest_cas_u32(PhysycalAddress addr, u32 expected, u32& actual, u32 desired) + bool RAM::guest_cas_u32(PhysicalAddress addr, u32 expected, u32& actual, u32 desired) { - if (addr + 4 > m_size || (addr & 3)) - { - fault(addr, 4, "Misaligned/OOB CAS"); - return false; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(4, "RAM::guest_cas_u32"); u32 shift = (addr & 4) * 8; u64 mask = u64(0xFFFFFFFFu) << shift; @@ -60,45 +79,33 @@ namespace dragon } } - u64 RAM::load_u64(PhysycalAddress addr) const + u64 RAM::load_u64(PhysicalAddress addr) const { - if (addr + 8 > m_size || (addr & 7)) - { - fault(addr, 8, "Memory Fault: "); - return 0; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(8, "RAM::load_u64"); return m_words[addr >> 3].load(std::memory_order_seq_cst); } - u32 RAM::load_u32(PhysycalAddress addr) const + u32 RAM::load_u32(PhysicalAddress addr) const { - if (addr + 4 > m_size || (addr & 3)) - { - fault(addr, 4, "Memory Fault: "); - return 0; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(4, "RAM::load_u32"); u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst); u32 shift = (addr & 4) * 8; // 0 or 32 return static_cast(word >> shift); } - u16 RAM::load_u16(PhysycalAddress addr) const + u16 RAM::load_u16(PhysicalAddress addr) const { - if (addr + 2 > m_size || (addr & 1)) - { - fault(addr, 2, "Memory Fault: "); - return 0; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(2, "RAM::load_u16"); u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst); u32 shift = (addr & 6) * 8; // 0, 16, 32, or 48 return static_cast(word >> shift); } - u8 RAM::load_u8(PhysycalAddress addr) const + u8 RAM::load_u8(PhysicalAddress addr) const { if (addr >= m_size) { - fault(addr, 1, "Memory Fault: "); + fault(addr, ExceptionCause::LoadBounds, "RAM::load_u8"); return 0; } u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst); @@ -106,24 +113,16 @@ namespace dragon return static_cast(word >> shift); } - bool RAM::store_u64(PhysycalAddress addr, u64 value) + bool RAM::store_u64(PhysicalAddress addr, u64 value) { - if (addr + 8 > m_size || (addr & 7)) - { - fault(addr, 8, "Memory Fault: "); - return false; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(8, "RAM::store_u64"); m_words[addr >> 3].store(value, std::memory_order_seq_cst); return true; } - bool RAM::store_u32(PhysycalAddress addr, u32 value) + bool RAM::store_u32(PhysicalAddress addr, u32 value) { - if (addr + 4 > m_size || (addr & 3)) - { - fault(addr, 4, "Memory Fault: "); - return false; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(4, "RAM::store_u32"); u32 shift = (addr & 4) * 8; u64 mask = u64(0xFFFFFFFFu) << shift; std::atomic& word = m_words[addr >> 3]; @@ -136,13 +135,9 @@ namespace dragon return true; } - bool RAM::store_u16(PhysycalAddress addr, u16 value) + bool RAM::store_u16(PhysicalAddress addr, u16 value) { - if (addr + 2 > m_size || (addr & 1)) - { - fault(addr, 2, "Memory Fault: "); - return false; - } + DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(2, "RAM::store_u16"); u32 shift = (addr & 6) * 8; u64 mask = u64(0xFFFFu) << shift; std::atomic& word = m_words[addr >> 3]; @@ -155,11 +150,11 @@ namespace dragon return true; } - bool RAM::store_u8(PhysycalAddress addr, u8 value) + bool RAM::store_u8(PhysicalAddress addr, u8 value) { if (addr >= m_size) { - fault(addr, 1, "Memory Fault: "); + fault(addr, ExceptionCause::StoreBounds, "RAM::store_u8"); return false; } u32 shift = (addr & 7) * 8; @@ -173,11 +168,5 @@ namespace dragon } while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed)); return true; } - - void RAM::fault(PhysycalAddress addr, u32 size, const String& msg) const - { - std::cout << msg << ostd::String::getHexStr(addr, true, 4) << "\n"; - /* TODO: raise guest trap */ - } } } diff --git a/src/hardware/RAM.hpp b/src/hardware/RAM.hpp index 54c24f4..56fcf7b 100644 --- a/src/hardware/RAM.hpp +++ b/src/hardware/RAM.hpp @@ -33,21 +33,19 @@ namespace dragon public: explicit RAM(u64 size) : AtomicStorageMemoryDevice(size) { } - bool guest_cas_u32(PhysycalAddress addr, u32 expected, u32& actual, u32 desired) override; + bool guest_cas_u32(PhysicalAddress addr, u32 expected, u32& actual, u32 desired) override; // ---- Aligned loads ---- - u64 load_u64(PhysycalAddress addr) const override; - u32 load_u32(PhysycalAddress addr) const override; - u16 load_u16(PhysycalAddress addr) const override; - u8 load_u8 (PhysycalAddress addr) const override; + u64 load_u64(PhysicalAddress addr) const override; + u32 load_u32(PhysicalAddress addr) const override; + u16 load_u16(PhysicalAddress addr) const override; + u8 load_u8 (PhysicalAddress addr) const override; // ---- Aligned stores ---- - bool store_u64(PhysycalAddress addr, u64 value) override; - bool store_u32(PhysycalAddress addr, u32 value) override; - bool store_u16(PhysycalAddress addr, u16 value) override; - bool store_u8 (PhysycalAddress addr, u8 value) override; - - [[noreturn]] void fault(PhysycalAddress addr, u32 size, const String& msg = "") const override; + bool store_u64(PhysicalAddress addr, u64 value) override; + bool store_u32(PhysicalAddress addr, u32 value) override; + bool store_u16(PhysicalAddress addr, u16 value) override; + bool store_u8 (PhysicalAddress addr, u8 value) override; }; } }