Testing backport of V2 components

This commit is contained in:
OmniaX-Dev 2026-05-22 21:28:32 +02:00
parent 6f0bed74d2
commit 4aa5e318f0
7 changed files with 661 additions and 94 deletions

View file

@ -12,7 +12,7 @@ set(CMAKE_C_COMPILER "clang")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.18)
project(${PROJ_NAME} LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
file(STRINGS "./other/build.nr" BUILD_NUMBER)
#-----------------------------------------------------------------------------------------

View file

@ -1 +1 @@
1646
1648

143
src/hardware/BUS.cpp Normal file
View file

@ -0,0 +1,143 @@
/*
DragonV2 - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of DragonV2.
DragonV2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DragonV2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
*/
#include "BUS.hpp"
namespace dragon
{
AtomicStorageMemoryDevice::AtomicStorageMemoryDevice(u64 size)
{
if (size == 0) // Special case, for classes like BUS, that are MemoryDevices but don't own any data
{
m_wordCount = 0;
m_size = 0;
m_words = nullptr;
return;
}
m_wordCount = (size + 7) / 8; // Round up to multiple of 8 bytes for the word-granular backing
m_size = size;
m_words = new std::atomic<u64>[m_wordCount];
for (u64 i = 0; i < m_wordCount; ++i)
m_words[i].store(0, std::memory_order_relaxed);
}
AtomicStorageMemoryDevice::~AtomicStorageMemoryDevice(void)
{
delete[] m_words;
}
bool MemoryDevice::guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired)
{
data::ErrorHandler::pushError(data::ErrorCodes::MM_AtomicNotSupported, "MemoryDevice::guest_cas_u32: atomic not supported: ");
fault(addr, data::ExceptionCause::AtomicNotSupported, "MemoryDevice::guest_cas_u32");
return false;
}
namespace hw
{
bool BUS::guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired)
{
auto region = findRegion(addr);
if (!region) { /* fault */ return false; }
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->guest_cas_u32(finalAddr, expected, actual, desired);
}
u8 BUS::load_u8(AddressType addr) const
{
auto region = findRegion(addr);
if (region == nullptr) return 0x00; //TODO: Error
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->load_u8(finalAddr);
}
u16 BUS::load_u16(AddressType addr) const
{
auto region = findRegion(addr);
if (region == nullptr) return 0x0000; //TODO: Error
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->load_u16(finalAddr);
}
u32 BUS::load_u32(AddressType addr) const
{
auto region = findRegion(addr);
if (region == nullptr) return 0x0000'0000; //TODO: Error
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->load_u32(finalAddr);
}
u64 BUS::load_u64(AddressType addr) const
{
auto region = findRegion(addr);
if (region == nullptr) return 0x0000'0000'0000'0000; //TODO: Error
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->load_u64(finalAddr);
}
bool BUS::store_u8(AddressType addr, u8 value)
{
auto region = findRegion(addr);
if (region == nullptr) return false;
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->store_u8(finalAddr, value);
}
bool BUS::store_u16(AddressType addr, u16 value)
{
auto region = findRegion(addr);
if (region == nullptr) return false;
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->store_u16(finalAddr, value);
}
bool BUS::store_u32(AddressType addr, u32 value)
{
auto region = findRegion(addr);
if (region == nullptr) return false;
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->store_u32(finalAddr, value);
}
bool BUS::store_u64(AddressType addr, u64 value)
{
auto region = findRegion(addr);
if (region == nullptr) return false;
AddressType finalAddr = (region->remap ? addr - region->startAddress : addr);
return region->device->store_u64(finalAddr, value);
}
void BUS::mapDevice(MemoryDevice& device, AddressType startAddr, AddressType endAddr, bool remap, ostd::String name)
{
m_regions.push_back({ &device, startAddr, endAddr, remap, name });
}
ostd::String BUS::getMemoryRegionName(AddressType address)
{
tMemoryRegion* region = findRegion(address);
if (region == nullptr) return "Invalid";
return region->name;
}
}
}

142
src/hardware/BUS.hpp Normal file
View file

@ -0,0 +1,142 @@
/*
DragonV2 - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of DragonV2.
DragonV2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DragonV2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ostd/data/BaseObject.hpp>
#include <atomic>
#include <span>
#include "../tools/GlobalData.hpp"
#pragma once
namespace dragon
{
class MemoryDevice : public ostd::BaseObject
{
public:
MemoryDevice(void) = default;
MemoryDevice(const MemoryDevice&) = delete;
MemoryDevice& operator=(const MemoryDevice&) = delete;
MemoryDevice(MemoryDevice&&) = delete;
MemoryDevice& operator=(MemoryDevice&&) = delete;
virtual ~MemoryDevice() = default;
virtual bool guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired);
// ---- Aligned loads ----
virtual u64 load_u64(AddressType addr) const = 0;
virtual u32 load_u32(AddressType 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<i64>(load_u64(addr)); }
inline i32 load_i32(AddressType addr) const { return cast<i32>(load_u32(addr)); }
inline i16 load_i16(AddressType addr) const { return cast<i16>(load_u16(addr)); }
inline i8 load_i8 (AddressType addr) const { return cast<i8 >(load_u8 (addr)); }
// ---- Aligned stores ----
virtual bool store_u64(AddressType addr, u64 value) = 0;
virtual bool store_u32(AddressType addr, u32 value) = 0;
virtual bool store_u16(AddressType addr, u16 value) = 0;
virtual bool store_u8 (AddressType addr, u8 value) = 0;
inline bool store_i64(AddressType addr, u64 value) { return store_u64(addr, cast<i64>(value)); }
inline bool store_i32(AddressType addr, u32 value) { return store_u32(addr, cast<i32>(value)); }
inline bool store_i16(AddressType addr, u16 value) { return store_u16(addr, cast<i16>(value)); }
inline bool store_i8 (AddressType addr, u8 value) { return store_u8 (addr, cast<i8 >(value)); }
[[noreturn]] virtual void fault(AddressType addr, data::ExceptionCause cause, const String& msg = "") const { throw data::GuestException{ cause, addr, msg }; }; // Message can be useful for debugging
};
class AtomicStorageMemoryDevice : public MemoryDevice
{
public:
explicit AtomicStorageMemoryDevice(u64 size);
virtual ~AtomicStorageMemoryDevice(void);
AtomicStorageMemoryDevice(const AtomicStorageMemoryDevice&) = delete;
AtomicStorageMemoryDevice& operator=(const AtomicStorageMemoryDevice&) = delete;
AtomicStorageMemoryDevice(AtomicStorageMemoryDevice&&) = delete;
AtomicStorageMemoryDevice& operator=(AtomicStorageMemoryDevice&&) = delete;
// ---- Helpers ----
inline u64 getWordCount(void) const { return m_wordCount; }
inline u64 getSize(void) const { return m_size; }
inline virtual std::span<std::atomic<u64>> getRawData(void) { return { m_words, m_size }; }
inline virtual std::span<const std::atomic<u64>> getRawData(void) const { return { m_words, m_size }; }
// Both getRawData() can be overridden to return empty spans for devices where internal memory shouldn't be exposed
protected:
std::atomic<u64>* m_words;
u64 m_wordCount;
u64 m_size;
};
namespace hw
{
class BUS : public MemoryDevice
{
public:
private: struct tMemoryRegion
{
MemoryDevice* device { nullptr };
AddressType startAddress { 0x0000'0000 };
AddressType endAddress { 0x0000'0000 };
bool remap { false };
ostd::String name { "" };
};
public:
BUS(void) = default;
bool guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired) override;
// ---- Aligned loads ----
u64 load_u64(AddressType addr) const override;
u32 load_u32(AddressType addr) const override;
u16 load_u16(AddressType addr) const override;
u8 load_u8 (AddressType addr) const override;
// ---- Aligned stores ----
bool store_u64(AddressType addr, u64 value) override;
bool store_u32(AddressType addr, u32 value) override;
bool store_u16(AddressType addr, u16 value) override;
bool store_u8 (AddressType addr, u8 value) override;
void mapDevice(MemoryDevice& device, AddressType startAddr, AddressType endAddr, bool remap = false, ostd::String name = "");
ostd::String getMemoryRegionName(AddressType address);
private:
template <typename Self>
auto findRegion(this Self&& self, AddressType address)
{
for (auto& region : self.m_regions)
{ // works whether self is const or not
if (address >= region.startAddress && address <= region.endAddress)
return &region;
}
data::ErrorHandler::pushError(data::ErrorCodes::MM_RegionNotFound, "Memory device not found");
return decltype(&self.m_regions[0]){nullptr};
}
private:
std::vector<tMemoryRegion> m_regions;
private:
};
}
}

172
src/hardware/RAM.cpp Normal file
View file

@ -0,0 +1,172 @@
/*
DragonV2 - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of DragonV2.
DragonV2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DragonV2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RAM.hpp"
#include <ostd/string/String.hpp>
#define DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(size, msg) \
if (addr + size > m_size) \
{ \
fault(addr, data::ExceptionCause::LoadBounds, msg); \
return 0; \
} \
if (addr & (size - 1)) \
{ \
fault(addr, data::ExceptionCause::MisalignedLoad, msg); \
return 0; \
} \
#define DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(size, msg) \
if (addr + size > m_size) \
{ \
fault(addr, data::ExceptionCause::StoreBounds, msg); \
return false; \
} \
if (addr & (size - 1)) \
{ \
fault(addr, data::ExceptionCause::MisalignedStore, msg); \
return false; \
} \
namespace dragon
{
namespace hw
{
bool RAM::guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired)
{
DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_R(4, "RAM::guest_cas_u32");
u32 shift = (addr & 4) * 8;
u64 mask = u64(0xFFFFFFFFu) << shift;
std::atomic<u64>& word = m_words[addr >> 3];
u64 word_expected = word.load(std::memory_order_seq_cst);
while (true)
{
u32 current_in_slot = static_cast<u32>(word_expected >> shift);
if (current_in_slot != expected)
{
// CAS fails: report what we saw, don't modify
actual = current_in_slot;
return false;
}
// CAS could succeed: try to update
u64 word_desired = (word_expected & ~mask) | (u64(desired) << shift);
if (word.compare_exchange_weak(word_expected, word_desired, std::memory_order_seq_cst, std::memory_order_seq_cst))
{
actual = expected; // tell caller we succeeded
return true;
}
// CAS spuriously failed or someone else changed the word; word_expected
// is updated. Loop and re-check.
}
}
u64 RAM::load_u64(AddressType addr) const
{
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(AddressType addr) const
{
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<u32>(word >> shift);
}
u16 RAM::load_u16(AddressType addr) const
{
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<u16>(word >> shift);
}
u8 RAM::load_u8(AddressType addr) const
{
if (addr >= m_size)
{
fault(addr, data::ExceptionCause::LoadBounds, "RAM::load_u8");
return 0;
}
u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst);
u32 shift = (addr & 7) * 8;
return static_cast<u8>(word >> shift);
}
bool RAM::store_u64(AddressType addr, u64 value)
{
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(AddressType addr, u32 value)
{
DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(4, "RAM::store_u32");
u32 shift = (addr & 4) * 8;
u64 mask = u64(0xFFFFFFFFu) << shift;
std::atomic<u64>& word = m_words[addr >> 3];
u64 expected = word.load(std::memory_order_relaxed);
u64 desired;
do
{
desired = (expected & ~mask) | (u64(value) << shift);
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
return true;
}
bool RAM::store_u16(AddressType addr, u16 value)
{
DRAGON_TEST_FOR_MISALIGNED_OR_BOUNDS_FAULT_W(2, "RAM::store_u16");
u32 shift = (addr & 6) * 8;
u64 mask = u64(0xFFFFu) << shift;
std::atomic<u64>& word = m_words[addr >> 3];
u64 expected = word.load(std::memory_order_relaxed);
u64 desired;
do
{
desired = (expected & ~mask) | (u64(value) << shift);
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
return true;
}
bool RAM::store_u8(AddressType addr, u8 value)
{
if (addr >= m_size)
{
fault(addr, data::ExceptionCause::StoreBounds, "RAM::store_u8");
return false;
}
u32 shift = (addr & 7) * 8;
u64 mask = u64(0xFFu) << shift;
std::atomic<u64>& word = m_words[addr >> 3];
u64 expected = word.load(std::memory_order_relaxed);
u64 desired;
do
{
desired = (expected & ~mask) | (u64(value) << shift);
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
return true;
}
}
}

51
src/hardware/RAM.hpp Normal file
View file

@ -0,0 +1,51 @@
/*
DragonV2 - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of DragonV2.
DragonV2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DragonV2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/Types.hpp>
#include "BUS.hpp"
namespace dragon
{
namespace hw
{
class RAM : public AtomicStorageMemoryDevice
{
public:
explicit RAM(u64 size) : AtomicStorageMemoryDevice(size) { }
bool guest_cas_u32(AddressType addr, u32 expected, u32& actual, u32 desired) override;
// ---- Aligned loads ----
u64 load_u64(AddressType addr) const override;
u32 load_u32(AddressType addr) const override;
u16 load_u16(AddressType addr) const override;
u8 load_u8 (AddressType addr) const override;
// ---- Aligned stores ----
bool store_u64(AddressType addr, u64 value) override;
bool store_u32(AddressType addr, u32 value) override;
bool store_u16(AddressType addr, u16 value) override;
bool store_u8 (AddressType addr, u8 value) override;
};
}
}

View file

@ -5,6 +5,7 @@
namespace dragon
{
using AddressType = u16;
namespace hw { class VirtualCPU; }
namespace data
{
@ -17,6 +18,7 @@ namespace dragon
inline static constexpr uint64_t AccessViolation_BiosModeRequired = 0x0000000000000001;
inline static constexpr uint64_t MM_RegionNotFound = 0x1000000000000000;
inline static constexpr uint64_t MM_AtomicNotSupported = 0x1000000000000001;
inline static constexpr uint64_t CPU_UnknownInstruction = 0x2000000000000000;
inline static constexpr uint64_t CPU_UnsupportedExtension = 0x2000000000000001;
@ -58,6 +60,63 @@ namespace dragon
};
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;
AddressType fault_addr; // VA for translation faults, PA for bus faults, 0 otherwise
String msg; // For debug
};
class ErrorHandler
{
public: struct tError