diff --git a/.cargo/config.toml b/.cargo/config.toml index a3511f9..1c6d81d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,20 @@ [target.riscv64imac-unknown-none-elf] -runner = "qemu-system-riscv64 -S -gdb tcp::10000 -machine virt -bios none -nographic -m 2G -initrd vmlinux_debug -kernel" -#runner = "qemu-system-riscv64 -S -gdb tcp::10000 -d int,in_asm,cpu_reset,mmu,page,guest_errors -machine virt -bios none -nographic -m 2G -initrd vmlinux -kernel" -#runner = "qemu-system-riscv64 -machine virt -bios none -nographic -m 2G -initrd vmlinux -kernel" +runner = """ +qemu-system-riscv64 +-machine virt +-bios none +-nographic +-m 2G +-initrd vmlinux +-drive file=rootfs.img,format=raw,id=hd0,if=none +-device virtio-blk-pci,drive=hd0,iommu_platform=true,disable-legacy=on +-append root=/dev/vda,rw,console=ttyS0 +-device riscv-iommu-pci +-kernel +""" +# for debug +# runner = "../../qemu/build/qemu-system-riscv64 -S -gdb tcp::10000 -d int,in_asm,cpu_reset,mmu,page,guest_errors -machine virt -bios none -nographic -m 2G -initrd vmlinux_debug -drive file=rootfs.img,format=raw,id=hd0,if=none -device virtio-blk-pci,drive=hd0,iommu_platform=true,disable-legacy=on -append root=/dev/vda,rw,console=ttyS0 -device riscv-iommu-pci -kernel" +# memo: maintenance packet Qqemu.PhyMemMode:1 rustflags = [ "-C", "link-arg=-Tmemory.x", diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..ce37815 --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,21 @@ +name: Tag + +on: + push: + branches: + - master + paths: + - Cargo.toml + +jobs: + tag: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + - uses: salsify/action-detect-and-tag-new-version@v2 + id: detect_tag + with: + create-tag: true + version-command: cargo read-manifest | jq -r .version diff --git a/.gitignore b/.gitignore index 7c35647..4d56d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,5 @@ Cargo.lock # Added by me .gdb_history -fedora-vmlinux -stage4-disk.img +vmlinux vmlinux_debug diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 20dd4cd..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "wild-screen-alloc"] - path = wild-screen-alloc - url = git@github.com:Alignof/wild-screen-alloc.git diff --git a/Cargo.toml b/Cargo.toml index bcf44cd..23a4326 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hikami" -version = "0.4.0" +version = "1.0.0" edition = "2021" [lints.clippy] @@ -10,11 +10,10 @@ pedantic = "warn" elf = { version = "0.7.2", default-features = false } fdt = "0.1.5" linked_list_allocator = "0.10.5" -raki = { version = "1.0.0-beta.3" } +raki = { version = "1.0.0" } riscv = "0.11.1" riscv-rt = "0.11.0" rustsbi = { version = "0.4.0-alpha.1", features = ["machine"] } sbi-rt = "0.0.3" sbi-spec = { version = "0.0.7", features = [ "legacy" ] } spin = "0.9.8" -wild_screen_alloc = { version = "0.1.1", path = "wild-screen-alloc" } diff --git a/README.md b/README.md index 88257ff..8554c6e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,84 @@ # hikami [![Rust](https://github.com/Alignof/hikami/actions/workflows/rust.yml/badge.svg)](https://github.com/Alignof/hikami/actions/workflows/rust.yml) -Light weight hypervisor for RISC-V H-extension. +Light weight type-1 hypervisor for RISC-V H-extension. -## Tutorial -TODO +This project aims not only to realize a lightweight hypervisor that can be used on RISC-V H extensions, but also to easily reproduce and manage the "extension" on the hypervisor. (currently in progress) +Poster in RISC-V Days Tokyo 2024 Summer: [PDF](https://riscv.or.jp/wp-content/uploads/RV-Days_Tokyo_2024_Summer_paper_9.pdf) + +## Run Linux +### Build QEMU +We need to build manually the QEMU to support IOMMU. +``` +$ git clone https://github.com/qemu/qemu.git -b staging +$ cd qemu/ +# https://patchwork.ozlabs.org/project/qemu-devel/list/?series=417654 +$ wget https://patchwork.ozlabs.org/series/417654/mbox/ --output-document riscv-QEMU-RISC-V-IOMMU-Support.patch +$ git apply riscv-QEMU-RISC-V-IOMMU-Support.patch +$ ./configure --target-list=riscv64-softmmu +$ make -j $(nproc) +# $ sudo make install +``` +Ver. 9.2 or later should officially support IOMMU, so it should no longer be necessary to apply patches. + +### Build Linux +``` +$ git clone https://github.com/torvalds/linux -b v6.9 + +$ cd /path/to/this/repository +$ cp ./guest_image/.config /path/to/linux + +$ cd /path/to/linux +$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig +$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j$(nproc) +$ mv vmlinux /path/to/this/repository +``` +See also for custom guest image: `guest_image/README.md`. + +### Create rootfs +``` +$ git clone https://gitee.com/mirrors/busyboxsource.git +$ cd busyboxsource + +# Select: Settings -> Build Options -> Build static binary +$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make menuconfig + +$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make -j8 +$ CROSS_COMPILE=riscv64-unknown-linux-gnu- make install + +$ cd ../ +$ qemu-img create rootfs.img 1g +$ mkfs.ext4 rootfs.img + +$ mkdir rootfs +$ mount -o loop rootfs.img rootfs +$ cd rootfs +$ cp -r ../busyboxsource/_install/* . +$ mkdir proc dev tec etc/init.d + +$ cd etc/init.d/ +$ cat << EOS > rcS +#!/bin/sh +mount -t proc none /proc +mount -t sysfs none /sys +/sbin/mdev -s +EOS + +$ chmod +x rcS + +$ umount rootfs +$ mv rootfs.img /path/to/this/repository +``` + +### Run +``` +# The actual command to be executed is written in .cargo/config.toml. +$ cargo r +``` + +## Documents +``` +$ cargo doc --open +``` ## References - [The RISC-V Instruction Set Manual: Volume I Version 20240411](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf) diff --git a/guest.dtb b/guest.dtb index 3916c92..14c012b 100644 Binary files a/guest.dtb and b/guest.dtb differ diff --git a/guest_image/README.md b/guest_image/README.md index 6daee42..5e1a6fd 100644 --- a/guest_image/README.md +++ b/guest_image/README.md @@ -1,17 +1,24 @@ # Build guest image -## Linux +## device tree ``` -git clone https://github.com/torvalds/linux -b v6.9 +$ ./build_dtb.sh create +$ vim guest.dts # edit dts +$ ./build_dtb.sh build +# guest.dtb is created to repository root. +``` + +## Linux (with debug info) +``` +$ git clone https://github.com/torvalds/linux -b v6.9 -cd /path/to/this/repository -cp ./.config /path/to/linux +$ cd /path/to/this/repository +$ cp ./guest_image/.config /path/to/linux -cd /path/to/linux -make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig -# For debug -# make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig -# DEBUG_KERNEL [=y], DEBUG_INFO [=y], EFI [=n], RELOCATABLE [=n] -make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j$(nproc) -mv vmlinux /path/to/linux/vmlinx_debug +$ cd /path/to/linux +$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig +$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig +$ DEBUG_KERNEL [=y], DEBUG_INFO [=y], EFI [=n], RELOCATABLE [=n] +$ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j$(nproc) +$ mv vmlinux /path/to/linux/vmlinx_debug ``` diff --git a/guest_image/build_dtb.sh b/guest_image/build_dtb.sh index 7a36df1..e3a1d29 100755 --- a/guest_image/build_dtb.sh +++ b/guest_image/build_dtb.sh @@ -12,7 +12,17 @@ fi if [ "$#" -eq 1 ]; then case "$1" in "create") - qemu-system-riscv64 -S -gdb tcp::10000 -machine virt -bios none -m 256M -machine dumpdtb=qemu.dtb + qemu-system-riscv64 -S -gdb tcp::10000 \ + -machine virt \ + -bios none \ + -m 256M \ + -initrd ../vmlinux_debug \ + -device riscv-iommu-pci \ + -drive file=../rootfs.img,format=raw,id=hd0,if=none \ + -device virtio-blk-device,drive=hd0 \ + -append "root=/dev/vda rw console=ttyS0" \ + -kernel ../target/riscv64imac-unknown-none-elf/debug/hikami \ + -machine dumpdtb=qemu.dtb dtc -I dtb -O dts -o guest.dts qemu.dtb rm -f qemu.dtb ;; diff --git a/guest_image/guest.dts b/guest_image/guest.dts index 79589ca..f59304f 100644 --- a/guest_image/guest.dts +++ b/guest_image/guest.dts @@ -47,10 +47,10 @@ riscv,cbop-block-size = <0x40>; riscv,cboz-block-size = <0x40>; riscv,cbom-block-size = <0x40>; - riscv,isa-extensions = "i\0m\0a\0f\0d\0c\0h\0zic64b\0zicbom\0zicbop\0zicboz\0ziccamoa\0ziccif\0zicclsm\0ziccrse\0zicntr\0zicsr\0zifencei\0zihintntl\0zihintpause\0zihpm\0za64rs\0zawrs\0zfa\0zca\0zcd\0zba\0zbb\0zbc\0zbs\0ssccptr\0sscounterenw\0sstc\0sstvala\0sstvecd\0svadu"; + riscv,isa-extensions = "i\0m\0a\0f\0d\0c\0h\0zic64b\0zicbom\0zicbop\0zicboz\0ziccamoa\0ziccif\0zicclsm\0ziccrse\0zicntr\0zicsr\0zifencei\0zihintntl\0zihintpause\0zihpm\0zmmul\0za64rs\0zaamo\0zalrsc\0zawrs\0zfa\0zca\0zcd\0zba\0zbb\0zbc\0zbs\0ssccptr\0sscounterenw\0sstc\0sstvala\0sstvecd\0svadu"; riscv,isa-base = "rv64i"; - riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_za64rs_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu"; - mmu-type = "riscv,sv39"; + riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zmmul_za64rs_zaamo_zalrsc_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu"; + mmu-type = "riscv,sv57"; interrupt-controller { #interrupt-cells = <0x01>; @@ -89,8 +89,11 @@ }; chosen { + bootargs = "root=/dev/vda rw console=ttyS0"; + linux,initrd-end = <0x00 0x97f67ce0>; + linux,initrd-start = <0x00 0x88000000>; stdout-path = "/soc/serial@10000000"; - rng-seed = <0x84243e45 0x3047b358 0xbacf5c32 0x578c88fe 0x89c61300 0x205ccc21 0x650e0486 0xf27a7c6f>; + rng-seed = <0xcaf53f7f 0x52fe6542 0xf3156947 0xa50b572d 0xc6f64192 0x47d6e429 0x1985224b 0xe8a94cc2>; }; soc { @@ -196,7 +199,11 @@ pci@30000000 { interrupt-map-mask = <0x1800 0x00 0x00 0x07>; interrupt-map = <0x00 0x00 0x00 0x01 0x03 0x20 0x00 0x00 0x00 0x02 0x03 0x21 0x00 0x00 0x00 0x03 0x03 0x22 0x00 0x00 0x00 0x04 0x03 0x23 0x800 0x00 0x00 0x01 0x03 0x21 0x800 0x00 0x00 0x02 0x03 0x22 0x800 0x00 0x00 0x03 0x03 0x23 0x800 0x00 0x00 0x04 0x03 0x20 0x1000 0x00 0x00 0x01 0x03 0x22 0x1000 0x00 0x00 0x02 0x03 0x23 0x1000 0x00 0x00 0x03 0x03 0x20 0x1000 0x00 0x00 0x04 0x03 0x21 0x1800 0x00 0x00 0x01 0x03 0x23 0x1800 0x00 0x00 0x02 0x03 0x20 0x1800 0x00 0x00 0x03 0x03 0x21 0x1800 0x00 0x00 0x04 0x03 0x22>; - ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>; + // BUS_ADDRESS(3) CPU_PHYSICAL(2) SIZE(2) + ranges = < + 0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 + 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 + 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>; reg = <0x00 0x30000000 0x00 0x10000000>; dma-coherent; bus-range = <0x00 0xff>; @@ -206,6 +213,14 @@ #size-cells = <0x02>; #interrupt-cells = <0x01>; #address-cells = <0x03>; + iommu-map = <0x00 0x8000 0x00 0x08 0x09 0x8000 0x09 0xfff7>; + + iommu@8 { + reg = <0x800 0x00 0x00 0x00 0x00>; + phandle = <0x8000>; + #iommu-cells = <0x01>; + compatible = "riscv,pci-iommu"; + }; }; }; }; diff --git a/src/device.rs b/src/device.rs index ffe6141..ad16f46 100644 --- a/src/device.rs +++ b/src/device.rs @@ -2,30 +2,55 @@ pub mod clint; mod initrd; +pub mod iommu; mod pci; -mod plic; +pub mod plic; mod rtc; pub mod uart; mod virtio; use crate::memmap::page_table::PteFlag; use crate::memmap::{page_table, HostPhysicalAddress, MemoryMap}; -use crate::HypervisorData; use alloc::vec::Vec; use fdt::Fdt; /// Page table for device -const PTE_FLAGS_FOR_DEVICE: [PteFlag; 6] = [ - PteFlag::Dirty, - PteFlag::Accessed, - PteFlag::Write, - PteFlag::Read, - PteFlag::User, - PteFlag::Valid, -]; +const PTE_FLAGS_FOR_DEVICE: [PteFlag; 4] = + [PteFlag::Write, PteFlag::Read, PteFlag::User, PteFlag::Valid]; -/// A struct that implement Device trait **must** has `base_addr` and size member. -pub trait Device { +/// Device emulation error. +#[allow(clippy::module_name_repetitions)] +pub enum DeviceEmulateError { + /// Invalid plic address. + InvalidAddress, + /// Context ID is out of range. + InvalidContextId, + /// Accessed register is reserved. + ReservedRegister, +} + +/// Pci device. +/// +/// A struct that implement this trait **must** has `bus`, `device`, `function` number. +#[allow(clippy::module_name_repetitions)] +pub trait PciDevice { + /// Create self instance. + /// * `device_tree` - struct Fdt + /// * `node_path` - node path in fdt + fn new(device_tree: &Fdt, node_path: &str) -> Option + where + Self: Sized; + + /// Initialize pci device. + /// * `pci` - struct `Pci` + fn init(&self, pci: &pci::Pci); +} + +/// Memory mapped I/O device. +/// +/// A struct that implement this trait **must** has `base_addr` and size member. +#[allow(clippy::module_name_repetitions)] +pub trait MmioDevice { /// Create self instance. /// * `device_tree` - struct Fdt /// * `node_path` - node path in fdt @@ -34,7 +59,7 @@ pub trait Device { fn size(&self) -> usize; /// Return address of physical memory fn paddr(&self) -> HostPhysicalAddress; - /// Return memory map between physical to physical + /// Return memory map between physical to physical (identity map) for crate page table. fn memmap(&self) -> MemoryMap; } @@ -45,26 +70,59 @@ pub trait Device { #[derive(Debug)] pub struct Devices { pub uart: uart::Uart, - pub virtio: Vec, + pub virtio_list: virtio::VirtIoList, pub initrd: initrd::Initrd, pub plic: plic::Plic, pub plic_context: usize, pub clint: clint::Clint, - pub pci: pci::Pci, pub rtc: rtc::Rtc, + pub pci: pci::Pci, + pub iommu: Option, } impl Devices { + pub fn new(device_tree: Fdt) -> Self { + Devices { + uart: uart::Uart::new(&device_tree, "/soc/serial"), + virtio_list: virtio::VirtIoList::new(&device_tree, "/soc/virtio_mmio"), + initrd: initrd::Initrd::new(&device_tree, "/chosen"), + plic: plic::Plic::new(&device_tree, "/soc/plic"), + plic_context: device_tree + .find_node("/cpus/cpu") + .unwrap() + .children() + .next() // interrupt-controller + .unwrap() + .property("phandle") + .unwrap() + .value[0] as usize, + clint: clint::Clint::new(&device_tree, "/soc/clint"), + rtc: rtc::Rtc::new(&device_tree, "/soc/rtc"), + pci: pci::Pci::new(&device_tree, "/soc/pci"), + iommu: iommu::IoMmu::new(&device_tree, "/soc/pci/iommu"), + } + } + + /// Initialization of IOMMU. + pub fn init_iommu(&mut self) { + if let Some(iommu) = &self.iommu { + iommu.init(&self.pci); + } + } + + /// Identity map for devices. pub fn device_mapping_g_stage(&self, page_table_start: HostPhysicalAddress) { let memory_map = self.create_device_map(); page_table::sv39x4::generate_page_table(page_table_start, &memory_map); } + /// Return devices range to crate identity map. + /// It does not return `Plic` address to emulate it. fn create_device_map(&self) -> Vec { let mut device_mapping: Vec = self - .virtio + .virtio_list .iter() - .flat_map(|virt| [virt.memmap(), virt.memmap()]) + .flat_map(|virt| [virt.memmap()]) .collect(); device_mapping.extend_from_slice(&[ @@ -76,35 +134,8 @@ impl Devices { self.rtc.memmap(), ]); - device_mapping - } -} + device_mapping.extend_from_slice(self.pci.pci_memory_maps()); -impl HypervisorData { - /// Set device data. - /// - /// It replace None (uninit value) to `Some(init_device)`. - /// - /// # Panics - /// It will be panic when parsing device tree failed. - pub fn register_devices(&mut self, device_tree: Fdt) { - self.devices.replace(Devices { - uart: uart::Uart::new(&device_tree, "/soc/serial"), - virtio: virtio::VirtIO::new_all(&device_tree, "/soc/virtio_mmio"), - initrd: initrd::Initrd::new(&device_tree, "/chosen"), - plic: plic::Plic::new(&device_tree, "/soc/plic"), - plic_context: device_tree - .find_node("/cpus/cpu") - .unwrap() - .children() - .next() // interrupt-controller - .unwrap() - .property("phandle") - .unwrap() - .value[0] as usize, - clint: clint::Clint::new(&device_tree, "/soc/clint"), - pci: pci::Pci::new(&device_tree, "/soc/pci"), - rtc: rtc::Rtc::new(&device_tree, "/soc/rtc"), - }); + device_mapping } } diff --git a/src/device/clint.rs b/src/device/clint.rs index fdd3522..bfc92f2 100644 --- a/src/device/clint.rs +++ b/src/device/clint.rs @@ -1,6 +1,6 @@ //! CLINT: Core Local `INTerrupt` -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{constant, GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use fdt::Fdt; use rustsbi::{HartMask, SbiRet}; @@ -28,7 +28,7 @@ pub struct Clint { size: usize, } -impl Device for Clint { +impl MmioDevice for Clint { fn new(device_tree: &Fdt, node_path: &str) -> Self { let region = device_tree .find_node(node_path) diff --git a/src/device/initrd.rs b/src/device/initrd.rs index 98ff32f..b2199d5 100644 --- a/src/device/initrd.rs +++ b/src/device/initrd.rs @@ -1,7 +1,7 @@ //! initrd: INITial RamDisk #![allow(clippy::doc_markdown)] -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use fdt::Fdt; @@ -13,15 +13,18 @@ pub struct Initrd { size: usize, } -impl Device for Initrd { +impl MmioDevice for Initrd { fn new(device_tree: &Fdt, node_path: &str) -> Self { let start_prop = "linux,initrd-start"; let end_prop = "linux,initrd-end"; let node = device_tree.find_node(node_path).unwrap(); + + // linux,initrd-start = <0x00 0xa0000000> -> [0, 0, 0, 0, 160, 0, 0, 0] + // `start[4..]` means skipping first four bytes. let start = node.property(start_prop).unwrap().value; - let start = u32::from_be_bytes(start.try_into().unwrap()) as usize; + let start = u32::from_be_bytes(start[4..].try_into().unwrap()) as usize; let end = node.property(end_prop).unwrap().value; - let end = u32::from_be_bytes(end.try_into().unwrap()) as usize; + let end = u32::from_be_bytes(end[4..].try_into().unwrap()) as usize; Initrd { base_addr: HostPhysicalAddress(start), diff --git a/src/device/iommu.rs b/src/device/iommu.rs new file mode 100644 index 0000000..52c381f --- /dev/null +++ b/src/device/iommu.rs @@ -0,0 +1,168 @@ +//! IOMMU: I/O memory management unit. +//! Ref: [https://github.com/riscv-non-isa/riscv-iommu/releases/download/v1.0.0/riscv-iommu.pdf](https://github.com/riscv-non-isa/riscv-iommu/releases/download/v1.0.0/riscv-iommu.pdf) + +mod register_map; + +use super::{ + pci::{ConfigSpaceRegister, Pci}, + PciDevice, +}; +use crate::h_extension::csrs::hgatp; +use crate::memmap::{page_table::constants::PAGE_SIZE, HostPhysicalAddress}; +use crate::PageBlock; +use register_map::{IoMmuMode, IoMmuRegisters}; + +use fdt::Fdt; + +/// IOMMU: I/O memory management unit. +#[derive(Debug)] +pub struct IoMmu { + bus: u32, + device: u32, + function: u32, +} + +impl IoMmu { + /// Set page table in IOMMU. + fn init_page_table(ddt_addr: HostPhysicalAddress) { + const OFFSET_IOHGATP: usize = 8; + const LEAF_DDT_ENTRY_SIZE: usize = 512; + // set all ddt entry + for offset in (0..PAGE_SIZE).step_by(LEAF_DDT_ENTRY_SIZE) { + let tc_addr = ddt_addr + offset; + let iohgatp_addr = ddt_addr + offset + OFFSET_IOHGATP; + + unsafe { + core::ptr::write_volatile(tc_addr.0 as *mut u64, 1); + core::ptr::write_volatile(iohgatp_addr.0 as *mut u64, hgatp::read().bits as u64); + } + } + } +} + +impl PciDevice for IoMmu { + fn new(device_tree: &Fdt, node_path: &str) -> Option { + let pci_reg = device_tree + .find_node(node_path)? + .raw_reg() + .unwrap() + .next() + .unwrap(); + assert_eq!(pci_reg.address.len(), 12); // 4 bytes * 3 + let pci_first_reg = u32::from(pci_reg.address[0]) << 24 + | u32::from(pci_reg.address[1]) << 16 + | u32::from(pci_reg.address[2]) << 8 + | u32::from(pci_reg.address[3]); + + Some(IoMmu { + bus: pci_first_reg >> 16 & 0b1111_1111, // 8 bit + device: pci_first_reg >> 11 & 0b1_1111, // 5 bit + function: pci_first_reg >> 8 & 0b111, // 3 bit + }) + } + + fn init(&self, pci: &Pci) { + let iommu_reg_addr: u32 = u32::try_from(pci.pci_memory_maps()[0].phys.start.0).unwrap(); + pci.write_config_register( + self.bus, + self.device, + self.function, + ConfigSpaceRegister::BaseAddressRegister1, + iommu_reg_addr, + ); + pci.write_config_register( + self.bus, + self.device, + self.function, + ConfigSpaceRegister::BaseAddressRegister2, + 0x0000_0000, + ); + pci.write_config_register( + self.bus, + self.device, + self.function, + ConfigSpaceRegister::Command, + 0b10, // memory space enable + ); + let registers = iommu_reg_addr as *mut IoMmuRegisters; + let registers = unsafe { &mut *registers }; + + // 6.2. Guidelines for initialization + // p.88 + + // 1. Read the capabilities register to discover the capabilities of the IOMMU. + // 2. Stop and report failure if capabilities.version is not supported. + let (major, _minor) = registers.capabilities.version(); + assert!(major >= 1); + assert!(registers.capabilities.is_sv39x4_supported()); + assert!(!registers.capabilities.is_base_format()); + + // 3. Read the feature control register (fctl). + // 3~8. are omitted. (does not needed for this system). + // 9. The icvec register is used to program an interrupt vector for each interrupt cause. + // 9~11. are omitted. (does not needed for this system). + + // 12. To program the command queue, first determine the number of entries N needed in the command queue. + // The number of entries in the command queue must be a power of two. + // Allocate a N x 16-bytes sized memory buffer that is naturally aligned to the greater of 4-KiB or N x 16-bytes. + // Let k=log2(N) and B be the physical page number (PPN) of the allocated memory buffer. + // CQB.PPN = B, CQB.LOG2SZ-1 = k - 1 + let command_queue = PageBlock::alloc(); + let command_queue_ptr = command_queue.0 as *mut [u8; 0x1000]; + unsafe { + core::ptr::write_bytes(command_queue_ptr, 0u8, 1); + } + registers.cqb.set(command_queue, 4096); + // cqt = 0 + registers.cqt.write(0); + // cqcsr.cqen = 1 + registers.cqcsr.set_cqen(); + // Poll on cqcsr.cqon until it reads 1 + while !registers.cqcsr.cqon() {} + + // 13. To program the fault queue, first determine the number of entries N needed in the fault queue. + // The number of entries in the fault queue is always a power of two. + // Allocate a N x 32-bytes sized memory buffer that is naturally aligned to the greater of 4-KiB or N x 32-bytes. + // Let k=log2(N) and B be the PPN of the allocated memory buffer. + // FQB.PPN = B, FQB.LOG2SZ-1 = k - 1 + let fault_queue = PageBlock::alloc(); + let fault_queue_ptr = fault_queue.0 as *mut [u8; 0x1000]; + unsafe { + core::ptr::write_bytes(fault_queue_ptr, 0u8, 1); + } + registers.fqb.set(fault_queue, 4096); + // fqt = 0 + registers.fqt.write(0); + // fqcsr.fqen = 1 + registers.fqcsr.set_fqen(); + // Poll on fqcsr.fqon until it reads 1 + while !registers.fqcsr.fqon() {} + + // 14. To program the page-request queue, first determine the number of entries N needed in the page-request queue. + // The number of entries in the page-request queue is always a power of two. + // Allocate a N x 16-bytes sized buffer that is naturally aligned to the greater of 4-KiB or N x 16-bytes. + // Let k=log2(N) and B be the PPN of the allocated memory buffer. + // PQB.PPN = B, PQB.LOG2SZ-1 = k - 1 + let page_request_queue = PageBlock::alloc(); + let page_request_queue_ptr = page_request_queue.0 as *mut [u8; 0x1000]; + unsafe { + core::ptr::write_bytes(page_request_queue_ptr, 0u8, 1); + } + registers.pqb.set(page_request_queue, 4096); + // pqt = 0 + registers.pqt.write(0); + // pqcsr.pqen = 1 + registers.pqcsr.set_pqen(); + // Poll on pqcsr.pqon until it reads 1 + while !registers.pqcsr.pqon() {} + + // 15. To program the DDT pointer, first determine the supported device_id width Dw and the format of the device-context data structure. + let ddt_addr = PageBlock::alloc(); + let ddt_ptr = ddt_addr.0 as *mut [u8; 0x1000]; + unsafe { + core::ptr::write_bytes(ddt_ptr, 0u8, 1); + } + Self::init_page_table(ddt_addr); + registers.ddtp.set(IoMmuMode::Lv1, ddt_addr); + } +} diff --git a/src/device/iommu/register_map.rs b/src/device/iommu/register_map.rs new file mode 100644 index 0000000..ecd6dc5 --- /dev/null +++ b/src/device/iommu/register_map.rs @@ -0,0 +1,200 @@ +//! Register map for IOMMU. + +use crate::memmap::HostPhysicalAddress; + +/// IOMMU register map +#[repr(C)] +pub struct IoMmuRegisters { + /// A read-only register reporting features supported by the IOMMU. + pub capabilities: Capabilities, + /// Feature control register + _fctl: u32, + /// Designated For custom use + _custom: u32, + /// Device directory table pointer + pub ddtp: Ddtp, + + /// Command-queue base + pub cqb: Cqb, + /// Command-queue head + _cqh: u32, + /// Command-queue tail + pub cqt: Cqt, + + /// Fault-queue base + pub fqb: Fqb, + /// Fault-queue head + _fqh: u32, + /// Fault-queue tail + pub fqt: Fqt, + + /// Page-request-queue base + pub pqb: Pqb, + /// Page-request-queue head + _pqh: u32, + /// Page-request-queue tail + pub pqt: Pqt, + + /// Command-queue CSR + pub cqcsr: CqCsr, + /// Fault-queue CSR + pub fqcsr: FqCsr, + /// Page-request-queue CSR + pub pqcsr: PqCsr, +} + +/// IOMMU capabilities +pub struct Capabilities(u64); +impl Capabilities { + /// Return (major version, minor version) + pub fn version(&self) -> (u8, u8) { + let version_reg = self.0; + ((version_reg >> 4 & 0xf) as u8, (version_reg & 0xf) as u8) + } + + /// Return + pub fn is_base_format(&self) -> bool { + (self.0 >> 22) & 0x1 == 0 + } + + /// Is sv39x4 supported? + pub fn is_sv39x4_supported(&self) -> bool { + const FIELD_CAPABILITIES_SV39X4: usize = 17; + self.0 >> FIELD_CAPABILITIES_SV39X4 & 0x1 == 1 + } +} + +/// Command-queue base +pub struct Cqb(u64); +impl Cqb { + /// set ppn value and `log_2(size`). + pub fn set(&mut self, queue_addr: HostPhysicalAddress, size: usize) { + // Is queue address aligned 4KiB? + assert!(queue_addr % 4096 == 0); + + // CQB.PPN = B, CQB.LOG2SZ-1 = k - 1 + self.0 = (queue_addr.0 as u64 >> 12) << 10 | u64::from(size.ilog2() - 1); + } +} + +/// Command-queue tail +pub struct Cqt(u32); +impl Cqt { + pub fn write(&mut self, value: u32) { + self.0 = value; + } +} + +/// Command-queue CSR +pub struct CqCsr(u32); +impl CqCsr { + /// set cqen (offset: 0) bit + pub fn set_cqen(&mut self) { + self.0 |= 1; + } + + /// cqon (offset: 16) + pub fn cqon(&self) -> bool { + const FIELD_CQCSR_CQON: usize = 0x10; + let cqcsr = self.0; + (cqcsr >> FIELD_CQCSR_CQON) & 0x1 == 1 + } +} + +/// Fault-queue base +pub struct Fqb(u64); +impl Fqb { + /// set ppn value and `log_2(size`). + pub fn set(&mut self, queue_addr: HostPhysicalAddress, size: usize) { + // Is queue address aligned 4KiB? + assert!(queue_addr % 4096 == 0); + + // FQB.PPN = B, FQB.LOG2SZ-1 = k - 1 + self.0 = (queue_addr.0 as u64 >> 12) << 10 | u64::from(size.ilog2() - 1); + } +} + +/// Fault-queue tail +pub struct Fqt(u32); +impl Fqt { + pub fn write(&mut self, value: u32) { + self.0 = value; + } +} + +/// Fault-queue CSR +pub struct FqCsr(u32); +impl FqCsr { + /// set fqen (offset: 0) bit + pub fn set_fqen(&mut self) { + self.0 |= 1; + } + + /// fqon (offset: 16) + pub fn fqon(&self) -> bool { + const FIELD_FQCSR_FQON: usize = 0x10; + let fqcsr = self.0; + fqcsr >> FIELD_FQCSR_FQON & 0x1 == 1 + } +} + +/// Page-request-queue base +pub struct Pqb(u64); +impl Pqb { + /// set ppn value and `log_2(size`). + pub fn set(&mut self, queue_addr: HostPhysicalAddress, size: usize) { + // Is queue address aligned 4KiB? + assert!(queue_addr % 4096 == 0); + + // PQB.PPN = B, PQB.LOG2SZ-1 = k - 1 + self.0 = (queue_addr.0 as u64 >> 12) << 10 | u64::from(size.ilog2() - 1); + } +} + +/// Page-request-queue tail +pub struct Pqt(u32); +impl Pqt { + pub fn write(&mut self, value: u32) { + self.0 = value; + } +} + +/// Page-request-queue CSR +pub struct PqCsr(u32); +impl PqCsr { + /// set pqen (offset: 0) bit + pub fn set_pqen(&mut self) { + self.0 |= 1; + } + + /// pqon (offset: 16) + pub fn pqon(&self) -> bool { + const FIELD_PQCSR_PQON: usize = 0x10; + let pqcsr = self.0; + pqcsr >> FIELD_PQCSR_PQON & 0x1 == 1 + } +} + +/// For `ddtp.iommu_mode`. +#[allow(dead_code)] +pub enum IoMmuMode { + /// No inbound memory transactions are allowed by the IOMMU. + Off, + /// No translation or protection. All inbound memory accesses are passed through. + Bare, + /// One-level device-directory-table + Lv1, + /// Two-level device-directory-table + Lv2, + /// Three-level device-directory-table + Lv3, +} + +/// Device-directory-table pointer +pub struct Ddtp(u64); +impl Ddtp { + pub fn set(&mut self, mode: IoMmuMode, ddt_addr: HostPhysicalAddress) { + const FIELD_DDTP_PPN: usize = 10; + self.0 = (ddt_addr.0 as u64 >> 12) << FIELD_DDTP_PPN | mode as u64; + } +} diff --git a/src/device/pci.rs b/src/device/pci.rs index ae1ced8..d63e022 100644 --- a/src/device/pci.rs +++ b/src/device/pci.rs @@ -1,19 +1,114 @@ //! PCI: Peripheral Component Interconnect -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use fdt::Fdt; +use alloc::vec::Vec; + +/// Registers in Common configuration Space Header. +/// +/// Ref: [https://astralvx.com/storage/2020/11/PCI_Express_Base_4.0_Rev0.3_February19-2014.pdf](https://astralvx.com/storage/2020/11/PCI_Express_Base_4.0_Rev0.3_February19-2014.pdf) p. 578 +/// Ref: [https://osdev.jp/wiki/PCI-Memo](https://osdev.jp/wiki/PCI-Memo) +/// Ref: [http://oswiki.osask.jp/?PCI](http://oswiki.osask.jp/?PCI) +#[derive(Clone, Copy)] +pub enum ConfigSpaceRegister { + /// Vendor ID + VendorId = 0x0, + /// Device ID + DeviceId = 0x2, + /// Command + Command = 0x4, + /// Status + Status = 0x6, + /// Base Address Register 1 + BaseAddressRegister1 = 0x10, + /// Base Address Register 2 + BaseAddressRegister2 = 0x14, +} + /// PCI: Peripheral Component Interconnect /// Local computer bus. #[derive(Debug)] pub struct Pci { base_addr: HostPhysicalAddress, size: usize, + /// Memory maps for pci devices + memory_maps: Vec, +} + +impl Pci { + /// Read config data from "PCI Configuration Space". + #[allow(clippy::cast_possible_truncation)] + pub fn read_config_register( + &self, + bus_num: u32, + device_num: u32, + function_num: u32, + reg: ConfigSpaceRegister, + ) -> u32 { + let config_data_reg_addr = self.base_addr.0 as u32 + | (bus_num & 0b1111_1111) << 20 + | (device_num & 0b1_1111) << 15 + | (function_num & 0b111) << 12 + | reg as u32; + + match reg { + ConfigSpaceRegister::VendorId + | ConfigSpaceRegister::DeviceId + | ConfigSpaceRegister::Command + | ConfigSpaceRegister::Status => unsafe { + u32::from(core::ptr::read_volatile(config_data_reg_addr as *const u16)) + }, + ConfigSpaceRegister::BaseAddressRegister1 + | ConfigSpaceRegister::BaseAddressRegister2 => unsafe { + core::ptr::read_volatile(config_data_reg_addr as *const u32) + }, + } + } + + /// Read config data from "PCI Configuration Space". + #[allow(clippy::cast_possible_truncation)] + pub fn write_config_register( + &self, + bus_num: u32, + device_num: u32, + function_num: u32, + reg: ConfigSpaceRegister, + data: u32, + ) { + let config_data_reg_addr = self.base_addr.0 as u32 + | (bus_num & 0b1111_1111) << 20 + | (device_num & 0b1_1111) << 15 + | (function_num & 0b111) << 12 + | reg as u32; + match reg { + ConfigSpaceRegister::VendorId + | ConfigSpaceRegister::DeviceId + | ConfigSpaceRegister::Command + | ConfigSpaceRegister::Status => unsafe { + core::ptr::write_volatile(config_data_reg_addr as *mut u16, data as u16); + }, + ConfigSpaceRegister::BaseAddressRegister1 + | ConfigSpaceRegister::BaseAddressRegister2 => unsafe { + core::ptr::write_volatile(config_data_reg_addr as *mut u32, data); + }, + } + } + + /// Return memory maps of Generic PCI host controller + /// + /// Ref: [https://www.kernel.org/doc/Documentation/devicetree/bindings/pci/host-generic-pci.txt](https://www.kernel.org/doc/Documentation/devicetree/bindings/pci/host-generic-pci.txt) + pub fn pci_memory_maps(&self) -> &[MemoryMap] { + &self.memory_maps + } } -impl Device for Pci { +impl MmioDevice for Pci { fn new(device_tree: &Fdt, node_path: &str) -> Self { + const BYTES_U32: usize = 4; + const RANGE_NUM: usize = 7; + let region = device_tree .find_node(node_path) .unwrap() @@ -21,10 +116,45 @@ impl Device for Pci { .unwrap() .next() .unwrap(); + let ranges = device_tree + .find_node(node_path) + .unwrap() + .property("ranges") + .unwrap() + .value; + + assert!(ranges.len() % 4 == 0); + assert!((ranges.len() / 4) % 7 == 0); + + let get_u32 = |range: &[u8], four_bytes_index: usize| { + let index = four_bytes_index * 4; + u32::from(range[index]) << 24 + | u32::from(range[index + 1]) << 16 + | u32::from(range[index + 2]) << 8 + | u32::from(range[index + 3]) + }; + let mut memory_maps = Vec::new(); + for range in ranges.chunks(RANGE_NUM * BYTES_U32) { + let bus_address = get_u32(range, 0); + + // ignore I/O space map + // https://elinux.org/Device_Tree_Usage#PCI_Address_Translation + if (bus_address >> 24) & 0b11 != 0b01 { + let address = (get_u32(range, 3) as usize) << 32 | get_u32(range, 4) as usize; + let size = (get_u32(range, 5) as usize) << 32 | get_u32(range, 6) as usize; + + memory_maps.push(MemoryMap::new( + GuestPhysicalAddress(address)..GuestPhysicalAddress(address) + size, + HostPhysicalAddress(address)..HostPhysicalAddress(address) + size, + &PTE_FLAGS_FOR_DEVICE, + )); + } + } Pci { base_addr: HostPhysicalAddress(region.starting_address as usize), size: region.size.unwrap(), + memory_maps, } } diff --git a/src/device/plic.rs b/src/device/plic.rs index bb68a97..63fb325 100644 --- a/src/device/plic.rs +++ b/src/device/plic.rs @@ -1,16 +1,36 @@ //! PLIC: Platform-Level Interrupt Controller //! ref: [https://github.com/riscv/riscv-plic-spec/releases/download/1.0.0/riscv-plic-1.0.0.pdf](https://github.com/riscv/riscv-plic-spec/releases/download/1.0.0/riscv-plic-1.0.0.pdf) -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{DeviceEmulateError, MmioDevice, PTE_FLAGS_FOR_DEVICE}; +use crate::h_extension::csrs::{hvip, VsInterruptKind}; +use crate::memmap::constant::MAX_HART_NUM; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use fdt::Fdt; -// unused constant for now -// pub const ENABLE_BASE: usize = 0x2000; -// pub const ENABLE_PER_HART: usize = 0x80; -// pub const CONTEXT_BASE: usize = 0x20_0000; -// pub const CONTEXT_PER_HART: usize = 0x1000; -// pub const CONTEXT_CLAIM: usize = 0x4; +/// Max number of PLIC context. +pub const MAX_CONTEXT_NUM: usize = MAX_HART_NUM * 2; + +/// Base offset of context. +const CONTEXT_BASE: usize = 0x20_0000; +/// Context registers region size. +const CONTEXT_REGS_SIZE: usize = 0x1000; +/// Claim/complete register offset from `CONTEXT_BASE` + `CONTEXT_REGS_SIZE` * `CONTEXT_REGS_SIZE`. +const CONTEXT_CLAIM: usize = 0x4; +/// End of context registers region. +const CONTEXT_END: usize = CONTEXT_BASE * CONTEXT_REGS_SIZE * MAX_CONTEXT_NUM; + +/// PLIC context ID. +pub struct ContextId(usize); + +impl ContextId { + pub fn new(hart_id: usize, is_supervisor: bool) -> Self { + ContextId(2 * hart_id + usize::from(is_supervisor)) + } + + pub fn raw(&self) -> usize { + self.0 + } +} /// PLIC: Platform-Level Interrupt Controller /// Interrupt controller for global interrupts. @@ -18,9 +38,88 @@ use fdt::Fdt; pub struct Plic { base_addr: HostPhysicalAddress, size: usize, + claim_complete: [u32; MAX_CONTEXT_NUM], +} + +impl Plic { + /// Read plic claim/update register and reflect to `claim_complete`. + pub fn update_claim_complete(&mut self, context_id: &ContextId) { + let claim_complete_addr = + self.base_addr + CONTEXT_BASE + CONTEXT_REGS_SIZE * context_id.raw() + CONTEXT_CLAIM; + let irq = unsafe { core::ptr::read_volatile(claim_complete_addr.raw() as *const u32) }; + self.claim_complete[context_id.raw()] = irq; + } + + /// Emulate reading plic context register + fn context_read(&self, offset: usize) -> Result { + let context_id = (offset - CONTEXT_BASE) / CONTEXT_REGS_SIZE; + if context_id > MAX_CONTEXT_NUM { + Err(DeviceEmulateError::InvalidContextId) + } else { + Ok(self.claim_complete[context_id]) + } + } + + /// Emulate reading plic register. + pub fn emulate_read(&self, dst_addr: HostPhysicalAddress) -> Result { + let offset = dst_addr.raw() - self.base_addr.raw(); + match offset { + CONTEXT_BASE..=CONTEXT_END => self.context_read(offset), + _ => Err(DeviceEmulateError::InvalidAddress), + } + } + + /// Emulate writing plic context register. + fn context_write( + &mut self, + dst_addr: HostPhysicalAddress, + value: u32, + ) -> Result<(), DeviceEmulateError> { + let offset = dst_addr.raw() - self.base_addr.raw(); + let context_id = (offset - CONTEXT_BASE) / CONTEXT_REGS_SIZE; + let offset_per_context = offset % CONTEXT_REGS_SIZE; + match offset_per_context { + // threshold + 0 => { + let dst_ptr = dst_addr.raw() as *mut u32; + unsafe { + dst_ptr.write_volatile(value); + } + + Ok(()) + } + // claim/complete + 4 => { + let dst_ptr = dst_addr.raw() as *mut u32; + unsafe { + dst_ptr.write_volatile(value); + } + self.claim_complete[context_id] = 0; + hvip::clear(VsInterruptKind::External); + + Ok(()) + } + 8 => Err(DeviceEmulateError::ReservedRegister), + _ => Err(DeviceEmulateError::InvalidAddress), + } + } + + /// Emulate writing plic register. + pub fn emulate_write( + &mut self, + dst_addr: HostPhysicalAddress, + value: u32, + ) -> Result<(), DeviceEmulateError> { + let offset = dst_addr.raw() - self.base_addr.raw(); + match offset { + CONTEXT_BASE..=CONTEXT_END => self.context_write(dst_addr, value), + _ => Err(DeviceEmulateError::InvalidAddress), + } + } } -impl Device for Plic { +impl MmioDevice for Plic { + #[allow(clippy::cast_ptr_alignment)] fn new(device_tree: &Fdt, node_path: &str) -> Self { let region = device_tree .find_node(node_path) @@ -33,6 +132,7 @@ impl Device for Plic { Plic { base_addr: HostPhysicalAddress(region.starting_address as usize), size: region.size.unwrap(), + claim_complete: [0u32; MAX_CONTEXT_NUM], } } @@ -45,10 +145,12 @@ impl Device for Plic { } fn memmap(&self) -> MemoryMap { + // Pass through 0x0 - 0x20_0000. + // Disallow 0x20_0000 - for emulation. let vaddr = GuestPhysicalAddress(self.paddr().raw()); MemoryMap::new( - vaddr..vaddr + self.size(), - self.paddr()..self.paddr() + self.size(), + vaddr..vaddr + CONTEXT_BASE, + self.paddr()..self.paddr() + CONTEXT_BASE, &PTE_FLAGS_FOR_DEVICE, ) } diff --git a/src/device/rtc.rs b/src/device/rtc.rs index 2677b75..9da908a 100644 --- a/src/device/rtc.rs +++ b/src/device/rtc.rs @@ -1,6 +1,6 @@ //! RTC: Real Time Clock. -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use fdt::Fdt; @@ -12,7 +12,7 @@ pub struct Rtc { size: usize, } -impl Device for Rtc { +impl MmioDevice for Rtc { fn new(device_tree: &Fdt, node_path: &str) -> Self { let region = device_tree .find_node(node_path) diff --git a/src/device/uart.rs b/src/device/uart.rs index 314c8e0..4aac560 100644 --- a/src/device/uart.rs +++ b/src/device/uart.rs @@ -1,6 +1,6 @@ //! UART: Universal Asynchronous Receiver-Transmitter -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use core::cell::OnceCell; @@ -69,7 +69,7 @@ impl Uart { } } -impl Device for Uart { +impl MmioDevice for Uart { fn new(device_tree: &Fdt, node_path: &str) -> Self { let region = device_tree .find_node(node_path) diff --git a/src/device/virtio.rs b/src/device/virtio.rs index 87af280..0d76de3 100644 --- a/src/device/virtio.rs +++ b/src/device/virtio.rs @@ -1,48 +1,61 @@ //! A virtualization standard for network and disk device drivers. -use super::{Device, PTE_FLAGS_FOR_DEVICE}; +use super::{MmioDevice, PTE_FLAGS_FOR_DEVICE}; use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; use alloc::vec::Vec; +use core::slice::Iter; use fdt::Fdt; /// A virtualization standard for network and disk device drivers. /// Since more than one may be found, we will temporarily use the first one. #[derive(Debug)] -pub struct VirtIO { +pub struct VirtIoList(Vec); + +impl VirtIoList { + /// Create each Virt IO data when device has multiple IOs. + pub fn new(device_tree: &Fdt, node_path: &str) -> Self { + VirtIoList( + device_tree + .find_all_nodes(node_path) + .map(|node| { + let region = node.reg().unwrap().next().unwrap(); + let irq = node.property("interrupts").unwrap().value[0]; + VirtIo { + base_addr: HostPhysicalAddress(region.starting_address as usize), + size: region.size.unwrap(), + irq, + } + }) + .collect(), + ) + } + + /// Return Virt IO list iterator + pub fn iter(&self) -> Iter<'_, VirtIo> { + self.0.iter() + } +} + +#[derive(Debug)] +pub struct VirtIo { base_addr: HostPhysicalAddress, size: usize, irq: u8, } -impl VirtIO { - /// Create each Virt IO data when device has multiple IOs. - pub fn new_all(device_tree: &Fdt, node_path: &str) -> Vec { - device_tree - .find_all_nodes(node_path) - .map(|node| { - let region = node.reg().unwrap().next().unwrap(); - let irq = node.property("interrupts").unwrap().value[0]; - VirtIO { - base_addr: HostPhysicalAddress(region.starting_address as usize), - size: region.size.unwrap(), - irq, - } - }) - .collect() - } - +impl VirtIo { pub fn irq(&self) -> u8 { self.irq } } -impl Device for VirtIO { +impl MmioDevice for VirtIo { fn new(device_tree: &Fdt, node_path: &str) -> Self { let node = device_tree.find_all_nodes(node_path).next().unwrap(); let region = node.reg().unwrap().next().unwrap(); let irq = node.property("interrupts").unwrap().value[0]; - VirtIO { + VirtIo { base_addr: HostPhysicalAddress(region.starting_address as usize), size: region.size.unwrap(), irq, diff --git a/src/guest.rs b/src/guest.rs index f528eda..f49a7dd 100644 --- a/src/guest.rs +++ b/src/guest.rs @@ -9,31 +9,12 @@ use crate::memmap::{ page_table::{constants::PAGE_SIZE, PageTableEntry, PteFlag}, GuestPhysicalAddress, HostPhysicalAddress, MemoryMap, }; +use crate::PageBlock; use context::{Context, ContextData}; -use alloc::boxed::Box; -use alloc::vec::Vec; use core::ops::Range; use elf::{endian::AnyEndian, ElfBytes}; -/// Aligned page size memory block -#[repr(C, align(0x1000))] -struct PageBlock([u8; 0x1000]); - -impl PageBlock { - /// Return aligned address of page size memory block. - fn alloc() -> HostPhysicalAddress { - let mut host_physical_block_as_vec: Vec> = - Vec::with_capacity(1); - unsafe { - host_physical_block_as_vec.set_len(1); - } - - let host_physical_block_slice = host_physical_block_as_vec.into_boxed_slice(); - HostPhysicalAddress(Box::into_raw(host_physical_block_slice) as *const u8 as usize) - } -} - /// Guest Information #[derive(Debug)] pub struct Guest { @@ -206,11 +187,12 @@ impl Guest { match prog_header.p_flags & 0b111 { 0b100 => &[Dirty, Accessed, Read, User, Valid], #[allow(clippy::match_same_arms)] - // for dynamic patch + // Add Write permission to RX for dynamic patch // ref: https://github.com/torvalds/linux/blob/67784a74e258a467225f0e68335df77acd67b7ab/arch/riscv/kernel/patch.c#L215C5-L215C21 // TODO: switch enable/disable write permission corresponding to VS-stage page table. - 0b101 => &[Dirty, Accessed, Exec, Write, Read, User, Valid], - 0b110 => &[Dirty, Accessed, Write, Read, User, Valid], + 0b101 => &[Dirty, Accessed, Read, Write, Exec, User, Valid], + // FIXME: Add Exec permission (RW -> RWX) + 0b110 => &[Dirty, Accessed, Read, Write, Exec, User, Valid], 0b111 => &[Dirty, Accessed, Exec, Write, Read, User, Valid], _ => panic!("unsupported flags"), }, diff --git a/src/h_extension.rs b/src/h_extension.rs index a7ce24e..f0080de 100644 --- a/src/h_extension.rs +++ b/src/h_extension.rs @@ -2,3 +2,30 @@ pub mod csrs; pub mod instruction; + +/// Exception type in H extension. +pub enum HvException { + /// Environment call from VS-mode + EcallFromVsMode = 10, + /// Instruction guest-page fault + InstructionGuestPageFault = 20, + /// Load guest-page fault + LoadGuestPageFault = 21, + /// Virtual instruction + VirtualInstruction = 22, + /// Store/AMO guest-page fault + StoreAmoGuestPageFault = 23, +} + +impl From for HvException { + fn from(exception_num: usize) -> Self { + match exception_num { + 10 => HvException::EcallFromVsMode, + 20 => HvException::InstructionGuestPageFault, + 21 => HvException::LoadGuestPageFault, + 22 => HvException::VirtualInstruction, + 23 => HvException::StoreAmoGuestPageFault, + _ => panic!("unsupported exception number: {exception_num}"), + } + } +} diff --git a/src/h_extension/csrs.rs b/src/h_extension/csrs.rs index 0590219..9a0dcfb 100644 --- a/src/h_extension/csrs.rs +++ b/src/h_extension/csrs.rs @@ -90,11 +90,11 @@ macro_rules! clear_csr_from_enum { /// VS-level interrupt kind. pub enum VsInterruptKind { /// VS-level external interrupts (bit 10) - External = 0b100_0000_0000, + External = 1 << 10, /// VS-level timer interrupts (bit 6) - Timer = 0b100_0000, + Timer = 1 << 6, /// VS-level software interrupts (bit 2) - Software = 0b100, + Software = 1 << 2, } pub mod vstvec { @@ -250,49 +250,6 @@ pub mod hcounteren { set_csr_as!(0x606); } -pub mod hvip { - //! Hypervisor virtual interrupt pending. - #![allow(dead_code)] - use super::VsInterruptKind; - - const HVIP: usize = 0x645; - pub struct Hvip { - bits: usize, - } - - set_csr_from_enum!(VsInterruptKind, 0x645); - clear_csr_from_enum!(VsInterruptKind, 0x645); - - read_csr_as!(Hvip, 0x645); - write_csr_as!(0x645); -} - -pub mod hgatp { - //! Hypervisor guest address translation and protection. - #![allow(dead_code)] - - const HGATP: usize = 0x680; - pub struct Hgatp { - bits: usize, - } - - /// Translation mode in G-stage. - #[allow(clippy::module_name_repetitions)] - pub enum HgatpMode { - Bare = 0, - Sv39x4 = 8, - Sv48x4 = 9, - Sv57x4 = 10, - } - - pub fn set(mode: HgatpMode, vmid: usize, ppn: usize) { - write((0xF & (mode as usize)) << 60 | (0x3FFF & vmid) << 44 | 0x0FFF_FFFF_FFFF & ppn); - } - - read_csr_as!(Hgatp, 0x680); - write_csr_as!(0x680); -} - pub mod henvcfg { //! Hypervisor environment configuration register. #![allow(dead_code)] @@ -338,3 +295,89 @@ pub mod henvcfg { } } } + +pub mod htval { + //! Hypervisor bad guest physical address. + #![allow(dead_code)] + + const HTVAL: usize = 0x643; + pub struct Htval { + pub bits: usize, + } + + read_csr_as!(Htval, 0x643); +} + +pub mod hvip { + //! Hypervisor virtual interrupt pending. + #![allow(dead_code)] + use super::VsInterruptKind; + + const HVIP: usize = 0x645; + pub struct Hvip { + bits: usize, + } + + set_csr_from_enum!(VsInterruptKind, 0x645); + clear_csr_from_enum!(VsInterruptKind, 0x645); + + read_csr_as!(Hvip, 0x645); + write_csr_as!(0x645); +} + +pub mod htinst { + //! Hypervisor trap instruction (transformed). + #![allow(dead_code)] + + const HTINST: usize = 0x64a; + pub struct Htinst { + pub bits: usize, + } + + read_csr_as!(Htinst, 0x64a); + write_csr_as!(0x64a); +} + +pub mod hgatp { + //! Hypervisor guest address translation and protection. + #![allow(dead_code)] + + const HGATP: usize = 0x680; + pub struct Hgatp { + pub bits: usize, + } + + impl Hgatp { + /// Return ppn. + pub fn ppn(&self) -> usize { + self.bits & 0xfff_ffff_ffff // 44 bit + } + + /// Return translation mode. + pub fn mode(&self) -> HgatpMode { + match (self.bits >> 60) & 0b1111 { + 0 => HgatpMode::Bare, + 8 => HgatpMode::Sv39x4, + 9 => HgatpMode::Sv48x4, + 10 => HgatpMode::Sv57x4, + _ => unreachable!(), + } + } + } + + /// Translation mode in G-stage. + #[allow(clippy::module_name_repetitions)] + pub enum HgatpMode { + Bare = 0, + Sv39x4 = 8, + Sv48x4 = 9, + Sv57x4 = 10, + } + + pub fn set(mode: HgatpMode, vmid: usize, ppn: usize) { + write((0xF & (mode as usize)) << 60 | (0x3FFF & vmid) << 44 | 0x0FFF_FFFF_FFFF & ppn); + } + + read_csr_as!(Hgatp, 0x680); + write_csr_as!(0x680); +} diff --git a/src/hypervisor_init.rs b/src/hypervisor_init.rs index 93d758e..bc6776b 100644 --- a/src/hypervisor_init.rs +++ b/src/hypervisor_init.rs @@ -1,6 +1,6 @@ //! HS-mode level initialization. -use crate::device::Device; +use crate::device::MmioDevice; use crate::guest::Guest; use crate::h_extension::csrs::{ hcounteren, hedeleg, hedeleg::ExceptionKind, henvcfg, hgatp, hgatp::HgatpMode, hideleg, hie, @@ -12,12 +12,12 @@ use crate::memmap::{ HostPhysicalAddress, }; use crate::trap::hypervisor_supervisor::hstrap_vector; -use crate::{GUEST_DTB, HYPERVISOR_DATA}; +use crate::{HypervisorData, GUEST_DTB, HYPERVISOR_DATA}; use core::arch::asm; use elf::{endian::AnyEndian, ElfBytes}; -use riscv::register::{sepc, sscratch, sstatus, stvec}; +use riscv::register::{sepc, sie, sscratch, sstatus, sstatus::FS, stvec}; /// Entry point to HS-mode. #[inline(never)] @@ -28,7 +28,7 @@ pub extern "C" fn hstart(hart_id: usize, dtb_addr: usize) -> ! { // dtb_addr test and hint for register usage. assert_ne!(dtb_addr, 0); - // clear all hypervisor interrupts. + // clear all hs-mode to vs-mode interrupts. hvip::clear(VsInterruptKind::External); hvip::clear(VsInterruptKind::Timer); hvip::clear(VsInterruptKind::Software); @@ -36,7 +36,17 @@ pub extern "C" fn hstart(hart_id: usize, dtb_addr: usize) -> ! { // disable address translation. vsatp::write(0); - // disable hypervisor external interrupt + // enable all hs-mode interrupts + unsafe { + sie::set_sext(); + sie::set_ssoft(); + sie::set_stimer(); + } + + // set hie = 0x444 + hie::set(VsInterruptKind::External); + hie::set(VsInterruptKind::Timer); + hie::set(VsInterruptKind::Software); // enable Sstc extention henvcfg::set_stce(); @@ -46,12 +56,6 @@ pub extern "C" fn hstart(hart_id: usize, dtb_addr: usize) -> ! { // enable hypervisor counter hcounteren::set(0xffff_ffff); - // set hie = 0x444 - // TODO?: trap VS-mode interrupt. - hie::set(VsInterruptKind::External); - hie::set(VsInterruptKind::Timer); - hie::set(VsInterruptKind::Software); - // specify delegation exception kinds. hedeleg::write( ExceptionKind::InstructionAddressMissaligned as usize @@ -76,9 +80,6 @@ pub extern "C" fn hstart(hart_id: usize, dtb_addr: usize) -> ! { /// * Parse DTB /// * Setup page table fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { - // aquire hypervisor data - let mut hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; - // create new guest data let guest_id = hart_id + 1; let guest_memory_begin = guest_memory::DRAM_BASE + guest_id * guest_memory::DRAM_SIZE_PER_GUEST; @@ -97,23 +98,24 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { Err(e) => panic!("{}", e), } }; - // parsing and storing device data - hypervisor_data.register_devices(device_tree); + + // initialize hypervisor data + let mut hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; + hypervisor_data.get_or_init(|| HypervisorData::new(device_tree)); // load guest elf from address + let initrd = &hypervisor_data.get_mut().unwrap().devices().initrd; let guest_elf = unsafe { ElfBytes::::minimal_parse(core::slice::from_raw_parts( - hypervisor_data.devices().initrd.paddr().raw() as *mut u8, - hypervisor_data.devices().initrd.size(), + initrd.paddr().raw() as *mut u8, + initrd.size(), )) .unwrap() }; // load guest image - let (guest_entry_point, elf_end_addr) = new_guest.load_guest_elf( - &guest_elf, - hypervisor_data.devices().initrd.paddr().raw() as *mut u8, - ); + let (guest_entry_point, elf_end_addr) = + new_guest.load_guest_elf(&guest_elf, initrd.paddr().raw() as *mut u8); // filling remain memory region new_guest.filling_memory_region( @@ -122,6 +124,8 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { // set device memory map hypervisor_data + .get_mut() + .unwrap() .devices() .device_mapping_g_stage(root_page_table_addr); @@ -129,13 +133,20 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { hgatp::set(HgatpMode::Sv39x4, 0, root_page_table_addr.raw() >> 12); hfence_gvma_all(); + // initialize IOMMU + hypervisor_data.get_mut().unwrap().devices().init_iommu(); + // set new guest data - hypervisor_data.register_guest(new_guest); + hypervisor_data.get_mut().unwrap().register_guest(new_guest); unsafe { // sstatus.SUM = 1, sstatus.SPP = 0 sstatus::set_sum(); sstatus::set_spp(sstatus::SPP::Supervisor); + // sstatus.sie = 1 + sstatus::set_sie(); + // sstatus.fs = 1 + sstatus::set_fs(FS::Initial); // hstatus.spv = 1 (enable V bit when sret executed) hstatus::set_spv(); @@ -150,7 +161,7 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { stvec::TrapMode::Direct, ); - let mut context = hypervisor_data.guest().context; + let mut context = hypervisor_data.get().unwrap().guest().context; context.set_sepc(sepc::read()); // set sstatus value to context @@ -159,7 +170,7 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { context.set_sstatus(sstatus_val); } - let guest_dtb_addr = hypervisor_data.guest().guest_dtb_addr(); + let guest_dtb_addr = hypervisor_data.get().unwrap().guest().guest_dtb_addr(); // release HYPERVISOR_DATA lock drop(hypervisor_data); @@ -171,8 +182,8 @@ fn vsmode_setup(hart_id: usize, dtb_addr: HostPhysicalAddress) -> ! { #[inline(never)] fn hart_entry(hart_id: usize, dtb_addr: GuestPhysicalAddress) -> ! { // aquire hypervisor data - let mut hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; - let stack_top = hypervisor_data.guest().stack_top(); + let hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; + let stack_top = hypervisor_data.get().unwrap().guest().stack_top(); // release HYPERVISOR_DATA lock drop(hypervisor_data); diff --git a/src/machine_init.rs b/src/machine_init.rs index 9cb1108..6b255a2 100644 --- a/src/machine_init.rs +++ b/src/machine_init.rs @@ -7,7 +7,8 @@ use crate::{sbi::Sbi, SBI}; use core::arch::asm; use riscv::asm::sfence_vma_all; use riscv::register::{ - mcounteren, medeleg, mepc, mideleg, mie, mscratch, mstatus, mtvec, pmpaddr0, pmpcfg0, satp, + mcounteren, medeleg, mepc, mideleg, mie, mscratch, mstatus, mtvec, pmpaddr0, pmpaddr1, + pmpaddr2, pmpcfg0, satp, Permission, Range, }; /// Machine start function @@ -31,7 +32,9 @@ pub fn mstart(hart_id: usize, dtb_addr: usize) -> ! { medeleg::set_load_page_fault(); medeleg::set_store_page_fault(); asm!("csrs medeleg, {vsmode_ecall}", vsmode_ecall = in(reg) 1 << 10, options(nomem)); // deleg env call from VS-mode - asm!("csrs medeleg, {virtual_instruction}", virtual_instruction = in(reg) 1 << 22, options(nomem)); // deleg env call from VS-mode + asm!("csrs medeleg, {load_guest_page_fault}", load_guest_page_fault = in(reg) 1 << 21, options(nomem)); // deleg load guest page fault + asm!("csrs medeleg, {virtual_instruction}", virtual_instruction = in(reg) 1 << 22, options(nomem)); // deleg virtual instruction + asm!("csrs medeleg, {store_amo_guest_page_fault}", store_amo_guest_page_fault = in(reg) 1 << 23, options(nomem)); // deleg store/amo guest page fault medeleg::clear_supervisor_env_call(); // mie = 0x088 @@ -71,12 +74,26 @@ pub fn mstart(hart_id: usize, dtb_addr: usize) -> ! { mcounteren::set_hpm(29); mcounteren::set_hpm(30); mcounteren::set_hpm(31); + + // switch to S-mode when mret executed. mstatus::set_mpp(mstatus::MPP::Supervisor); + + // set M-mode stack pointer mscratch::write( core::ptr::addr_of!(crate::_top_m_stack) as usize + STACK_SIZE_PER_HART * hart_id, ); - pmpaddr0::write(0xffff_ffff_ffff_ffff); - pmpcfg0::write(pmpcfg0::read().bits | 0x1f); + + // pmp settings + pmpcfg0::set_pmp(0, Range::OFF, Permission::NONE, false); + pmpaddr0::write(0); + // 0x0 - 0x8000_0000 = RW + pmpcfg0::set_pmp(1, Range::TOR, Permission::RW, false); + pmpaddr1::write(0x8000_0000 >> 2); + // 0x8000_0000 - 0xffff_ffff = RWX + pmpcfg0::set_pmp(2, Range::TOR, Permission::RWX, false); + pmpaddr2::write(0xffff_ffff_ffff_ffff); + + // no address translation satp::set(satp::Mode::Bare, 0, 0); // enable Sstc and Zicboz extention diff --git a/src/main.rs b/src/main.rs index b6f1de3..bbec49f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,28 +11,31 @@ mod memmap; mod sbi; mod trap; +use alloc::boxed::Box; +use alloc::vec::Vec; use core::arch::asm; use core::cell::OnceCell; use core::panic::PanicInfo; -use riscv_rt::entry; +use fdt::Fdt; use linked_list_allocator::LockedHeap; +use riscv_rt::entry; use spin::Mutex; +use crate::device::Devices; use crate::guest::Guest; use crate::machine_init::mstart; use crate::memmap::constant::{DRAM_BASE, MAX_HART_NUM, STACK_SIZE_PER_HART}; +use crate::memmap::HostPhysicalAddress; use crate::sbi::Sbi; #[global_allocator] -// static mut ALLOCATOR: WildScreenAlloc = WildScreenAlloc::empty(); /// Global allocator. static ALLOCATOR: LockedHeap = LockedHeap::empty(); +// static mut ALLOCATOR: WildScreenAlloc = WildScreenAlloc::empty(); /// Singleton for this hypervisor. -/// -/// TODO: change to `Mutex>`? -static mut HYPERVISOR_DATA: Mutex = Mutex::new(HypervisorData::const_default()); +static mut HYPERVISOR_DATA: Mutex> = Mutex::new(OnceCell::new()); /// Singleton for SBI handler. static SBI: Mutex> = Mutex::new(OnceCell::new()); @@ -61,39 +64,62 @@ pub fn panic(info: &PanicInfo) -> ! { } } +/// Aligned page size memory block +#[repr(C, align(0x1000))] +struct PageBlock([u8; 0x1000]); + +impl PageBlock { + /// Return aligned address of page size memory block. + fn alloc() -> HostPhysicalAddress { + let mut host_physical_block_as_vec: Vec> = + Vec::with_capacity(1); + unsafe { + host_physical_block_as_vec.set_len(1); + } + + let host_physical_block_slice = host_physical_block_as_vec.into_boxed_slice(); + HostPhysicalAddress(Box::into_raw(host_physical_block_slice) as *const u8 as usize) + } +} + /// Global data for hypervisor. /// /// FIXME: Rename me! #[derive(Debug)] pub struct HypervisorData { current_hart: usize, - guest: [Option; MAX_HART_NUM], - devices: Option, + guests: [Option; MAX_HART_NUM], + devices: device::Devices, } impl HypervisorData { - /// Const default function for global variable - const fn const_default() -> Self { + /// Initialize hypervisor. + /// + /// # Panics + /// It will be panic when parsing device tree failed. + #[must_use] + pub fn new(device_tree: Fdt) -> Self { const ARRAY_INIT_VALUE: Option = None; HypervisorData { current_hart: 0, - guest: [ARRAY_INIT_VALUE; MAX_HART_NUM], - devices: None, + guests: [ARRAY_INIT_VALUE; MAX_HART_NUM], + devices: Devices::new(device_tree), } } /// # Panics /// It will be panic if devices are uninitialized. #[must_use] - pub fn devices(&self) -> &device::Devices { - self.devices.as_ref().expect("device data is uninitialized") + pub fn devices(&mut self) -> &mut device::Devices { + &mut self.devices } /// # Panics /// It will be panic if current HART's guest data is empty. - pub fn guest(&mut self) -> &mut Guest { - self.guest[self.current_hart] - .as_mut() + #[must_use] + pub fn guest(&self) -> &Guest { + self.guests[self.current_hart] + .as_ref() .expect("guest data not found") } @@ -102,7 +128,7 @@ impl HypervisorData { pub fn register_guest(&mut self, new_guest: Guest) { let hart_id = new_guest.hart_id(); assert!(hart_id < MAX_HART_NUM); - self.guest[hart_id] = Some(new_guest); + self.guests[hart_id] = Some(new_guest); } } diff --git a/src/memmap.rs b/src/memmap.rs index abc6345..10d05c4 100644 --- a/src/memmap.rs +++ b/src/memmap.rs @@ -80,12 +80,12 @@ impl AddressRangeUtil for Range { } /// Struct for represent memory regtion. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct MemoryMap { /// Guest physical address virt: Range, /// Host physical address - phys: Range, + pub phys: Range, /// Page table entry flags flags: u8, } diff --git a/src/memmap/page_table.rs b/src/memmap/page_table.rs index 8723f87..f0648e1 100644 --- a/src/memmap/page_table.rs +++ b/src/memmap/page_table.rs @@ -34,7 +34,7 @@ enum PageTableLevel { } impl PageTableLevel { - pub fn size(self) -> usize { + fn size(self) -> usize { match self { Self::Lv1GB => 0x4000_0000, Self::Lv2MB => 0x0020_0000, @@ -75,12 +75,35 @@ impl PageTableEntry { Self(ppn << 10 | u64::from(flags)) } - fn already_created(self) -> bool { - self.0 & PteFlag::Valid as u64 == 1 + /// Is leaf page table entry + #[allow(dead_code)] + fn is_leaf(self) -> bool { + let pte_r = self.0 >> 1 & 0x1; + let pte_x = self.0 >> 3 & 0x1; + + pte_r == 1 || pte_x == 1 + } + + /// Return ppn + #[allow(clippy::cast_possible_truncation)] + #[allow(dead_code)] + fn ppn(self, index: usize) -> usize { + match index { + 2 => (self.0 as usize >> 28) & 0x3ff_ffff, // 26 bit + 1 => (self.0 as usize >> 19) & 0x1ff, // 9 bit + 0 => (self.0 as usize >> 10) & 0x1ff, // 9 bit + _ => unreachable!(), + } } - fn pte(self) -> u64 { - self.0 >> 10 + /// Return entire ppn + fn entire_ppn(self) -> u64 { + (self.0 >> 10) & 0xfff_ffff_ffff // 44 bit + } + + /// Is it has already been created + fn already_created(self) -> bool { + self.0 & PteFlag::Valid as u64 == 1 } } @@ -107,6 +130,7 @@ impl PageTableAddress { } impl GuestPhysicalAddress { + /// Return vpn value with index. fn vpn(self, index: usize) -> usize { match index { 2 => (self.0 >> 30) & 0x7ff, @@ -115,6 +139,12 @@ impl GuestPhysicalAddress { _ => unreachable!(), } } + + /// Return page offset. + #[allow(dead_code)] + fn page_offset(self) -> usize { + self.0 & 0xfff + } } impl HostPhysicalAddress { diff --git a/src/memmap/page_table/sv39x4.rs b/src/memmap/page_table/sv39x4.rs index 5bb6e75..ee099ef 100644 --- a/src/memmap/page_table/sv39x4.rs +++ b/src/memmap/page_table/sv39x4.rs @@ -3,14 +3,15 @@ //! //! [The RISC-V Instruction Set Manual: Volume II Version 20240411](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf) p.151 -use alloc::boxed::Box; -use core::slice::from_raw_parts_mut; - use super::{ constants::{PAGE_SIZE, PAGE_TABLE_LEN}, PageTableAddress, PageTableEntry, PageTableLevel, PteFlag, }; -use crate::memmap::{HostPhysicalAddress, MemoryMap}; +use crate::h_extension::csrs::{hgatp, hgatp::HgatpMode}; +use crate::memmap::{GuestPhysicalAddress, HostPhysicalAddress, MemoryMap}; + +use alloc::boxed::Box; +use core::slice::from_raw_parts_mut; /// First page table size pub const FIRST_LV_PAGE_TABLE_LEN: usize = 2048; @@ -92,7 +93,7 @@ pub fn generate_page_table(root_table_start_addr: HostPhysicalAddress, memmaps: // Create next level page table next_table_addr = if current_page_table[vpn].already_created() { PageTableAddress( - usize::try_from(current_page_table[vpn].pte()).unwrap() * PAGE_SIZE, + usize::try_from(current_page_table[vpn].entire_ppn()).unwrap() * PAGE_SIZE, ) } else { let next_page_table = Box::new([PageTableEntry::default(); PAGE_TABLE_LEN]); @@ -110,3 +111,62 @@ pub fn generate_page_table(root_table_start_addr: HostPhysicalAddress, memmaps: } } } + +/// Translate gpa to hpa in sv39x4 +#[allow(clippy::cast_possible_truncation)] +#[allow(dead_code)] +pub fn trans_addr(gpa: GuestPhysicalAddress) -> HostPhysicalAddress { + let hgatp = hgatp::read(); + let mut page_table_addr = PageTableAddress(hgatp.ppn() << 12); + assert!(matches!(hgatp.mode(), HgatpMode::Sv39x4)); + for level in [ + PageTableLevel::Lv1GB, + PageTableLevel::Lv2MB, + PageTableLevel::Lv4KB, + ] { + let page_table = match level { + PageTableLevel::Lv1GB => unsafe { + from_raw_parts_mut(page_table_addr.to_pte_ptr(), FIRST_LV_PAGE_TABLE_LEN) + }, + PageTableLevel::Lv2MB | PageTableLevel::Lv4KB => unsafe { + from_raw_parts_mut(page_table_addr.to_pte_ptr(), PAGE_TABLE_LEN) + }, + }; + let pte = page_table[gpa.vpn(level as usize)]; + if pte.is_leaf() { + match level { + PageTableLevel::Lv1GB => { + assert!( + pte.ppn(0) == 0, + "Address translation failed: pte.ppn[0] != 0" + ); + assert!( + pte.ppn(1) == 0, + "Address translation failed: pte.ppn[1] != 0" + ); + return HostPhysicalAddress( + pte.ppn(2) << 30 | gpa.vpn(1) << 21 | gpa.vpn(0) << 12 | gpa.page_offset(), + ); + } + PageTableLevel::Lv2MB => { + assert!( + pte.ppn(0) == 0, + "Address translation failed: pte.ppn[0] != 0" + ); + return HostPhysicalAddress( + pte.ppn(2) << 30 | pte.ppn(1) << 21 | gpa.vpn(0) << 12 | gpa.page_offset(), + ); + } + PageTableLevel::Lv4KB => { + return HostPhysicalAddress( + pte.ppn(2) << 30 | pte.ppn(1) << 21 | pte.ppn(0) << 12 | gpa.page_offset(), + ) + } + } + } + + page_table_addr = PageTableAddress(pte.entire_ppn() as usize * PAGE_SIZE); + } + + unreachable!(); +} diff --git a/src/sbi.rs b/src/sbi.rs index da28439..9a81347 100644 --- a/src/sbi.rs +++ b/src/sbi.rs @@ -4,7 +4,7 @@ mod rfence; -use crate::device::{clint, uart, Device}; +use crate::device::{clint, uart, MmioDevice}; use fdt::Fdt; use rustsbi::RustSBI; diff --git a/src/trap/hypervisor_supervisor.rs b/src/trap/hypervisor_supervisor.rs index 5b8ad89..63b87d6 100644 --- a/src/trap/hypervisor_supervisor.rs +++ b/src/trap/hypervisor_supervisor.rs @@ -18,8 +18,8 @@ use riscv::register::scause::{self, Trap}; #[allow(clippy::inline_always)] unsafe fn hstrap_exit() -> ! { // aquire hypervisor data - let mut hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; - let stack_top = hypervisor_data.guest().stack_top(); + let hypervisor_data = unsafe { HYPERVISOR_DATA.lock() }; + let stack_top = hypervisor_data.get().unwrap().guest().stack_top(); // release HYPERVISOR_DATA lock drop(hypervisor_data); diff --git a/src/trap/hypervisor_supervisor/exception.rs b/src/trap/hypervisor_supervisor/exception.rs index b2fc52c..a38c098 100644 --- a/src/trap/hypervisor_supervisor/exception.rs +++ b/src/trap/hypervisor_supervisor/exception.rs @@ -3,11 +3,17 @@ mod sbi_handler; use super::hstrap_exit; +use crate::device::DeviceEmulateError; use crate::guest; -use crate::h_extension::csrs::vstvec; +use crate::h_extension::{ + csrs::{htinst, htval, vstvec}, + HvException, +}; +use crate::memmap::HostPhysicalAddress; use crate::HYPERVISOR_DATA; + use core::arch::asm; -use raki::{Decode, Isa::Rv64, OpcodeKind, ZicntrOpcode}; +use raki::{Decode, Instruction, Isa::Rv64, OpcodeKind, ZicntrOpcode}; use riscv::register::{ scause::{self, Exception}, stval, @@ -20,7 +26,7 @@ use sbi_handler::{sbi_base_handler, sbi_rfnc_handler}; #[allow(clippy::inline_always, clippy::module_name_repetitions)] pub extern "C" fn hs_forward_exception() { unsafe { - let mut context = HYPERVISOR_DATA.lock().guest().context; + let mut context = HYPERVISOR_DATA.lock().get().unwrap().guest().context; asm!( "csrw vsepc, {sepc}", "csrw vscause, {scause}", @@ -83,26 +89,95 @@ fn virtual_instruction_handler(inst_bytes: u32, context: &mut guest::context::Co /// Trap handler for exception #[allow(clippy::cast_possible_truncation, clippy::module_name_repetitions)] pub unsafe fn trap_exception(exception_cause: Exception) -> ! { - let mut context = unsafe { HYPERVISOR_DATA.lock().guest().context }; - match exception_cause { Exception::SupervisorEnvCall => panic!("SupervisorEnvCall should be handled by M-mode"), // Enum not found in `riscv` crate. - Exception::Unknown => { - match scause::read().code() { - // Ecall from VS-mode - 10 => { - sbi_vs_mode_handler(&mut context); - context.set_sepc(context.sepc() + 4); + Exception::Unknown => match HvException::from(scause::read().code()) { + HvException::EcallFromVsMode => { + let mut context = unsafe { HYPERVISOR_DATA.lock().get().unwrap().guest().context }; + sbi_vs_mode_handler(&mut context); + context.set_sepc(context.sepc() + 4); + } + HvException::InstructionGuestPageFault => { + panic!("Instruction guest-page fault"); + } + HvException::LoadGuestPageFault => { + let fault_addr = HostPhysicalAddress(htval::read().bits << 2); + let fault_inst_value = htinst::read().bits; + // htinst bit 1 replaced with a 0. + // thus it needed to flip bit 1. + // ref: vol. II p.161 + let fault_inst = Instruction::try_from(fault_inst_value | 0b10) + .expect("decoding load fault instruction failed"); + + let mut hypervisor_data = HYPERVISOR_DATA.lock(); + match hypervisor_data + .get_mut() + .unwrap() + .devices() + .plic + .emulate_read(fault_addr) + { + Ok(value) => { + let mut context = hypervisor_data.get().unwrap().guest().context; + context.set_xreg(fault_inst.rd.expect("rd is not found"), u64::from(value)); + if (fault_inst_value & 0b10) >> 1 == 0 { + // compressed instruction + context.set_sepc(context.sepc() + 2); + } else { + // normal size instruction + context.set_sepc(context.sepc() + 4); + } + } + Err( + DeviceEmulateError::InvalidAddress + | DeviceEmulateError::InvalidContextId + | DeviceEmulateError::ReservedRegister, + ) => hs_forward_exception(), } - // Virtual Instruction - 22 => { - virtual_instruction_handler(stval::read() as u32, &mut context); - context.set_sepc(context.sepc() + 4); + } + HvException::StoreAmoGuestPageFault => { + let fault_addr = HostPhysicalAddress(htval::read().bits << 2); + let fault_inst_value = htinst::read().bits; + // htinst bit 1 replaced with a 0. + // thus it needed to flip bit 1. + // ref: vol. II p.161 + let fault_inst = Instruction::try_from(fault_inst_value | 0b10) + .expect("decoding load fault instruction failed"); + + let mut hypervisor_data = HYPERVISOR_DATA.lock(); + let context = hypervisor_data.get().unwrap().guest().context; + let update_epc = |fault_inst_value: usize, mut context: guest::context::Context| { + if (fault_inst_value & 0b10) >> 1 == 0 { + // compressed instruction + context.set_sepc(context.sepc() + 2); + } else { + // normal size instruction + context.set_sepc(context.sepc() + 4); + } + }; + let store_value = context.xreg(fault_inst.rs2.expect("rs2 is not found")); + + if let Ok(()) = hypervisor_data + .get_mut() + .unwrap() + .devices() + .plic + .emulate_write(fault_addr, store_value.try_into().unwrap()) + { + update_epc(fault_inst_value, context); + drop(hypervisor_data); + hstrap_exit(); // exit handler } - _ => unreachable!(), + + hs_forward_exception(); } - } + HvException::VirtualInstruction => { + let mut context = unsafe { HYPERVISOR_DATA.lock().get().unwrap().guest().context }; + virtual_instruction_handler(stval::read() as u32, &mut context); + context.set_sepc(context.sepc() + 4); + } + }, _ => hs_forward_exception(), } diff --git a/src/trap/hypervisor_supervisor/interrupt.rs b/src/trap/hypervisor_supervisor/interrupt.rs index 4d7183b..dfddc00 100644 --- a/src/trap/hypervisor_supervisor/interrupt.rs +++ b/src/trap/hypervisor_supervisor/interrupt.rs @@ -1,9 +1,11 @@ //! Trap VS-mode interrupt. use super::hstrap_exit; -use crate::device::Device; +use crate::device::plic::ContextId; +use crate::device::MmioDevice; use crate::h_extension::csrs::{hvip, vsip, VsInterruptKind}; use crate::HYPERVISOR_DATA; + use riscv::register::scause::Interrupt; use riscv::register::sie; @@ -12,9 +14,10 @@ use riscv::register::sie; pub unsafe fn trap_interrupt(interrupt_cause: Interrupt) -> ! { match interrupt_cause { Interrupt::SupervisorSoft => { - let mut hypervisor_data = HYPERVISOR_DATA.lock(); - let hart_id = hypervisor_data.guest().hart_id(); - let clint_addr = hypervisor_data.devices.as_ref().unwrap().clint.paddr(); + let hypervisor_data = HYPERVISOR_DATA.lock(); + // TODO handle with device::Clint + let hart_id = hypervisor_data.get().unwrap().guest().hart_id(); + let clint_addr = hypervisor_data.get().unwrap().devices.clint.paddr(); vsip::set_ssoft(); let interrupt_addr = (clint_addr.raw() + hart_id * 4) as *mut u64; @@ -24,7 +27,21 @@ pub unsafe fn trap_interrupt(interrupt_cause: Interrupt) -> ! { hvip::set(VsInterruptKind::Timer); sie::clear_stimer(); } - Interrupt::SupervisorExternal => hvip::set(VsInterruptKind::External), + Interrupt::SupervisorExternal => { + let mut hypervisor_data = HYPERVISOR_DATA.lock(); + let hart_id = hypervisor_data.get().unwrap().guest().hart_id(); + let context_id = ContextId::new(hart_id, true); + + // read plic claim/update register and reflect to plic.claim_complete. + hypervisor_data + .get_mut() + .unwrap() + .devices() + .plic + .update_claim_complete(&context_id); + + hvip::set(VsInterruptKind::External); + } Interrupt::Unknown => panic!("unknown interrupt type"), } diff --git a/src/trap/machine/exception.rs b/src/trap/machine/exception.rs index 20f014e..e72dcf6 100644 --- a/src/trap/machine/exception.rs +++ b/src/trap/machine/exception.rs @@ -1,7 +1,7 @@ //! Trap machine exception. use super::{mtrap_exit, mtrap_exit_sbi}; -use crate::device::Device; +use crate::device::MmioDevice; use crate::print; use crate::{HYPERVISOR_DATA, SBI}; use riscv::register::{ @@ -93,7 +93,7 @@ pub extern "C" fn forward_exception() { pub unsafe fn trap_exception(exception_cause: Exception) -> ! { match exception_cause { Exception::MachineEnvCall | Exception::SupervisorEnvCall | Exception::UserEnvCall => { - let context = unsafe { HYPERVISOR_DATA.lock().guest().context }; + let context = unsafe { HYPERVISOR_DATA.lock().get().unwrap().guest().context }; let a0 = context.xreg(10) as usize; let a1 = context.xreg(11) as usize; let a2 = context.xreg(12) as usize; diff --git a/vmlinux b/vmlinux deleted file mode 100755 index a3e725d..0000000 Binary files a/vmlinux and /dev/null differ diff --git a/wild-screen-alloc b/wild-screen-alloc deleted file mode 160000 index 6abe81f..0000000 --- a/wild-screen-alloc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6abe81f71d248f11530ccbf21373083b3c3fd2e7