mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-12 01:36:57 +00:00
Move design docs to one folder
Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
fddd5b6694
commit
5850a13f18
3 changed files with 0 additions and 0 deletions
153
design_docs/DESIGN_DOC_ASSERT_FRAMEWORK.md
Normal file
153
design_docs/DESIGN_DOC_ASSERT_FRAMEWORK.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
## **Design Document: Core Assertion Subsystem**
|
||||
|
||||
**Author:** GloriousTacoo, Lead Developer
|
||||
**Status:** FINAL
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-09-20
|
||||
|
||||
*Disclaimer: This document was written by AI. I'm not a good technical writer.riter**
|
||||
|
||||
### **1. Problem Statement**
|
||||
|
||||
We require a rigorous, inescapable, and informative assertion framework that codifies our fail-fast philosophy. An invalid program state must lead to immediate, loud, and unrecoverable termination. There is no other acceptable outcome. This system will serve as the bedrock of the PVM's stability, ensuring that programmer errors are caught and exposed, not hidden.
|
||||
|
||||
### **2. Glossary**
|
||||
|
||||
* **Assertion:** A predicate within the code that declares an invariant—a condition that must be true for the program to be considered correct. Its failure indicates a bug.
|
||||
* **Predicate:** The boolean expression evaluated by an assertion.
|
||||
* **Assertion Failure:** The event triggered when an assertion's predicate evaluates to false. This signifies a critical, non-recoverable programmer error.
|
||||
* **Fail-Fast:** The core design principle of this entire project. The system will not attempt to limp along in a corrupted state. At the first sign of an invalid condition, it will terminate.
|
||||
* **Unrecoverable Error:** Any state from which continued correct operation cannot be guaranteed. By definition, any assertion failure signals such a state.
|
||||
|
||||
### **3. Breaking Changes**
|
||||
|
||||
* All usage of the standard library `<assert.h>` is hereby deprecated and forbidden.
|
||||
* A pre-commit hook will be integrated into the repository. It will scan for the token `assert(` and will reject any commit that contains it. This is not a suggestion. Your code will not be accepted if it uses the standard macro.
|
||||
* All existing modules must be migrated to the new `PVM_ASSERT` API. This is a one-time, mandatory refactoring effort. All pull requests will be blocked until this migration is complete.
|
||||
|
||||
### **4. Success Criteria**
|
||||
|
||||
* **Adoption:** 100% of all precondition, postcondition, and invariant checks within the PVM codebase will utilize the new assertion framework. Zero exceptions.
|
||||
* **Information Richness:** A failed assertion must produce a diagnostic message on `stderr` containing, at minimum: the full text of the failed expression, the source file name, the line number, the enclosing function name, and an optional, developer-supplied formatted message.
|
||||
* **Inescapability:** The assertion framework must be impossible to disable. The state of the `NDEBUG` macro will have no effect. Assertions are a permanent, non-optional feature of the executable in all build configurations.
|
||||
* **Termination Guarantee:** A failed assertion must, without exception, result in immediate program termination via a call to `abort()`. No cleanup, no unwinding, no second chances. The process will stop *now*.
|
||||
|
||||
### **5. Proposed Design**
|
||||
|
||||
This framework is built upon an unyielding philosophy. You will internalize it.
|
||||
|
||||
* **Tenet 1: Bugs Are Defects.** An assertion failure is not a runtime error. It is proof of a flaw in the logic of the code. It is a bug that you, the developer, introduced. The purpose of this system is to expose these flaws.
|
||||
* **Tenet 2: Failure is Absolute.** There is no "graceful" way to handle a broken invariant. The only correct action is immediate termination to prevent the propagation of a corrupt state. The output must be loud, clear, and provide maximum context to diagnose the defect.
|
||||
* **Tenet 3: Correctness is Not a "Debug" Feature.** Assertions are always on. They are an integral part of the executable's logic and its contract for safe operation. Any performance argument against this is invalid and will be dismissed. The cost of a passing check is infinitesimal; the cost of a latent bug is mission failure.
|
||||
* **Tenet 4: Clarity is Mandatory.** The API will be simple, but its use requires discipline. You will provide context in your assertions. A naked expression is often not enough to explain *why* a condition must be true.
|
||||
|
||||
The API will consist of three macros. Use them correctly.
|
||||
|
||||
`PVM_ASSERT(expression)`
|
||||
`PVM_ASSERT_MSG(expression, fmt, ...)`
|
||||
`PVM_UNREACHABLE()`
|
||||
|
||||
### **6. Technical Design**
|
||||
|
||||
The implementation will be brutally simple and effective.
|
||||
|
||||
1. **Frontend Macros (`common/assert.h`):**
|
||||
* These macros are the complete public API. There are no other entry points.
|
||||
* `PVM_ASSERT(expression)`: Expands to an `if` statement. If `(expression)` is false, it calls the internal failure handler, passing the stringified expression (`#expression`), file, line, and function name.
|
||||
```c
|
||||
do {
|
||||
if (!(expression)) {
|
||||
pound_internal_assert_fail(__FILE__, __LINE__, __func__, #expression, NULL);
|
||||
}
|
||||
} while(0)
|
||||
```
|
||||
* `PVM_ASSERT_MSG(expression, fmt, ...)`: Similar to the above, but if the check fails, it first formats the developer-supplied message into a temporary buffer and passes that buffer to the failure handler. The formatting cost is **only** paid on failure. There is no excuse for not using this macro to provide context for complex invariants.
|
||||
* `PVM_UNREACHABLE()`: This is not a suggestion. It is a declaration that a code path is logically impossible. It expands to a direct call to the failure handler with a static message like "Unreachable code executed". If this assertion ever fires, the logical model of the function is wrong.
|
||||
|
||||
2. **Failure Handler (`common/assert.c`):**
|
||||
* A single, internal C function: `void pound_internal_assert_fail(const char* file, int line, const char* func, const char* expr_str, const char* user_msg)`.
|
||||
* This function is marked with `_Noreturn` (or `__attribute__((noreturn))`). It will never return to the caller. The compiler will know this.
|
||||
* It will lock a mutex to prevent garbled output if two threads fail simultaneously (a near-impossibility, but we engineer for correctness).
|
||||
* It will format a single, comprehensive, multi-line error message to `stderr`. The format is fixed and not subject to debate:
|
||||
```
|
||||
================================================================================
|
||||
PVM ASSERTION FAILURE
|
||||
================================================================================
|
||||
File: src/kvm/mmu.cpp
|
||||
Line: 521
|
||||
Function: mmu_translate_va
|
||||
Expression: page_table->entry[idx].is_valid()
|
||||
Message: Attempted to translate VA 0xDEADBEEF but page table entry was invalid.
|
||||
================================================================================
|
||||
Terminating program via abort(). Core dump expected.
|
||||
```
|
||||
* After printing, it will immediately call `abort()`.
|
||||
|
||||
### **7. Components**
|
||||
|
||||
* **Application Modules (kvm, frontend, etc.):** These are the components whose logic is being enforced. They will include `common/assert.h` and use the macros to state invariants.
|
||||
* **Assertion Core (`common/assert`):** This small, self-contained module provides the macros and the single failure handler function. It has no purpose other than to enforce correctness and terminate the program.
|
||||
* **Build System:** Will be configured to enforce the ban on `<assert.h>`.
|
||||
|
||||
### **8. Dependencies**
|
||||
|
||||
* **C Standard Library:** For `fprintf`, `stderr`, and `abort`.
|
||||
|
||||
### **9. Major Risks & Mitigations**
|
||||
|
||||
* **Risk 1: Performance Complacency.** A developer may write a computationally expensive operation inside an assertion's predicate.
|
||||
* **Mitigation:** This is a failure of code review, not a failure of the framework. The performance of a *passing* assertion (a boolean check) is negligible and is the accepted cost of safety. The performance of a *failing* assertion is irrelevant, as the program ceases to exist. Code reviewers are directed to reject any assertion that performs non-trivial work. Assertions check state; they do not compute it.
|
||||
* **Risk 2: Confusion with Error Handling.** A developer might use `PVM_ASSERT` to handle recoverable, runtime errors (e.g., failed I/O, invalid user input).
|
||||
* **Mitigation:** Documentation and merciless code review. This will be made explicitly clear: **Assertions are for bugs.** They are for conditions that, if false, prove the program's logic is flawed. Runtime errors must be handled through status codes or other proper error-handling mechanisms. Any pull request that misuses assertions for error handling will be rejected immediately.
|
||||
|
||||
### **10. Out of Scope**
|
||||
|
||||
* **Error Recovery:** The word "recovery" does not apply to an assertion failure. It is, by definition, an unrecoverable state.
|
||||
* **Crash Reporting Infrastructure:** This framework's responsibility ends at calling `abort()`. The generation of a core dump and any subsequent analysis is the responsibility of the execution environment, not this library. We provide the trigger; other systems can handle the post-mortem.
|
||||
* **Any Form of "Gentle" Shutdown:** Resources will not be freed. Files will not be flushed. Sockets will not be closed. Such actions would be performed by a program in a corrupt state and cannot be trusted. `abort()` is the only clean exit.
|
||||
|
||||
### **12. Alternatives Considered**
|
||||
|
||||
* **Alternative #1: Use standard `assert.h` and globally `#undef NDEBUG`.**
|
||||
* **Pros:** Requires no new code.
|
||||
* **Cons:** The output is spartan and implementation-defined. It provides no mechanism for custom, formatted messages. It creates a dependency on a build flag that could be accidentally overridden by a sub-project or complex build configuration. It is fragile.
|
||||
* **Reasons Discarded:** Insufficiently informative and not robust enough for Pound. We will control our own destiny, not hope a build flag is set correctly.
|
||||
|
||||
* **Alternative #2: Ad-hoc `if (condition) { fprintf(...); abort(); }`.**
|
||||
* **Pros:** None. This is engineering malpractice.
|
||||
* **Cons:** Verbose, error-prone, guarantees inconsistent output formats, and omits critical context like the stringified expression and function name unless manually added each time. It is a recipe for unmaintainable chaos.
|
||||
* **Reasons Discarded:** This is not a real alternative. It is an anti-pattern that this framework is designed to eradicate.
|
||||
|
||||
### **13. Appendix**
|
||||
#### **Example Usage and Output**
|
||||
|
||||
**Code:**
|
||||
```c
|
||||
// in some function in memory_manager.c
|
||||
void* memory_manager_alloc(struct mmu* mmu, size_t bytes) {
|
||||
PVM_ASSERT_MSG(mmu != NULL, "MMU context must not be null.");
|
||||
PVM_ASSERT_MSG(bytes > 0 && bytes < MAX_ALLOC_SIZE, "Invalid allocation size requested: %zu bytes", bytes);
|
||||
|
||||
// ... logic ...
|
||||
|
||||
// This case should be handled by prior logic
|
||||
if (page_is_full) {
|
||||
PVM_UNREACHABLE();
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
**Sample output from a failed assertion:**
|
||||
```
|
||||
================================================================================
|
||||
PVM ASSERTION FAILURE
|
||||
================================================================================
|
||||
File: src/core/memory_manager.c
|
||||
Line: 84
|
||||
Function: memory_manager_alloc
|
||||
Expression: bytes > 0 && bytes < MAX_ALLOC_SIZE
|
||||
Message: Invalid allocation size requested: 0 bytes
|
||||
================================================================================
|
||||
Terminating program via abort().
|
||||
124
design_docs/DESIGN_DOC_LOGGING_FRAMEWORK.md
Normal file
124
design_docs/DESIGN_DOC_LOGGING_FRAMEWORK.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
## **Design Document: Core Logging Subsystem**
|
||||
|
||||
**Author:** GloriousTaco, Lead Developer
|
||||
**Status:** FINAL
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-09-14
|
||||
|
||||
*Disclaimer: This was mostly written with AI. I'm not a good technical writer*
|
||||
|
||||
### **1. Problem Statement**
|
||||
|
||||
The Pound project's current logging system is full of object oriented abstractions with no documentation. The system currently risides in `src/common/Logging` with no one going anywhere near it. However, as we move on from prototyping to testing, we require a logging framework that provides a performant diagnostic output and is easy to maintain.
|
||||
|
||||
### **2. Glossary**
|
||||
|
||||
* **Log Level:** A classification of a log message's severity (e.g., TRACE, DEBUG, INFO, WARN, ERROR, FATAL).
|
||||
* **Log Sink:** A destination for log messages. This can be a console, a file, a network socket, etc.
|
||||
* **Structured Logging:** A practice of logging messages in a consistent, machine-readable format (e.g., JSON), with key-value pairs, rather than unstructured text strings.
|
||||
* **Compile-Time Log Level:** The minimum log level that will be compiled into the binary. Messages below this level are completely removed by the preprocessor, incurring zero runtime cost.
|
||||
* **Runtime Log Level:** The minimum log level that the system will process and output at runtime. This can be changed dynamically without recompiling.
|
||||
* **PVM:** Pound Virtual Machine, the overall project.
|
||||
|
||||
### **3. Breaking Changes**
|
||||
|
||||
* This design will deprecate and forbid all usage of `printf`, `fprintf(stderr, ...)` and other direct-to-console I/O for diagnostic purposes within the PVM codebase (excluding `main.cpp` for initial setup/teardown).
|
||||
* A pre-commit hook will be introduced to enforce this policy, which will cause existing pull requests to fail until they are updated to use the new logging API.
|
||||
* All existing modules will require modification to adopt the new logging API.
|
||||
|
||||
### **4. Success Criteria**
|
||||
|
||||
* **Adoption:** 100% of diagnostic messages in the `kvm`, `common`, `host`, and `frontend` modules will use the new logging system.
|
||||
* **Performance:** In a release build with the runtime log level set to `INFO`, the overhead of disabled `DEBUG` and `TRACE` log statements shall be statistically unmeasurable (<0.1% performance impact) compared to a binary compiled with logging completely disabled.
|
||||
* **Usability:** A developer can successfully add a new namespaced log message and filter the system output to show logs only from their module within 15 minutes, using only the API header and a quick-start guide.
|
||||
|
||||
### **5. Proposed Design**
|
||||
|
||||
We will implement a lightweight, header-only, macro-based logging framework heavily inspired by systems like `spdlog` but simplified for our specific needs. The core design is built on the following tenets:
|
||||
|
||||
* **Tenet 1: Performance is Paramount.** Logging is a diagnostic tool; it must never be the cause of a performance issue. The system will aggressively optimize away disabled log calls at compile time.
|
||||
* **Tenet 2: Structure is Mandatory.** All log messages will be structured, capturing not just a message but also the severity level, timestamp, source location (file and line), and module.
|
||||
* **Tenet 3: Control is Granular.** Developers must have fine-grained control over logging verbosity at both compile time and runtime, on a per-module basis.
|
||||
* **Tenet 4: Simplicity in Use.** The API presented to developers must be simple and intuitive, encouraging adoption through macros like `LOG_WARN(...)`.
|
||||
|
||||
The primary user interface will be a set of macros:
|
||||
`LOG_TRACE(module, fmt, ...)`
|
||||
`LOG_DEBUG(module, fmt, ...)`
|
||||
`LOG_INFO(module, fmt, ...)`
|
||||
`LOG_WARN(module, fmt, ...)`
|
||||
`LOG_ERROR(module, fmt, ...)`
|
||||
`LOG_FATAL(module, fmt, ...)`
|
||||
|
||||
### **6. Technical Design**
|
||||
|
||||
The system will be composed of three main parts: the frontend macros, the logging core, and the output sink.
|
||||
|
||||
1. **Frontend Macros:**
|
||||
* The `LOG_X` macros will be the only public-facing API.
|
||||
* the `LOG_FATAL` macro will be terminal. After logging the message, it will immediately terminate the program via a call to `abort()`.
|
||||
* The Log macros will first check against a `COMPILE_TIME_LOG_LEVEL`. If the message level is below this threshold, the macro will expand to nothing (`(void)0`), ensuring the code and its arguments are completely compiled out.
|
||||
* If the level is sufficient, the macro will expand into a call to a logging core function, automatically passing `__FILE__`, `__LINE__`, the log level, and the module name.
|
||||
* This will live in a `common/logging.h` header.
|
||||
|
||||
2. **Logging Core:**
|
||||
* A central `logger_log()` function will be the target of the macros.
|
||||
* This function will check the message's log level against a globally configured `runtime_log_level` for the specified module. If the level is insufficient, the function returns immediately.
|
||||
* If the level is sufficient, it will capture the current high-resolution timestamp, format the message string using the `fmt` library (which we already have as a dependency), and pass the formatted output string to the active sink.
|
||||
* A small utility will manage the runtime log levels for each registered module (e.g., `logger_set_level("kvm", LEVEL_TRACE)`).
|
||||
|
||||
3. **Output Sink:**
|
||||
* The default sink will be a thread-safe, mutex-protected object that writes to `stderr`.
|
||||
* The output format will be structured and non-negotiable: `[ISO8601 Timestamp] [LEVEL] [module] [file:line] Message`
|
||||
* Example: `[2025-09-14T11:23:45.1234Z] [ERROR] [kvm] [mmu.cpp:412] Page table fault at GPA 0xDEADBEEF: Invalid permissions.`
|
||||
* The design will allow for the possibility of replacing this sink in the future (e.g., to log to a file), but the initial implementation will be `stderr` only.
|
||||
|
||||
### **7. Components**
|
||||
|
||||
* **Application Modules (kvm, frontend, etc.):** These are the *producers* of log messages. They will include `common/logging.h` and use the `LOG_X` macros.
|
||||
* **Logging Core (`common/logging`):** The central library responsible for filtering, formatting, and dispatching log messages.
|
||||
* **Sink (`common/logging`):** The *consumer* of formatted log messages. Initially, this is the `stderr` writer.
|
||||
* **Main Application (`main.cpp`):** The owner of the system. It is responsible for initializing the logging system, setting initial runtime log levels (e.g., from command-line arguments), and shutting it down cleanly.
|
||||
|
||||
### **8. Dependencies**
|
||||
|
||||
* **`fmt` library:** Will be used for high-performance string formatting. This is already a project dependency.
|
||||
* **C++ Standard Library:** Specifically `<chrono>` for timestamps and `<mutex>` for thread safety in the sink.
|
||||
|
||||
### **9. Major Risks & Mitigations**
|
||||
|
||||
* **Risk 1: Performance Overhead.** Careless implementation could lead to significant overhead even for enabled logs (e.g., excessive string allocation, slow timestamping).
|
||||
* **Mitigation:** The use of the `fmt` library is a known high-performance choice. We will benchmark the logging of 1 million messages in a tight loop to quantify the overhead and ensure it meets the success criteria.
|
||||
* **Risk 2: Thread Safety Issues.** Improper locking in the sink could lead to garbled output or race conditions when multiple threads log simultaneously.
|
||||
* **Mitigation:** A single `std::mutex` will protect all writes to the sink. Code will be peer-reviewed specifically for thread-safety concerns.
|
||||
* **Risk 3: Slow Adoption / Incorrect Usage.** Developers may continue to use `printf` out of habit.
|
||||
* **Mitigation:** The API will be designed for extreme ease of use. A short, clear guide will be written. Critically, a pre-commit hook and CI linting job will be added to the build system to automatically fail any code that uses `printf`/`fprintf`. This makes the correct path the only path.
|
||||
|
||||
### **10. Out of Scope**
|
||||
|
||||
* **File-based Logging:** The initial version will only log to `stderr`. A file sink is a desirable future feature but is not required for V1.
|
||||
* **Network Logging:** Transmitting logs over a network is not a requirement.
|
||||
* **Log Rotation:** Since we are not logging to files, rotation is not applicable.
|
||||
* **Dynamic Sink Swapping:** The sink will be configured at startup and fixed for the application's lifetime.
|
||||
|
||||
### **12. Alternatives Considered**
|
||||
|
||||
* **Alternative #1: Use a full-featured third-party library (e.g., `spdlog`, `glog`).**
|
||||
* **Pros:** Mature, feature-rich, well-tested.
|
||||
* **Cons:** Adds another third-party dependency to maintain. May contain features (async logging, complex file sinks) that add unnecessary complexity and code size for our specific use case. Our needs are simple enough that a minimal, custom implementation is justified.
|
||||
* **Reasons Discarded:** The primary reason is to minimize external dependencies and code footprint. We can achieve 95% of the value with 10% of the complexity by building a minimal system tailored to our exact needs, leveraging our existing `fmt` dependency.
|
||||
|
||||
* **Alternative #2: "Do Nothing" (Continue using `printf`).**
|
||||
* **Pros:** No development effort required.
|
||||
* **Cons:** Unstructured, impossible to filter, no severity levels, no timestamps, not thread-safe. Fails to meet every requirement for a mission-safe diagnostic system.
|
||||
* **Reasons Discarded:** This is a non-starter. It is fundamentally unsuitable for the project's goals.
|
||||
|
||||
### **13. Appendix**
|
||||
#### **Benchmarking Performance**
|
||||
1. **Baseline Measurement (A):** A simple `for` loop will be created that iterates 1 million times, performing a trivial, non-optimizable operation (e.g., incrementing a `volatile` integer). The total execution time of this loop will be measured using a high-resolution clock.
|
||||
2. **Disabled Log Measurement (B):** The same loop will be modified to include a `LOG_DEBUG` call. The test binary will be compiled with a `COMPILE_TIME_LOG_LEVEL` of `INFO`. This means the `LOG_DEBUG` macro will expand to `(void)0` and be completely compiled out. The execution time of this loop will be measured.
|
||||
3. **Enabled Log Measurement (C):** The same loop will be run, but with the `runtime_log_level` set to allow the `LOG_DEBUG` messages to be processed and written to the sink. The sink's output will be redirected to `/dev/null` to measure the cost of formatting and dispatch, not the I/O cost of the terminal itself. The execution time will be measured.
|
||||
|
||||
**Analysis:**
|
||||
* The difference between **(B)** and **(A)** should be zero or statistically insignificant, proving the success criterion that disabled logs have no overhead.
|
||||
* The difference between **(C)** and **(A)** represents the full overhead of an enabled log call. This allows us to calculate the average cost-per-message on our specific hardware.
|
||||
|
||||
107
design_docs/DESIGN_DOC_MMIO.md
Normal file
107
design_docs/DESIGN_DOC_MMIO.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
## **Design Document: MMIO Subsystem**
|
||||
|
||||
**Author:** GloriousTacoo, Lead Developer
|
||||
**Status:** FINAL
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-09-20
|
||||
|
||||
*Disclaimer: This document was mostly written by AI. I'm not a good technical writer.*
|
||||
|
||||
### **1. Problem Statement**
|
||||
|
||||
A virtual machine is not merely a CPU core; it is a system. A system includes peripherals. In modern architectures, communication with these peripherals is overwhelmingly achieved via Memory-Mapped I/O (MMIO). A guest operating system's attempt to read from or write to a specific physical memory address is not a memory access, but a command to a virtual device.
|
||||
|
||||
The MMIO subsystem is the central nervous system for this communication. It is the gatekeeper that intercepts these special memory accesses and dispatches them to the correct emulated device handlers. Its failure is not an option. A slow, buggy, or poorly designed MMIO dispatcher results in a slow, buggy, and fundamentally incorrect virtual machine.
|
||||
|
||||
We require an MMIO dispatcher that is:
|
||||
1. **Fast:** It sits on the most critical path of the PVM—memory access. Its overhead must be minimal to the point of being statistically insignificant for non-MMIO accesses.
|
||||
2. **Correct:** It must unambiguously and correctly route every access to the one and only correct handler, or correctly identify it as a non-MMIO access.
|
||||
3. **Rigid:** The registration process for MMIO regions must be strict, preventing common errors such as overlapping address spaces at system configuration time, not at runtime.
|
||||
4. **Testable:** The entire subsystem must be verifiable in isolation, independent of the full PVM, to prove its correctness.
|
||||
|
||||
### **2. Glossary**
|
||||
|
||||
* **MMIO (Memory-Mapped I/O):** The use of a shared memory address space for both main memory and peripheral device registers.
|
||||
* **Guest Physical Address (GPA):** The physical memory address as seen from within the guest virtual machine. This is the address space the MMIO subsystem operates on.
|
||||
* **MMIO Region:** A contiguous, half-open range of GPAs, `[base, end)`, assigned to a single virtual device.
|
||||
* **MMIO Handler:** A function pointer (or pair of function pointers for read/write) that implements the behavior of a virtual device when its MMIO region is accessed.
|
||||
* **Dispatcher:** The core logic responsible for taking an incoming GPA and efficiently finding the corresponding MMIO handler.
|
||||
* **MMIO Database (`mmio_db_t`):** The central data structure holding all registered MMIO regions and their associated handlers.
|
||||
* **Structure of Arrays (SoA):** The chosen data layout for the MMIO database. Parallel arrays (`handlers`, `address_ranges`) are used instead of a single array of complex structs. This improves cache performance by ensuring memory accesses during the dispatch search are contiguous.
|
||||
|
||||
### **3. Breaking Changes**
|
||||
|
||||
* Any and all previous, ad-hoc mechanisms for device emulation are deprecated and will be removed.
|
||||
* All virtual devices (e.g., UART, interrupt controller, timers) **must** register their memory regions through the `mmio_db_register` function during the PVM's initialization phase. No other mechanism for device interaction will be permitted.
|
||||
* The system initialization sequence will be refactored to create and populate the `mmio_db_t` *before* the first guest instruction is executed.
|
||||
|
||||
### **4. Success Criteria**
|
||||
|
||||
* **Performance:** A dispatch call for a GPA that does *not* map to any MMIO region (the most common case) must complete in sub-microsecond time. The overhead should be dominated by the function call and a cache-hot binary search.
|
||||
* **Correctness:** 100% of MMIO accesses must be routed to the correct handler. 100% of non-MMIO accesses must be correctly identified as such (`ENOT_HANDLED`). There is no tolerance for mis-routing.
|
||||
* **Safety:** The `mmio_db_register` function must be provably correct. It must be impossible to register two overlapping MMIO regions. An attempt to do so will result in a fatal assertion failure during initialization.
|
||||
* **Test Coverage:** The MMIO dispatcher and registration logic will have 100% unit test coverage for all code paths, including error conditions and edge cases.
|
||||
|
||||
### **5. Proposed Design**
|
||||
|
||||
The design is based on the principle of **early failure and fast paths**. We do the expensive, complex work of validation and organization once at startup, allowing the runtime dispatch path to be brutally simple and fast. The core of the system is the `mmio_db_t` structure, a purpose-built database optimized for one single operation: finding the handler for a given GPA.
|
||||
|
||||
1. **Data-Oriented Structure:** The `mmio_db_t` will use a Structure of Arrays (SoA) layout. The `address_ranges` and `handlers` will be stored in separate, parallel `std::vector`s. When the dispatcher searches for an address, it will only scan through the `address_ranges` vector. This maximizes CPU cache-line utilization, as the search algorithm does not need to load and pollute the cache with handler function pointers, which are irrelevant until a match is found.
|
||||
|
||||
2. **Immutable Database at Runtime:** The MMIO database is configured once during PVM initialization. After the first guest instruction is executed, the database is considered immutable. There will be no mechanism for runtime registration or de-registration of MMIO regions. This simplifies the design enormously by eliminating the need for complex locking or thread-safety mechanisms on the database itself. The dispatcher can read from it without any synchronization primitives.
|
||||
|
||||
3. **High-Performance Dispatch:** The `mmio_db_dispatch_*` functions will use a binary search (`std::upper_bound`) on the `address_ranges` vector, which **must** be kept sorted by `gpa_base`. This provides logarithmic time complexity, `O(log N)`, where N is the number of MMIO regions. For the expected small number of regions (`MMIO_REGIONS` is 20), this is practically constant time and is the optimal search strategy. The search logic described in the header file's comments is sound and will be the basis of the implementation.
|
||||
|
||||
4. **Strict and Unforgiving Registration:** The `mmio_db_register` function is the sole gatekeeper for the database. It is not designed for performance; it is designed for absolute correctness. Before inserting a new region, it will perform a linear scan of all existing regions to guarantee that the new region does not overlap with any of them. If an overlap is detected, it will return `EADDRESS_OVERLAP`. The caller (the PVM initializer) will treat this error as a fatal configuration flaw and will `PVM_ASSERT_MSG(false, ...)` to terminate immediately. Configuration errors must be caught at boot, not during guest execution.
|
||||
|
||||
### **6. Technical Design**
|
||||
|
||||
The implementation will be based on the provided header file, with the following clarifications and mandates.
|
||||
|
||||
* **`mmio_db_t`**: The use of a custom arena allocator is approved. This ensures that all MMIO metadata resides in a single, contiguous block of memory, further improving locality of reference.
|
||||
* **`mmio_db_register`**:
|
||||
1. The function will first iterate through the *entire* existing `address_ranges` vector. For each `existing_range` in the vector, it will check if `new_range.gpa_base < existing_range.gpa_end && new_range.gpa_end > existing_range.gpa_base`. If this condition is ever true, an overlap exists. Return `EADDRESS_OVERLAP` immediately.
|
||||
2. If no overlap is found, the function will find the correct insertion point to maintain the sort order of `address_ranges`. `std::upper_bound` can be used to find this position efficiently.
|
||||
3. The new `range` and `handler` will be inserted into their respective vectors at the calculated position.
|
||||
* **`mmio_db_dispatch_read`/`write`**:
|
||||
1. Use `std::upper_bound` on `db->address_ranges` with a custom comparator that only compares the search GPA with the `gpa_base` of the `mmio_range_t` elements.
|
||||
2. This returns an iterator to the first element whose `gpa_base` is *greater than* the target GPA.
|
||||
3. If the iterator is the beginning of the vector, no preceding range exists, so it's a non-match. Return `ENOT_HANDLED`.
|
||||
4. Otherwise, decrement the iterator to get the candidate range (the one immediately before the one found by `upper_bound`).
|
||||
5. Perform the final check: `gpa >= candidate.gpa_base && gpa < candidate.gpa_end`.
|
||||
6. If it matches, retrieve the handler from the *parallel* `handlers` vector using the same index. Check if the required handler (`read` or `write`) is non-NULL. If it is NULL, return `EACCESS_DENIED`. Otherwise, call the handler and return its result.
|
||||
7. If the final check fails, the GPA is in a hole between regions. Return `ENOT_HANDLED`.
|
||||
|
||||
### **7. Testing**
|
||||
|
||||
The correctness of this subsystem is not a matter of luck; it will be proven. A dedicated unit test suite (`test_mmio.cpp`) will be created. It will have no dependencies on the rest of the PVM.
|
||||
|
||||
* **Test Harness:** A mock `kvm_t` object will be created. A set of dummy handler functions will be implemented that, when called, simply set a flag and return a specific value, allowing the test to verify that the correct handler was indeed called.
|
||||
|
||||
* **Registration Tests:**
|
||||
1. **Test Empty DB:** Test registration into a fresh, empty database.
|
||||
2. **Test Non-Overlapping:** Register several disjoint, non-adjacent regions (e.g., `[0x1000, 0x2000)`, `[0x5000, 0x6000)`) and assert that registration succeeds and the database remains sorted.
|
||||
3. **Test Adjacent:** Register two perfectly adjacent regions (e.g., `[0x1000, 0x2000)`, `[0x2000, 0x3000)`). This must succeed.
|
||||
4. **Test Overlap (Fatal):** Test every possible overlap condition and assert that `mmio_db_register` returns `EADDRESS_OVERLAP`:
|
||||
* New region completely contains an existing one.
|
||||
* Existing region completely contains the new one.
|
||||
* New region overlaps the end of an existing one.
|
||||
* New region overlaps the beginning of an existing one.
|
||||
* New region is identical to an existing one.
|
||||
5. **Test Full DB:** If we define a hard limit like `MMIO_REGIONS`, test that attempting to register more than this limit fails correctly (this may require an assertion or a specific error code).
|
||||
|
||||
* **Dispatch Tests:**
|
||||
1. **Setup:** Create a pre-populated database with several known regions (e.g., R1:[0x1000-0x1FFF], R2:[0x4000-0x4FFF], R3:[0x8000-0x8FFF]). R1 is read-only, R2 is write-only, R3 is read-write.
|
||||
2. **Test Hits:**
|
||||
* Dispatch a read to the beginning, middle, and last byte of R1. Verify the R1 read handler is called.
|
||||
* Dispatch a write to the beginning, middle, and last byte of R2. Verify the R2 write handler is called.
|
||||
* Dispatch both reads and writes to R3 and verify correct handler invocation.
|
||||
3. **Test Misses (Holes):**
|
||||
* Dispatch reads and writes to addresses *between* regions (e.g., `0x3000`, `0x6000`). Assert that the return code is `ENOT_HANDLED` and no handler is called.
|
||||
* Dispatch to an address below all regions (e.g., `0x0`).
|
||||
* Dispatch to an address above all regions (e.g., `0x9000`).
|
||||
4. **Test Access Denied:**
|
||||
* Dispatch a *write* to the read-only region R1. Assert the return is `EACCESS_DENIED`.
|
||||
* Dispatch a *read* to the write-only region R2. Assert the return is `EACCESS_DENIED`.
|
||||
|
||||
These tests are not optional. They are the only way to gain confidence in this critical component. They will be integrated into the CI/CD pipeline and must pass before any change to this subsystem is even considered for review. subsystem is even considered for review.
|
||||
Loading…
Add table
Add a link
Reference in a new issue