1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
//! MMIO based simple printer device.
//!
//! ## Background
//! Memory-mapped I/O uses the same address space for both main memory and I/O devices.
//! Unlike PIO, which uses dedicated instructions, in MMIO, one can think of the memory and register of I/O devices as if
//! they are mapped in a specific address of main memory. This method allows for the convenient and fast definition of I/O
//! device behavior and concise implementation of the CPU and I/O devices.
//!
//! In memory-mapped I/O, the same memory address space is used for accessing both main memory (RAM) and I/O devices.
//! Commands for accessing main memory (such as load and store) are also used to access devices, reading from and writing to memory instead.
//! For instance, if there exists memory-mapped I/O (MMIO) mapping between a console device and CPU at address 0x00ff, writing 'a' to the 0x00ff will print a letter 'a' to the console device.
//!
//! The specific area of memory for these operations can be temporarily agreed upon between a CPU and device or it can be a permanently assigned area.
//! As an example, modern PCIe devices negotiate the locations of the MMIO sections with a CPU by using a base address register (BAR) in the initialization process.
//!
//! On modern CPUs, MMIO is performed by the memory management unit (MMU), which is a hardware component responsible for managing memory access.
//! When a program running on the CPU performs an I/O operation on a memory-mapped location,
//! the MMU intercepts the memory access and sends it to the appropriate hardware device, which performs the requested operation.
//! Each I/O device monitors the CPU's address bus and when the memory is accessed, the device executes the command and writes the
//! result to a specific memory location or performs the command.
//!
//! Memory-mapped I/O is generally faster than Programmed I/O (PIO) because it avoids the overhead of the processor having to manage the I/O operations directly.
//! In PIO, the CPU should execute IN and OUT operations to communicate with peripheral devices. Which causes delays and limits overall system performance.
//! In contrast, MMIO allows I/O devices to be directly mapped to memory and managed by MMU. This makes I/O operations can be performed quickly and efficiently,
//! without requiring the CPU to spend a lot of time managing the I/O operations directly.
//!
//!
//! ## Tasks
//! In this project, you are requested to implement simple virtual MMIO control for the purpose of the printing text on the host console.
//! In our MMIO PrinterDev specifications, we dictate the usage of 0xcafe0000 guest physical address for the buffer, which contains an array of utf8 strings.
//! The length of the array can be founded at the 0xcafe0008, while the doorbell is located at 0xcafe0010.
//! The doorbell in MMIO typically refers to a deginated memory location used to trigger device operations.
//! The PrinterDev virtual device lauches an operation when a write occurs to the doorbell address, and subsequently fetches the address and length of the string buffer.
//! It then parses the string data from the buffer and outputs given text to the host console.
//! In summary:
//! * 0xcafe0000: Guest physical address for the utf8 string buffer
//! * 0xcafe0008: Length of the utf8 string buffer
//! * 0xcafe0010: The doorbell which notifies VMM to print the registered string to the console
//!
//! Your device SHOULD parses the string data from the buffer and outputs the given text to the host console using [`PrinterProxy`].
//! You can translate the utf8 to str by using [`core::slice::from_utf8`] and a raw pointer to slice by using [`core::slice::from_raw_parts`].
//!
//! [`core::slice::from_utf8`]: https://doc.rust-lang.org/beta/core/str/fn.from_utf8.html
//! [`core::slice::from_raw_parts`]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
//! [`PrinterProxy`]: project2::PrinterProxy
use crate::vmexit::mmio::{self, MmioInfo, MmioRegion};
use core::fmt::Write;
use kev::{
vcpu::{GenericVCpuState, VmexitResult},
vm::Gpa,
Probe, VmError,
};
use project2::PrinterProxy;
pub struct PrinterDev {}
impl Default for PrinterDev {
fn default() -> Self {
Self {}
}
}
impl mmio::MmioHandler for PrinterDev {
fn region(&self) -> MmioRegion {
MmioRegion {
start: Gpa::new(0xcafe0000).unwrap(),
end: Gpa::new(0xcafe0018).unwrap(),
}
}
fn handle(
&mut self,
p: &dyn Probe,
info: MmioInfo,
GenericVCpuState { vmcs, .. }: &mut GenericVCpuState,
) -> Result<VmexitResult, VmError> {
// Mmio register:
// - 0xcafe0000 (8 bytes): physical address of the buffer.
// - 0xcafe0008 (8 bytes): length of the buffer.
// - 0xcafe0010 (8 bytes): the doorbell.
//
// - To print out the contents, you required to do following steps:
// 1. Write the buffer address to pa 0xcafe0000.
// 2. Write the buffer length to pa 0xcafe0008.
// 3. Write any value to 0xcafe0010 to ring the doorbell.
//
// Hint:
// - If io size is invalid, ignore the request.
// - You should reflect the change on the mmio area.
todo!()
}
}