From 8b1818985622084022a29fd15ed6c4b7ef8a3622 Mon Sep 17 00:00:00 2001 From: decoder3 Date: Sun, 11 Sep 2022 15:08:08 +0530 Subject: [PATCH 1/3] added test.md --- docs/test.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/test.md diff --git a/docs/test.md b/docs/test.md new file mode 100644 index 0000000..e69de29 From 0f42655528ad6a731b037d1d05956d711846c4f5 Mon Sep 17 00:00:00 2001 From: decoder3 Date: Sun, 11 Sep 2022 15:18:05 +0530 Subject: [PATCH 2/3] description of vmm implementation --- docs/{test.md => vmm_implementation.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{test.md => vmm_implementation.md} (100%) diff --git a/docs/test.md b/docs/vmm_implementation.md similarity index 100% rename from docs/test.md rename to docs/vmm_implementation.md From 48c6347a31fac0c734d9c8d3fa9bdcd04f1bd6dd Mon Sep 17 00:00:00 2001 From: decoder3 Date: Sun, 11 Sep 2022 21:07:23 +0530 Subject: [PATCH 3/3] vmm implementation documentation --- docs/vmm_implementation.md | 110 +++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/docs/vmm_implementation.md b/docs/vmm_implementation.md index e69de29..f551429 100644 --- a/docs/vmm_implementation.md +++ b/docs/vmm_implementation.md @@ -0,0 +1,110 @@ +# Serial Console + +```rs + fn add_serial_console(&mut self) -> Result<()> +``` +It emulates the Linux Serial Console by emulating a Serial COM Port. + +```rs + let interrupt_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).map_err(Error::IO)?; + let serial = Arc::new(Mutex::new(SerialWrapper(Serial::new( + interrupt_evt.try_clone().map_err(Error::IO)?, + stdout(), + )))); +``` + +The `Serial` device is created by passing a `Stdout` wrapper object that implements the `Write` trait. The `Serial` device is wrapped in an `Arc` and `Mutex` to make it shareable and safe to pass around. The `interrupt_evt` helps in the interaction between the driver and device by using a `Trigger` object for notifications. + +```rs + self.kernel_cfg + .cmdline + .insert_str("console=ttyS0") + .map_err(Error::Cmdline)?; + + #[cfg(target_arch = "aarch64")] + self.kernel_cfg + .cmdline + .insert_str(&format!("earlycon=uart,mmio,0x{:08x}", AARCH64_MMIO_BASE)) + .map_err(Error::Cmdline)?; + + #[cfg(target_arch = "x86_64")] + { + let range = PioRange::new(PioAddress(0x3f8), 0x8).unwrap(); + self.device_mgr + .lock() + .unwrap() + .register_pio(range, serial.clone()) + .unwrap(); + } + #[cfg(target_arch = "aarch64")] + { + let range = MmioRange::new(MmioAddress(AARCH64_MMIO_BASE), 0x1000).unwrap(); + self.device_mgr + .lock() + .unwrap() + .register_mmio(range, serial.clone()) + .unwrap(); + } +``` + +The `Serial` device is registered on the bus by using the `register_pio` or `register_mmio` methods. The `register_pio` method takes a `PioRange` object and the `Serial` device. The `register_mmio` method takes a `MmioRange` object and the `Serial` device. The `PioRange` and `MmioRange` objects are created by passing the base address and the size of the range. The `register_pio` and `register_mmio` methods are defined in the `DeviceManager` struct. `0x3f8` is the port IO address being registered for the serial device, Using it, we can print stuff on the screen. + +
+
+ +# i8042 Device + +```rs + fn add_i8042_device(&mut self) -> Result<()> { + let reset_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).map_err(Error::IO)?; + let i8042_device = Arc::new(Mutex::new(I8042Wrapper(I8042Device::new( + reset_evt.try_clone().map_err(Error::IO)?, + )))); + self.vm.register_irqfd(&reset_evt, 1)?; + } +``` + +It emulates a minimal i8042 controller just enough to shutdown the machine. It implements a `Trigger` object `reset_evt` which is used for `notifying` the `VMM` about the CPU `reset` event. + +
+
+ +# rtc Device + +```rs + #[cfg(target_arch = "aarch64")] + fn add_rtc_device(&mut self) { + let rtc = Arc::new(Mutex::new(RtcWrapper(Rtc::new()))); + let range = MmioRange::new(MmioAddress(AARCH64_MMIO_BASE + 0x1000), 0x1000).unwrap(); + } +``` + +It implements a `PL031` Real Time Clock (RTC) that `emulates` a long time base counter. + +
+
+ +# Setting up the FDT + +```rs +fn setup_fdt(&mut self) -> Result<()> { + let mem_size: u64 = self.guest_memory.iter().map(|region| region.len()).sum(); + let fdt_offset = mem_size - AARCH64_FDT_MAX_SIZE - 0x10000; + let fdt = FdtBuilder::new() + .with_cmdline(self.kernel_cfg.cmdline.as_str()) + .with_num_vcpus(self.num_vcpus.try_into().unwrap()) + .with_mem_size(mem_size) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt() + .map_err(Error::SetupFdt)?; + fdt.write_to_mem(&self.guest_memory, fdt_offset) + .map_err(Error::SetupFdt)?; + Ok(()) + } +``` + + This function sets up the FDT(File Descriptor Table) for the guest. The FDT is a data structure that describes the hardware configuration of the guest. It is used by the guest kernel to initialize the hardware. The FDT is written to the guest memory at the offset `fdt_offset`. The `fdt_offset` is calculated by subtracting the `AARCH64_FDT_MAX_SIZE` and `0x10000` from the `mem_size`. The `AARCH64_FDT_MAX_SIZE` is the maximum size of the FDT and `0x10000` is the offset from the end of the guest memory. The `fdt_offset` is used to write the FDT to the guest memory. The `fdt` is created by using the `FdtBuilder` struct. The `FdtBuilder` struct is used to create the FDT by adding the nodes and properties to it. The `with_cmdline` method adds the `cmdline` property to the FDT. The `with_num_vcpus` method adds the `num_vcpus` property to the FDT. The `with_mem_size` method adds the `mem_size` property to the FDT. The `with_serial_console` method adds the `serial_console` node to the FDT. The `with_rtc` method adds the `rtc` node to the FDT. The `write_to_mem` method writes the FDT to the guest memory at the offset `fdt_offset`. + + +