mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
kvm: Add framework for machine types and MMIO dispatch
The core of the machine-type support is the new operations table, kvm_ops_t. This acts as a standard C-style virtual table decoupling the generic KVM core logic from target specific hardware emualtion. The kvm_t VM instance now points to an ops table, which defines the "personality" of the guest. A kvm_probe() factory function has been added to initialize a kvm_t instance with the correct ops table for a given machine type (eg, Switch 1). The ops table's .mmio_read and .mmio_write function pointers are the link between the armv8 CPU core and this new MMIO dispatcher. When a physical memory access is determined to be MMIO, the VM will call the appropriate function pointer, which in turn will use the MMIO dispatcher to find and execute the correct device handler. The initial implementation for the Switch 1 target (targets/switch1/hardware/probe.cpp) is a stub. The bootstrapping logic will be added in subsequent patches. Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
dea94dc259
commit
c6706dd8a0
57 changed files with 533 additions and 70 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -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
|
||||
|
|
|
|||
13
3rd_Party/CMakeLists.txt
vendored
13
3rd_Party/CMakeLists.txt
vendored
|
|
@ -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()
|
||||
|
|
|
|||
1
3rd_Party/toml11
vendored
1
3rd_Party/toml11
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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} /..)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#include "memory.h"
|
||||
|
||||
namespace pound::aarch64::memory
|
||||
{
|
||||
|
||||
} // namespace pound::aarch64::memory
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
#include "Config.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <toml.hpp>
|
||||
|
||||
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<bool>(general, "Advanced Log", false);
|
||||
typeLog = toml::find_or<std::string>(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
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include "panels.h"
|
||||
#include <imgui.h>
|
||||
#include <math.h>
|
||||
#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)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#ifndef POUND_ARENA_ALLOCATOR_H
|
||||
#define POUND_ARENA_ALLOCATOR_H
|
||||
|
||||
#include "arena.h"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#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<int> alloc(&my_arena);
|
||||
|
||||
std::vector<int, memory::arena_allocator<int>> vec(alloc);
|
||||
std::vector<int, arena_allocator<int>> vec(alloc);
|
||||
vec.push_back(42);
|
||||
// ...
|
||||
arena_reset(&my_arena); // Frees all allocations in the arena
|
||||
10
src/kvm/CMakeLists.txt
Normal file
10
src/kvm/CMakeLists.txt
Normal file
|
|
@ -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} /..)
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
|
@ -1,12 +1,25 @@
|
|||
#include "isa.h"
|
||||
#include "kvm.h"
|
||||
#include <cassert>
|
||||
#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<uint8_t*>(guest_memory_arena.data);
|
||||
guest_ram.size = guest_memory_arena.capacity;
|
||||
|
||||
(void)test_guest_ram_access(&guest_ram);
|
||||
}
|
||||
} // namespace pound::arm64
|
||||
} // namespace pound::kvm
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
#include "guest.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
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
|
||||
141
src/kvm/mmio.cpp
Normal file
141
src/kvm/mmio.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "mmio.h"
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
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
|
||||
191
src/kvm/mmio.h
Normal file
191
src/kvm/mmio.h
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#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<mmio_handler_t, pound::host::memory::arena_allocator<mmio_handler_t>> 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<mmio_range_t, pound::host::memory::arena_allocator<mmio_range_t>> 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
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#include "mmu.h"
|
||||
#include <limits.h>
|
||||
#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
|
||||
|
|
@ -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
|
||||
13
src/targets/switch1/hardware/CMakeLists.txt
Normal file
13
src/targets/switch1/hardware/CMakeLists.txt
Normal file
|
|
@ -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}/..
|
||||
)
|
||||
39
src/targets/switch1/hardware/probe.cpp
Normal file
39
src/targets/switch1/hardware/probe.cpp
Normal file
|
|
@ -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) */
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue