diff --git a/.gitmodules b/.gitmodules index 5550e6e..6749a9c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "3rd_Party/fmt"] path = 3rd_Party/fmt url = https://github.com/fmtlib/fmt.git -[submodule "3rd_Party/toml11"] - path = 3rd_Party/toml11 - url = https://github.com/ToruNiina/toml11.git [submodule "3rd_Party/SDL3"] path = 3rd_Party/SDL3 url = https://github.com/libsdl-org/SDL.git diff --git a/3rd_Party/CMakeLists.txt b/3rd_Party/CMakeLists.txt index 62faead..f4618ee 100644 --- a/3rd_Party/CMakeLists.txt +++ b/3rd_Party/CMakeLists.txt @@ -19,16 +19,3 @@ if (NOT TARGET SDL3::SDL3) set(SDL_PIPEWIRE OFF) add_subdirectory(SDL3) endif() - -# Toml11 -if (NOT TARGET toml11::toml11) - add_subdirectory(toml11) - set(TOML11_INSTALL OFF) - - # This removes the warnings generated by toml11 - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - get_target_property(_toml11_compile_options toml11 INTERFACE_COMPILE_OPTIONS) - list(REMOVE_ITEM _toml11_compile_options "/Zc:preprocessor") - set_target_properties(toml11 PROPERTIES INTERFACE_COMPILE_OPTIONS ${_toml11_compile_options}) - endif() -endif() diff --git a/3rd_Party/toml11 b/3rd_Party/toml11 deleted file mode 160000 index be08ba2..0000000 --- a/3rd_Party/toml11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286 diff --git a/CMakeLists.txt b/CMakeLists.txt index aafe2c9..d83307f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,19 +24,19 @@ project(Pound) find_package(fmt 10.2.1 CONFIG) find_package(SDL3 3.2.10 CONFIG) -find_package(toml11 4.4.0 CONFIG) add_subdirectory(3rd_Party) add_executable(Pound - core/main.cpp + src/main.cpp ) -add_subdirectory(core/arm64) -add_subdirectory(core/common) -add_subdirectory(core/frontend) -add_subdirectory(core/host) +add_subdirectory(src/kvm) +add_subdirectory(src/common) +add_subdirectory(src/frontend) +add_subdirectory(src/host) +add_subdirectory(src/targets/switch1/hardware) target_compile_options(Pound PRIVATE -Wall -Wpedantic -Wshadow @@ -47,7 +47,7 @@ target_compile_options(Pound PRIVATE -Wall -Wpedantic ) # Link libraries -target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3 toml11::toml11) +target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3) if (WIN32) add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN) diff --git a/core/arm64/CMakeLists.txt b/core/arm64/CMakeLists.txt deleted file mode 100644 index 73d2b1e..0000000 --- a/core/arm64/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -#Copyright 2025 Pound Emulator Project.All rights reserved. - -set(ARM64_SOURCES ${CMAKE_CURRENT_SOURCE_DIR} / isa.cpp ${CMAKE_CURRENT_SOURCE_DIR} / - guest.cpp ${CMAKE_CURRENT_SOURCE_DIR} / mmu.cpp) - - target_sources(Pound PRIVATE ${ARM64_SOURCES}) - - target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..) diff --git a/core/arm64/guest.cpp b/core/arm64/guest.cpp deleted file mode 100644 index 48bb9da..0000000 --- a/core/arm64/guest.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "memory.h" - -namespace pound::aarch64::memory -{ - -} // namespace pound::aarch64::memory diff --git a/core/common/Arch.h b/src/common/Arch.h similarity index 100% rename from core/common/Arch.h rename to src/common/Arch.h diff --git a/core/common/Assert.cpp b/src/common/Assert.cpp similarity index 100% rename from core/common/Assert.cpp rename to src/common/Assert.cpp diff --git a/core/common/Assert.h b/src/common/Assert.h similarity index 100% rename from core/common/Assert.h rename to src/common/Assert.h diff --git a/core/common/BoundedQueue.h b/src/common/BoundedQueue.h similarity index 100% rename from core/common/BoundedQueue.h rename to src/common/BoundedQueue.h diff --git a/core/common/CMakeLists.txt b/src/common/CMakeLists.txt similarity index 100% rename from core/common/CMakeLists.txt rename to src/common/CMakeLists.txt diff --git a/core/common/Config.cpp b/src/common/Config.cpp similarity index 98% rename from core/common/Config.cpp rename to src/common/Config.cpp index d3b2e96..b709b5e 100644 --- a/core/common/Config.cpp +++ b/src/common/Config.cpp @@ -3,7 +3,6 @@ #include "Config.h" #include -#include namespace Config { @@ -38,6 +37,7 @@ std::string logType() void Load(const std::filesystem::path& path) { + /* // If the configuration file does not exist, create it and return std::error_code error; if (!std::filesystem::exists(path, error)) @@ -67,10 +67,12 @@ void Load(const std::filesystem::path& path) logAdvanced = toml::find_or(general, "Advanced Log", false); typeLog = toml::find_or(general, "Log Type", "async"); } + */ } void Save(const std::filesystem::path& path) { + /* toml::ordered_value data; std::error_code error; @@ -103,6 +105,7 @@ void Save(const std::filesystem::path& path) std::ofstream file(path, std::ios::binary); file << data; file.close(); + */ } } // namespace Config diff --git a/core/common/Config.h b/src/common/Config.h similarity index 100% rename from core/common/Config.h rename to src/common/Config.h diff --git a/core/common/Enum.h b/src/common/Enum.h similarity index 100% rename from core/common/Enum.h rename to src/common/Enum.h diff --git a/core/common/Error.h b/src/common/Error.h similarity index 100% rename from core/common/Error.h rename to src/common/Error.h diff --git a/core/common/IoFile.cpp b/src/common/IoFile.cpp similarity index 100% rename from core/common/IoFile.cpp rename to src/common/IoFile.cpp diff --git a/core/common/IoFile.h b/src/common/IoFile.h similarity index 100% rename from core/common/IoFile.h rename to src/common/IoFile.h diff --git a/core/common/Logging/Backend.cpp b/src/common/Logging/Backend.cpp similarity index 100% rename from core/common/Logging/Backend.cpp rename to src/common/Logging/Backend.cpp diff --git a/core/common/Logging/Backend.h b/src/common/Logging/Backend.h similarity index 100% rename from core/common/Logging/Backend.h rename to src/common/Logging/Backend.h diff --git a/core/common/Logging/Filter.cpp b/src/common/Logging/Filter.cpp similarity index 97% rename from core/common/Logging/Filter.cpp rename to src/common/Logging/Filter.cpp index 522114b..9b5feae 100644 --- a/core/common/Logging/Filter.cpp +++ b/src/common/Logging/Filter.cpp @@ -69,6 +69,8 @@ bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) { CLS(Render) \ CLS(ARM) \ CLS(Memory) \ + CLS(PROBE) \ + CLS(MMIO_S1) // GetClassName is a macro defined by Windows.h, grrr... const char* GetLogClassName(Class logClass) { diff --git a/core/common/Logging/Filter.h b/src/common/Logging/Filter.h similarity index 100% rename from core/common/Logging/Filter.h rename to src/common/Logging/Filter.h diff --git a/core/common/Logging/Log.h b/src/common/Logging/Log.h similarity index 100% rename from core/common/Logging/Log.h rename to src/common/Logging/Log.h diff --git a/core/common/Logging/LogEntry.h b/src/common/Logging/LogEntry.h similarity index 100% rename from core/common/Logging/LogEntry.h rename to src/common/Logging/LogEntry.h diff --git a/core/common/Logging/LogTypes.h b/src/common/Logging/LogTypes.h similarity index 98% rename from core/common/Logging/LogTypes.h rename to src/common/Logging/LogTypes.h index a580535..6333090 100644 --- a/core/common/Logging/LogTypes.h +++ b/src/common/Logging/LogTypes.h @@ -34,6 +34,8 @@ enum class Class : const u8 { System, // Base System messages Render, // OpenGL and Window messages ARM, + PROBE, + MMIO_S1, Memory, Count // Total number of logging classes }; diff --git a/core/common/Logging/TextFormatter.cpp b/src/common/Logging/TextFormatter.cpp similarity index 100% rename from core/common/Logging/TextFormatter.cpp rename to src/common/Logging/TextFormatter.cpp diff --git a/core/common/Logging/TextFormatter.h b/src/common/Logging/TextFormatter.h similarity index 100% rename from core/common/Logging/TextFormatter.h rename to src/common/Logging/TextFormatter.h diff --git a/core/common/PathUtil.cpp b/src/common/PathUtil.cpp similarity index 100% rename from core/common/PathUtil.cpp rename to src/common/PathUtil.cpp diff --git a/core/common/PathUtil.h b/src/common/PathUtil.h similarity index 100% rename from core/common/PathUtil.h rename to src/common/PathUtil.h diff --git a/core/common/PolyfillThread.h b/src/common/PolyfillThread.h similarity index 100% rename from core/common/PolyfillThread.h rename to src/common/PolyfillThread.h diff --git a/core/common/StringUtil.cpp b/src/common/StringUtil.cpp similarity index 100% rename from core/common/StringUtil.cpp rename to src/common/StringUtil.cpp diff --git a/core/common/StringUtil.h b/src/common/StringUtil.h similarity index 100% rename from core/common/StringUtil.h rename to src/common/StringUtil.h diff --git a/core/common/Thread.cpp b/src/common/Thread.cpp similarity index 100% rename from core/common/Thread.cpp rename to src/common/Thread.cpp diff --git a/core/common/Thread.h b/src/common/Thread.h similarity index 100% rename from core/common/Thread.h rename to src/common/Thread.h diff --git a/core/common/Types.h b/src/common/Types.h similarity index 100% rename from core/common/Types.h rename to src/common/Types.h diff --git a/core/common/swap.h b/src/common/swap.h similarity index 100% rename from core/common/swap.h rename to src/common/swap.h diff --git a/core/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt similarity index 100% rename from core/frontend/CMakeLists.txt rename to src/frontend/CMakeLists.txt diff --git a/core/frontend/color.cpp b/src/frontend/color.cpp similarity index 100% rename from core/frontend/color.cpp rename to src/frontend/color.cpp diff --git a/core/frontend/color.h b/src/frontend/color.h similarity index 100% rename from core/frontend/color.h rename to src/frontend/color.h diff --git a/core/frontend/gui.cpp b/src/frontend/gui.cpp similarity index 100% rename from core/frontend/gui.cpp rename to src/frontend/gui.cpp diff --git a/core/frontend/gui.h b/src/frontend/gui.h similarity index 100% rename from core/frontend/gui.h rename to src/frontend/gui.h diff --git a/core/frontend/panels.cpp b/src/frontend/panels.cpp similarity index 98% rename from core/frontend/panels.cpp rename to src/frontend/panels.cpp index d58abae..4a6677b 100644 --- a/core/frontend/panels.cpp +++ b/src/frontend/panels.cpp @@ -1,7 +1,7 @@ #include "panels.h" #include #include -#include "arm64/isa.h" +#include "kvm/kvm.h" #include "common/Assert.h" int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data, @@ -94,7 +94,7 @@ int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup) if (::ImGui::Button("Run CPU Test", ImVec2(120, 0))) { - pound::arm64::cpuTest(); + pound::kvm::cpuTest(); *show_cpu_result_popup = true; } if (true == *show_cpu_result_popup) diff --git a/core/frontend/panels.h b/src/frontend/panels.h similarity index 100% rename from core/frontend/panels.h rename to src/frontend/panels.h diff --git a/core/host/CMakeLists.txt b/src/host/CMakeLists.txt similarity index 100% rename from core/host/CMakeLists.txt rename to src/host/CMakeLists.txt diff --git a/core/host/memory/arena.cpp b/src/host/memory/arena.cpp similarity index 100% rename from core/host/memory/arena.cpp rename to src/host/memory/arena.cpp diff --git a/core/host/memory/arena.h b/src/host/memory/arena.h similarity index 100% rename from core/host/memory/arena.h rename to src/host/memory/arena.h diff --git a/core/host/memory/arena_allocator.h b/src/host/memory/arena_stl.h similarity index 96% rename from core/host/memory/arena_allocator.h rename to src/host/memory/arena_stl.h index 4bd9c74..e8fb59c 100644 --- a/core/host/memory/arena_allocator.h +++ b/src/host/memory/arena_stl.h @@ -1,9 +1,9 @@ #ifndef POUND_ARENA_ALLOCATOR_H #define POUND_ARENA_ALLOCATOR_H +#include "arena.h" #include #include -#include "arena.h" namespace pound::host::memory { @@ -19,7 +19,7 @@ namespace pound::host::memory arena_t my_arena = memory::arena_init(4096); arena_allocator alloc(&my_arena); - std::vector> vec(alloc); + std::vector> vec(alloc); vec.push_back(42); // ... arena_reset(&my_arena); // Frees all allocations in the arena diff --git a/src/kvm/CMakeLists.txt b/src/kvm/CMakeLists.txt new file mode 100644 index 0000000..6bee731 --- /dev/null +++ b/src/kvm/CMakeLists.txt @@ -0,0 +1,10 @@ +#Copyright 2025 Pound Emulator Project.All rights reserved. + +set(KVM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mmu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mmio.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/kvm.cpp +) + +target_sources(Pound PRIVATE ${KVM_SOURCES}) + + target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..) diff --git a/core/arm64/guest.h b/src/kvm/guest.h similarity index 98% rename from core/arm64/guest.h rename to src/kvm/guest.h index 9d78990..60012f0 100644 --- a/core/arm64/guest.h +++ b/src/kvm/guest.h @@ -3,7 +3,7 @@ #include #include -namespace pound::arm64::memory +namespace pound::kvm::memory { /* @@ -193,4 +193,4 @@ static inline void guest_mem_writeq(guest_memory_t* memory, uint64_t gpa, uint64 uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa); *hva = val; } -} // namespace pound::arm64::memory +} // namespace pound::kvm::memory diff --git a/core/arm64/isa.cpp b/src/kvm/kvm.cpp similarity index 93% rename from core/arm64/isa.cpp rename to src/kvm/kvm.cpp index 5dfe355..16ba9c1 100644 --- a/core/arm64/isa.cpp +++ b/src/kvm/kvm.cpp @@ -1,12 +1,25 @@ -#include "isa.h" +#include "kvm.h" #include #include "common/Logging/Log.h" #include "guest.h" #include "host/memory/arena.h" -namespace pound::arm64 +namespace pound::kvm { -void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address) + +uint8_t kvm_probe(kvm_t* kvm, enum target_type type) +{ + if (type != KVM_TARGET_SWITCH1) + { + assert(!"Only Switch 1 is supported"); + } + kvm->ops = s1_ops; + /* Go to targets/switch1/hardware/probe.cpp */ + (void)kvm->ops.init(kvm); + return 0; +} + +void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address) { assert(nullptr != vcpu); /* An EC holds 6 bits.*/ @@ -68,7 +81,7 @@ void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uin * @param memory A pointer to an initialized guest_memory_t struct. * @return true if all tests pass, false otherwise. */ -bool test_guest_ram_access(pound::arm64::memory::guest_memory_t* memory) +bool test_guest_ram_access(pound::kvm::memory::guest_memory_t* memory) { LOG_INFO(Memory, "--- [ Starting Guest RAM Access Test ] ---"); if (memory == nullptr || memory->base == nullptr || memory->size < 4096) @@ -154,14 +167,13 @@ bool test_guest_ram_access(pound::arm64::memory::guest_memory_t* memory) void cpuTest() { - vcpu_state_t vcpu_states[CPU_CORES] = {}; pound::host::memory::arena_t guest_memory_arena = pound::host::memory::arena_init(GUEST_RAM_SIZE); assert(nullptr != guest_memory_arena.data); - pound::arm64::memory::guest_memory_t guest_ram = {}; + pound::kvm::memory::guest_memory_t guest_ram = {}; guest_ram.base = static_cast(guest_memory_arena.data); guest_ram.size = guest_memory_arena.capacity; (void)test_guest_ram_access(&guest_ram); } -} // namespace pound::arm64 +} // namespace pound::kvm diff --git a/core/arm64/isa.h b/src/kvm/kvm.h similarity index 59% rename from core/arm64/isa.h rename to src/kvm/kvm.h index d3b990d..1ecd8c0 100644 --- a/core/arm64/isa.h +++ b/src/kvm/kvm.h @@ -1,19 +1,17 @@ // Copyright 2025 Pound Emulator Project. All rights reserved. #pragma once +#include "guest.h" #include #include #include -namespace pound::arm64 +namespace pound::kvm { /* AArch64 R0-R31 */ #define GP_REGISTERS 32 -/* AArch64 V0-V31 */ -#define FP_REGISTERS 32 - #define CACHE_LINE_SIZE 64 #define GUEST_RAM_SIZE 10240 // 10KiB #define CPU_CORES 8 @@ -30,8 +28,7 @@ namespace pound::arm64 #define PSTATE_EL1H 0b0101 /* - * vcpu_state_t - Holds the architectural and selected system-register state for an emulated vCPU. - * @v: 128-bit SIMD/FP vector registers V0–V31. + * kvm_vcpu_t - Holds the architectural and selected system-register state for an emulated vCPU. * @r: General-purpose registers X0–X31 (X31 as SP/ZR as appropriate). * @pc: Program Counter. * @cntfreq_el0: Counter Frequency. @@ -61,7 +58,6 @@ namespace pound::arm64 */ typedef struct alignas(CACHE_LINE_SIZE) { - unsigned __int128 v[FP_REGISTERS]; uint64_t r[GP_REGISTERS]; uint64_t pc; uint64_t cntfreq_el0; @@ -134,7 +130,92 @@ typedef struct alignas(CACHE_LINE_SIZE) uint32_t dczid_el0; uint32_t pmcr_el0; uint32_t pstate; -} vcpu_state_t; +} kvm_vcpu_t; + +/* This is here to break the circular dependency between kvm_t and kvm_ops_t. */ +typedef struct kvm_s kvm_t; + +/* + * struct kvm_ops_t - A table of machine-specific operations. + * @init: Function pointer to initialize the target machine's state. + * Called once by kvm_probe(). It is responsible for setting up + * the guest memory map, loading firmware, and registering all + * MMIO device handlers. + * @mmio_read: Function pointer to handle a guest read from an MMIO region. + * @mmio_write: Function pointer to handle a guest write to an MMIO region. + * @destroy: Function pointer to clean up and deallocate all resources + * associated with the machine instance. Called on VM shutdown. + * + * This structure acts as a "virtual table" in C, allowing the generic KVM + * core to call target-specific code (e.g., for a Switch 1 or Switch 2) + * without needing to know the implementation details. Each supported target + * machine must provide a globally visible instance of this struct. + */ +typedef struct +{ + /* Initialize the machine state */ + int8_t (*init)(kvm_t* kvm); + + /* Handles an MMIO read from the guest. */ + int8_t (*mmio_read)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); + + /* Handles an MMIO write from the guest. */ + int8_t (*mmio_write)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); + + /* Clean up on shutdown */ + void (*destroy)(kvm_t* kvm); +} kvm_ops_t; + +/* + * kvm_s - The main KVM instance structure. + * @vcpu: The state of the emulated virtual CPU core. Contains all guest- + * visible registers. + * @memory: A structure representing the guest's physical RAM. + * @ops: A table of function pointers to the machine-specific implementation + * for this KVM instance. This should only be set by kvm_probe(). + * + * This structure represents a single virtual machine instance. + */ +struct kvm_s +{ + pound::kvm::kvm_vcpu_t vcpu; + pound::kvm::memory::guest_memory_t memory; + kvm_ops_t ops; +}; + +/** + * s1_ops - The machine-specific operations for the "Switch 1" target. + * + * This is the global instance of the operations table for the Switch 1. + * It is defined in the target-specific source file + * (targets/switch1/hardware/probe.cpp) and provides the iplementations + * for initializing and running the emulated Switch 1 hardware. + */ +extern const kvm_ops_t s1_ops; + +enum target_type +{ + KVM_TARGET_SWITCH1 = 0, + KVM_TARGET_SWITCH2 = 1, +}; + +/* + * kvm_probe - Probes for and initializes a target machine configuration. + * @kvm: A pointer to the KVM instance to be initialized. This function will + * populate the fields of this struct. + * @type: The type of target machine to initialize. + * + * This function is the primary factory for creating a virtual machine. It + * looks up the requested machine @type, attaches the corresponding operations + * table (e.g., s1_ops) to the @kvm instance, and calls the machine-specific + * init() function. + * + * On successful return, the @kvm struct will be fully configured and ready + * for execution. + * + * Return: 0 on success, or a negative errno code on failure. + */ +uint8_t kvm_probe(kvm_t* kvm, enum target_type type); /* * take_synchronous_exception() - Emulates the hardware process of taking a synchronous exception to EL1. @@ -151,7 +232,7 @@ typedef struct alignas(CACHE_LINE_SIZE) * program counter by branching to the appropriate offset in the EL1 vector table. * */ -void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address); +void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address); void cpuTest(); -} // namespace pound::arm64 +} // namespace pound::kvm diff --git a/src/kvm/mmio.cpp b/src/kvm/mmio.cpp new file mode 100644 index 0000000..567bf25 --- /dev/null +++ b/src/kvm/mmio.cpp @@ -0,0 +1,141 @@ +#include "mmio.h" +#include +#include + +namespace pound::kvm::memory +{ +/* + * This function implements a strict weak ordering comparison on two + * MMIO ranges, based solely on their starting guest physical address. + * + * It is designed to be used with std::lower_bound. + */ +bool mmio_compare_ranges(const mmio_range_t& a, const mmio_range_t& b) +{ + return a.gpa_base < b.gpa_base; +} + +int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_handler_t handler) +{ + assert(nullptr != db); + assert((db->address_ranges.size() + 1) <= MMIO_REGIONS); + + auto it = std::lower_bound(db->address_ranges.begin(), db->address_ranges.end(), range, mmio_compare_ranges); + size_t i = it - db->address_ranges.begin(); + + /* + * Scenario: UART is a current region, TIMER is a new region being + * registered. + * + * [-- UART --] + * 0x9000 0x9004 + * [---- TIMER ----] <-- CONFLICT! + * 0x9002 0x900A + */ + if (i > 0) + { + if (range.gpa_base < db->address_ranges[i - 1].gpa_end) + { + return EADDRESS_OVERLAP; + } + } + + /* + * Scenario: UART is a current region, TIMER is a new region being + * registered. + * + * [---- TIMER ----] <-- CONFLICT! + * 0x9000 0x9004 + * [-- UART --] + * 0x9002 0x900A + */ + if (i < db->address_ranges.size()) + { + if (db->address_ranges[i].gpa_base < range.gpa_end) + { + return EADDRESS_OVERLAP; + } + } + + db->address_ranges.insert(it, range); + db->handlers.insert(db->handlers.begin() + i, handler); + return MMIO_SUCCESS; +} + +bool mmio_compare_addresses(const mmio_range_t& a, const mmio_range_t& b) +{ + return a.gpa_base < b.gpa_base; +} + +int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len) +{ + assert(nullptr != db); + assert(nullptr != kvm); + assert(nullptr != data); + assert(len > 0); + + mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0}; + /* Find the first region that starts after the target gpa */ + auto it = + std::upper_bound(db->address_ranges.begin(), db->address_ranges.end(), search_key, mmio_compare_addresses); + + /* If `it` is the beginning, then the gpa is smaller than all known regions. */ + if (db->address_ranges.begin() == it) + { + return ENOT_HANDLED; + } + + mmio_range_t candidate = *(it - 1); + /* base <= gpa < end */ + if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end)) + { + size_t i = (it - 1) - db->address_ranges.begin(); + if (nullptr == db->handlers[i].write) + { + return EACCESS_DENIED; + } + + db->handlers[i].write(kvm, gpa, data, len); + return MMIO_SUCCESS; + } + + /* The gpa is not in any mmio region. */ + return ENOT_HANDLED; +} + +int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len) +{ + assert(nullptr != db); + assert(nullptr != kvm); + assert(nullptr != data); + assert(len > 0); + + mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0}; + /* Find the first region that starts after the target gpa */ + auto it = + std::upper_bound(db->address_ranges.begin(), db->address_ranges.end(), search_key, mmio_compare_addresses); + + /* If `it` is the beginning, then the gpa is smaller than all known regions. */ + if (db->address_ranges.begin() == it) + { + return ENOT_HANDLED; + } + + mmio_range_t candidate = *(it - 1); + /* base <= gpa < end */ + if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end)) + { + size_t i = (it - 1) - db->address_ranges.begin(); + if (nullptr == db->handlers[i].read) + { + return EACCESS_DENIED; + } + + db->handlers[i].read(kvm, gpa, data, len); + return MMIO_SUCCESS; + } + + /* The gpa is not in any mmio region. */ + return ENOT_HANDLED; +} +} // namespace pound::kvm::memory diff --git a/src/kvm/mmio.h b/src/kvm/mmio.h new file mode 100644 index 0000000..3fff682 --- /dev/null +++ b/src/kvm/mmio.h @@ -0,0 +1,191 @@ +#pragma once + +#include +#include +#include "host/memory/arena_stl.h" +#include "kvm.h" + +namespace pound::kvm::memory +{ +/* + * MMIO_REGIONS - The maximum number of distinct MMIO regions supported. + * + * It sets a hard limit on how many separate hardware device regions + * can be registered at boot time. + */ +#define MMIO_REGIONS 20 + +/* MMIO_SUCCESS - Return code for a successfull MMIO operation. */ +#define MMIO_SUCCESS 0 + +/* EADDRESS_OVERLAP - Error code for an MMIO address space conflict. */ +#define EADDRESS_OVERLAP (-1) + +#define ENOT_HANDLED (-2) + +#define EACCESS_DENIED (-3) + +/* + * typedef mmio - Function pointer type for an MMIO access handler. + * @kvm: A pointer to the KVM instance. + * @gpa: The guest physical address of the access. + * @data: A pointer to the data buffer. For reads, this buffer + * should be filled by the handler. For writes, this buffer + * contains the data written by the guest. + * @len: The size of the access in bytes. + * + * This function pointer defines the contract for all MMIO read + * and write handler functions. Handlers are responsible for emulating + * the hardware's response to a memory access at a specific register + * address. + * + * Returns: MMIO_SUCCESS on success, negative errno code on failure. + */ +typedef int8_t (*mmio)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); + +/* + * mmio_handler_t - A pair of handlers for an MMIO region. + * @read: A function pointer to be called for read access within the + * region. Can be NULL if the region is write-only. + * @write: A function pointer to be called for write access within the + * region. Can NULL if the region is read-only. + * + * This structure stores the read and write operations for a single + * hardware device or memory region. + * + */ +typedef struct +{ + mmio read; + mmio write; +} mmio_handler_t; + +/* + * mmio_range_t - Defines a half-open guest physical address range. + * @gpa_base: The starting (inclusive) guest physical address of + * the region. + * @gpa_end: The ending (exclusive) guest physical address of the region. + * + * This structure defines a contiguous block of guest physical address + * space, [gpa_base, gpa_end). The use of an exclusive end address + * simplifies range and adjacency calculations. + */ +typedef struct +{ + uint64_t gpa_base; + uint64_t gpa_end; +} mmio_range_t; + +/* + * mmio_db_t - A data-oriented database for MMIO dispatch. + * @handlers: A vector of MMIO handler pairs. + * @address_ranges: A vector of physical address ranges, sorted by GPA base. + * + * This structure manages all registered Memory-Mapped I/O regions for a + * virtual machine. It is designed with a "Structure of Arrays" layout + * to maximize host CPU cache efficiency during lookups. + */ +typedef struct +{ + /* + * This uses a custom arena allocator to ensure that all handler nodes + * are allocated fron a single, pre-allocated memory block. + * + * This is a parallel array to @address_ranges. + */ + std::vector> handlers; + + /* + * This vector is the primary target for the binary search lookup + * in the MMIO dispatcher. Maintaining its sort order is critical + * for the performance of the system. + */ + std::vector> address_ranges; +} mmio_db_t; + +/* + * Registers a new MMIO region into the database. + * @db: A pointer to the MMIO database to be modified. + * @range: The new region's address space. + * @handler: The read and write callbacks. + * + * This function safely inserts a new MMIO region into the database. + * + * Returns: + * MMIO_SUCCESS on successfull registration. + * EADDRESS_OVERLAP if the new @range conflicts with any existing region. + */ +int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_handler_t handler); + +/* + * mmio_db_dispatch_write - Dispatches a guest physical write to a registered MMIO handler. + * @db: A pointer to the MMIO database to be queried. + * @kvm: A pointer to the KVM instance. + * @gpa: The guest physical address of the memory write. + * @data: A pointer to the buffer containing the data written by the guest. + * @len: The size of the write access in bytes. + * + * This function is on the critical path ("hot path") of the emulator. It + * performs a high-performance binary search to determine if the target @gpa + * falls within any of the registered MMIO regions. + * + * The logic is a two-stage process: + * 1. An approximate search using std::upper_bound finds the first region that + * starts *after* the target @gpa. The actual candidate region must be the + * one immediately preceding this result. + * 2. A precise check verifies if the @gpa is contained within the candidate + * region's half-open interval [base, end). + * + * If a match is found, the corresponding write handler is invoked. If not, the + * function signals that the access is not handled by the MMIO system and + * should be treated as a normal RAM access. + * + * --- Visual Scenario --- + * + * Database Ranges: [-- R1 --) [---- R2 ----) [--- R3 ---) + * Address Space: 0x1000 0x1010 0x4000 0x4080 0x9000 0x9010 + * + * Search for GPA = 0x4020: + * + * 1. upper_bound() finds the first region starting > 0x4020, which is R3. + * The iterator 'it' points to R3 at index 2. + * + * [-- R1 --) [---- R2 ----) [--- R3 ---) + * ^ + * | + * 'it' + * + * 2. The candidate for the search is the region before 'it', which is R2. + * + * [-- R1 --) [---- R2 ----) [--- R3 ---) + * ^ + * | + * candidate + * + * 3. Final check: Is 0x4020 >= R2.base (0x4000) AND < R2.end (0x4080)? Yes. + * Result: Match found. Dispatch to handler for R2. + * + * Return: + * MMIO_SUCCESS if the write was handled by a registered device. Returns + * ENOT_HANDLED if the @gpa does not map to any MMIO region. + * EACCESS_DENIED if the MMIO region has no write function pointer. + */ +int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); + +/* + * mmio_db_dispatch_read - Dispatches a guest physical read to a registered MMIO handler. + * @db: A pointer to the MMIO database to be queried. + * @kvm: A pointer to the KVM instance. + * @gpa: The guest physical address of the memory write. + * @data: A pointer to the buffer containing the data written by the guest. + * @len: The size of the write access in bytes. + * + * See @mmio_db_dispatch_write() for proper explanation. + * + * Return: + * MMIO_SUCCESS if the write was handled by a registered device. Returns + * ENOT_HANDLED if the @gpa does not map to any MMIO region. + * EACCESS_DENIED if the MMIO region has no write function pointer. + */ +int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); +} // namespace pound::kvm::memory diff --git a/core/arm64/mmu.cpp b/src/kvm/mmu.cpp similarity index 98% rename from core/arm64/mmu.cpp rename to src/kvm/mmu.cpp index 3ed509b..870d803 100644 --- a/core/arm64/mmu.cpp +++ b/src/kvm/mmu.cpp @@ -1,8 +1,8 @@ #include "mmu.h" #include -#include "isa.h" +#include "kvm.h" -namespace pound::arm64::memory +namespace pound::kvm::memory { #define GRANULE_4KB (1ULL << 12) #define GRANULE_16KB (1ULL << 14) @@ -42,7 +42,7 @@ static inline uint8_t msvc_ctzll(unsigned long long val) /* Define the size of a page table entry (descriptor) */ #define PAGE_TABLE_ENTRY_SHIFT 3 /* log2(8 bytes) */ -int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa) +int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa) { const uint8_t SCTLR_EL1_M_BIT = (1 << 0); if (0 == (vcpu->sctlr_el1 & SCTLR_EL1_M_BIT)) @@ -392,4 +392,4 @@ int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uin } return -1; } -} // namespace pound::arm64::memory +} // namespace pound::kvm::memory diff --git a/core/arm64/mmu.h b/src/kvm/mmu.h similarity index 89% rename from core/arm64/mmu.h rename to src/kvm/mmu.h index 85ba026..6822415 100644 --- a/core/arm64/mmu.h +++ b/src/kvm/mmu.h @@ -1,9 +1,9 @@ #pragma once #include "guest.h" -#include "isa.h" +#include "kvm.h" -namespace pound::arm64::memory +namespace pound::kvm::memory { /* * mmu_gva_to_gpa() - Translate a Guest Virtual Address to a Guest Physical Address. @@ -36,5 +36,5 @@ namespace pound::arm64::memory * Return: 0 on successful translation. A negative error code on a translation * fault (e.g., for a page fault, permission error, or alignment fault). */ -int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa); -} // namespace pound::arm64::memory +int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa); +} // namespace pound::kvm::memory diff --git a/core/main.cpp b/src/main.cpp similarity index 100% rename from core/main.cpp rename to src/main.cpp diff --git a/src/targets/switch1/hardware/CMakeLists.txt b/src/targets/switch1/hardware/CMakeLists.txt new file mode 100644 index 0000000..06e09f6 --- /dev/null +++ b/src/targets/switch1/hardware/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2025 Pound Emulator Project. All rights reserved. + +set(HARDWARE_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/probe.cpp +) + +target_sources(Pound PRIVATE + ${HARDWARE_SOURCES} +) + +target_include_directories(Pound PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) diff --git a/src/targets/switch1/hardware/probe.cpp b/src/targets/switch1/hardware/probe.cpp new file mode 100644 index 0000000..36f1b3c --- /dev/null +++ b/src/targets/switch1/hardware/probe.cpp @@ -0,0 +1,39 @@ +#include "kvm/kvm.h" +#include "common/Logging/Log.h" + +namespace pound::kvm +{ +static int8_t s1_init(kvm_t* kvm); +static int8_t s1_mmio_read(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); +static int8_t s1_mmio_write(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len); +static void s1_destroy(kvm_t* kvm); + +const kvm_ops_t s1_ops = { + .init = s1_init, + .mmio_read = s1_mmio_read, + .mmio_write = s1_mmio_write, + .destroy = s1_destroy, +}; + + +static int8_t s1_init(kvm_t* kvm) +{ + LOG_INFO(PROBE, "Initializing Switch 1 virtual machine"); + /* BOOTSTRAPPING CODE GOES HERE */ + return 0; +} +static int8_t s1_mmio_read(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len) +{ + /* TODO(GloriousTacoo:kvm) */ + return 0; +} +static int8_t s1_mmio_write(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len) +{ + /* TODO(GloriousTacoo:kvm) */ + return 0; +} +static void s1_destroy(kvm_t* kvm) +{ + /* TODO(GloriousTacoo:kvm) */ +} +}