From 19474a87597144d5c24f30ddc5a8fd869c9512e4 Mon Sep 17 00:00:00 2001 From: Manami Mori Date: Mon, 7 Mar 2022 17:30:06 +0900 Subject: [PATCH] Version 0.3.0 --- .github/workflows/rust.yml | 2 + CONTRIBUTING.md | 44 ++++ README.md | 98 +++++++-- src/Makefile | 4 +- src/common/src/acpi.rs | 1 + src/common/src/acpi/iort.rs | 109 ++++++++++ src/common/src/cpu.rs | 1 + src/common/src/lib.rs | 3 + src/common/src/paging.rs | 7 + src/common/src/smmu.rs | 173 ++++++++++++++++ src/hypervisor_bootloader/.cargo/config | 1 + src/hypervisor_bootloader/rust-toolchain.toml | 2 +- src/hypervisor_bootloader/src/main.rs | 10 +- src/hypervisor_bootloader/src/smmu.rs | 188 ++++++++++++++++++ src/hypervisor_kernel/.cargo/config | 6 +- src/hypervisor_kernel/rust-toolchain.toml | 2 +- src/hypervisor_kernel/src/emulation.rs | 7 +- src/hypervisor_kernel/src/emulation/load.rs | 11 +- src/hypervisor_kernel/src/emulation/store.rs | 11 +- src/hypervisor_kernel/src/main.rs | 15 +- src/hypervisor_kernel/src/memory_hook.rs | 7 +- src/hypervisor_kernel/src/pci.rs | 7 +- src/hypervisor_kernel/src/smmu.rs | 113 +++++++++++ 23 files changed, 775 insertions(+), 47 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 src/common/src/acpi/iort.rs create mode 100644 src/common/src/smmu.rs create mode 100644 src/hypervisor_bootloader/src/smmu.rs create mode 100644 src/hypervisor_kernel/src/smmu.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 80dfbee..daf0377 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,6 +5,8 @@ on: branches: [ main ] pull_request: branches: [ main ] + schedule: + - cron: '15 4 */1 * *' env: CARGO_TERM_COLOR: always diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9414538 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contributing +We welcome your issue reports and pull requests. +The following describes issues and pull requests for contributing MilvusVisor. + +## Issues +We will receive the following kind of issues. + +- Bug report +- Documentation error + +You also feel free to create issues about feature requests and discuss them. However, we may not be able to respond to them because of our limited development resources. + + +In creating issues about a bug, please write the following information. + +- Machine information that you tested (Product name of the machine, and CPU, and devices) +- The way to reproduce the problem. + +### Pull requests + +We will receive the following kind of PRs. + +- New feature +- Bugfix +- New documentation +- Fix documentation (typo, better sentences) + + +In creating PR of a new feature, please write the following information. + +- Summary of the new feature +- Machine information that you tested (Product name of the machine, and CPU, and devices) +- The way to test the new feature + + +In creating PR for a bugfix, please write the following information. + +- Summary of the bug fix +- Machine information that you tested (Product name of the machine, and CPU, and devices) +- The way to reproduce the problem without the bugfix or the link to the original issue +- The way to test the bugfix + +Note that we will assume that your uploaded code is under the MIT license. +If you want to provide your code under other license, please ask us whether there is license problem or not on an issue before you create a PR. diff --git a/README.md b/README.md index 5a114bd..e45c329 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,75 @@ -# How to build the hypervisor +# MilvusVisor +MilvusVisor is a thin hypervisor that runs on aarch64 CPUs. -## By Rust toolchain -(TBD) +The features of the MilvusVisor are the following. +- (Theoretically) Smaller footprint and overhead than typical VMM (e.g. QEMU/KVM, Xen) +- Support running only one guest OS simultaneously for keeping it simple and thin +- Written in Rust -## By docker -### Requirements + +MilvusVisor allows providing functions OS-independently without the overhead of device virtualization. +We are currently developing MilvusVisor as a research activity to achieve HPC environments that provide root privilege to the users without virtualization overhead. + +## Functions + +Currently, MilvusVisor provides the following function. + +- Protecting non-volatile data in devices from guest OS (e.g. Firmware, MAC address) + - Supported device: Intel I210 +- Protecting MilvusVisor itself against DMA attack + - Using SMMUv3 Stage 2 Page Translation to protect from DMA attack + +## Tested machines + +We have tested MilvusVisor on the following machines. + +- FX700 +- AML-S805X-AC +- QEMU + +The following table shows which feature worked on which machines. + +| Test items \\ Machine | FX700 | AML | QEMU | +|:-------------------------------------------------|:-----:|:---:|:----:| +| Booting Linux on MilvusVisor (Multi-core) | o | o | o | +| Protecting non-volatile data of Intel I210 | o | - | - | +| Protecting MilvusVisor itself against DMA attack | o | - | - | + + +## How to build the hypervisor + +### By Rust toolchain + +#### Requirements +- `rustup` command-line tool (you can install from https://rustup.rs/) + +#### Steps (commands list) +``` +rustup component add rust-src +cd path/to/repo-root/src +make +``` + +Next (How to run the hypervisor)[#How to run the hypervisor] + +### By docker +#### Requirements - Docker (Tested by `Docker version 20.10.8, build 3967b7d28e`) - I tested by non-root users (See [this](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) to run docker command by non-root user) -### Steps (commands list) +#### Steps (commands list) ```bash cd path/to/repo-root/src ./build_docker_image.sh #Build docker image to build ./build_hypervisor_by_docker.sh #Build the hypervisor by the docker image ``` -More detail, please see the scripts. +For more detail, please see the scripts. -# How to run the hypervisor -## On QEMU -First, please install QEMU that support to emulate `QEMU 2.12 ARM Virtual Machine`, `cortex-a53` CPU. +## How to run the hypervisor +### On QEMU +First, please install QEMU that supports emulating `QEMU ARM Virtual Machine`, `a64fx` CPU. Then, run the following command to run the built hypervisor. ```bash @@ -28,16 +77,35 @@ cd path/to/repo-root/src make QEMU_EFI=/usr/share/qemu-efi/QEMU_EFI.fd run #Please set the path of your QEMU_EFI.fd to QEMU_EFI ``` -## On a physical machine from an USB memory stick -### Requirement -- Prepare a USB memory which has an EFI (FAT) partition that has `/EFI/BOOT/` directory. Please confirm that there is no important file in the partition. +### On a physical machine from a USB memory stick +#### Requirement +- Prepare a USB memory that has an EFI (FAT) partition that has `/EFI/BOOT/` directory. Please confirm that there is no important file in the partition. - Prepare a physical machine that has ARMv8-A or later, and UEFI firmware. -### Steps +#### Steps 1. Attach your USB memory stick to the development machine which built the hypervisor binary. 2. Identify the EFI partition (in the following description, `/dev/sdX1` is the EFI partition). 3. Run `sudo make DEVICE=/dev/sdX1 write` to copy the binary. - !! Please be carefully not to specifying a wrong partition as `DEVICE` because the script mount/unmount the partition and copy the binary file with root privilege.!! + !! Please be careful not to specify a wrong partition as `DEVICE` because the script mount/unmount the partition and copies the binary file with root privilege.!! 4. Detach the USB memory from the development machine, and attach it to the physical machine to run the hypervisor. 5. Boot the physical machine with UEFI, and specify `BOOTAA64.EFI` in the EFI partition as the EFI application to boot. +## How to generate the documentation +You can generate the document by `cargo doc` in each cargo project directory. + +If you want to see bootloader's document, please run the following command. + +```bash +cd path/to/repo-root/src/hypervisor_bootloader +cargo doc --open # Browser will open +``` + +If you want to see kernel's document, please run the following command. + +```bash +cd path/to/repo-root/src/hypervisor_kernel +cargo doc --open # Browser will open +``` + +## Acknowledgment +This work was supported by JSPS KAKENHI Grant Number 21K17727. diff --git a/src/Makefile b/src/Makefile index 1a53be8..1fd39ef 100644 --- a/src/Makefile +++ b/src/Makefile @@ -43,10 +43,10 @@ fmt: $(CD) $(KERNEL) && $(CARGO) fmt run: all - $(QEMU) -m 1G -cpu cortex-a53 -machine virt-2.12,virtualization=on -smp 4 -nographic -bios $(QEMU_EFI) -drive file=fat:rw:bin/,format=raw,media=disk + $(QEMU) -m 1G -cpu a64fx -machine virt,virtualization=on,iommu=smmuv3 -smp 4 -nographic -bios $(QEMU_EFI) -drive file=fat:rw:bin/,format=raw,media=disk debug: all - $(QEMU) -m 1G -cpu cortex-a53 -machine virt-2.12,virtualization=on -smp 4 -monitor stdio -bios $(QEMU_EFI) -drive file=fat:rw:bin/,format=raw,media=disk + $(QEMU) -m 1G -cpu a64fx -machine virt,virtualization=on,iommu=smmuv3 -smp 4 -monitor stdio -bios $(QEMU_EFI) -drive file=fat:rw:bin/,format=raw,media=disk write: $(MOUNT) $(DEVICE) /mnt diff --git a/src/common/src/acpi.rs b/src/common/src/acpi.rs index 27d12bd..56b04ed 100644 --- a/src/common/src/acpi.rs +++ b/src/common/src/acpi.rs @@ -9,6 +9,7 @@ //! //! Supported ACPI Version 6.4 +pub mod iort; pub mod madt; const RSDP_SIGNATURE: [u8; 8] = *b"RSD PTR "; diff --git a/src/common/src/acpi/iort.rs b/src/common/src/acpi/iort.rs new file mode 100644 index 0000000..e0e970f --- /dev/null +++ b/src/common/src/acpi/iort.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2022 RIKEN +// All rights reserved. +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +//! +//! I/O Remapping Table +//! + +const SMMU_V3_NODE_TYPE: u8 = 0x04; + +#[repr(C, packed)] +pub struct IORT { + signature: [u8; 4], + length: u32, + revision: u8, + checksum: u8, + oem_id: [u8; 6], + oem_table_id: [u8; 8], + oem_revision: u32, + creator_id: [u8; 4], + creator_revision: u32, + number_of_iort_nodes: u32, + offset_to_array_of_iort_nodes: u32, + reserved: u32, +} + +#[repr(C, packed)] +pub struct SmmuV3Node { + s_type: u8, + length: u16, + revision: u8, + id: u32, + pub number_of_id_mappings: u32, + reference_to_id_array: u32, + pub base_address: u64, + flag: u32, + reserved: u32, + vatos_address: u64, + model: u32, + event: u32, + pri: u32, + gerr: u32, + sync: u32, + proximity_domain: u32, + device_id_mapping_index: u32, +} + +pub struct IdMappingIter { + p: usize, + n: u32, +} + +#[derive(Clone)] +pub struct IdMapping { + pub input_base: u32, + pub number_of_ids: u32, + pub output_base: u32, + pub output_reference: u32, + pub flags: u32, +} + +impl IORT { + pub const SIGNATURE: [u8; 4] = *b"IORT"; + + pub fn get_smmu_v3_information(&self) -> Option<&SmmuV3Node> { + let mut node_address = + self as *const Self as usize + self.offset_to_array_of_iort_nodes as usize; + let num_of_entries = self.number_of_iort_nodes; + for _ in 0..num_of_entries { + if unsafe { *(node_address as *const u8) } == SMMU_V3_NODE_TYPE { + return Some(unsafe { &*(node_address as *const SmmuV3Node) }); + } + node_address += unsafe { *((node_address + 1) as *const u16) } as usize; + } + return None; + } +} + +impl SmmuV3Node { + pub fn get_array_of_id_mappings(&self) -> IdMappingIter { + IdMappingIter { + p: self as *const _ as usize + self.reference_to_id_array as usize, + n: self.number_of_id_mappings, + } + } +} + +impl Iterator for IdMappingIter { + type Item = IdMapping; + + fn next(&mut self) -> Option { + if self.n == 0 { + None + } else { + let a = self.p; + self.n -= 1; + self.p += core::mem::size_of::(); + Some(unsafe { &*(a as *const Self::Item) }.clone()) + } + } +} + +impl IdMapping { + pub const fn is_single_map(&self) -> bool { + (self.flags & 1) != 0 + } +} diff --git a/src/common/src/cpu.rs b/src/common/src/cpu.rs index 00e1059..28895e1 100644 --- a/src/common/src/cpu.rs +++ b/src/common/src/cpu.rs @@ -105,6 +105,7 @@ pub const HCR_EL2_VM: u64 = 1 << 0; pub const VTCR_EL2_RES1: u64 = 1 << 31; pub const VTCR_EL2_HWU_BITS_OFFSET: u64 = 25; pub const VTCR_EL2_PS_BITS_OFFSET: u64 = 16; +pub const VTCR_EL2_PS: u64 = 0b111 << VTCR_EL2_PS_BITS_OFFSET; pub const VTCR_EL2_TG0_BITS_OFFSET: u64 = 14; pub const VTCR_EL2_SH0_BITS_OFFSET: u64 = 12; pub const VTCR_EL2_ORG0_BITS_OFFSET: u64 = 10; diff --git a/src/common/src/lib.rs b/src/common/src/lib.rs index 32e82d2..00d9592 100644 --- a/src/common/src/lib.rs +++ b/src/common/src/lib.rs @@ -10,6 +10,7 @@ pub mod acpi; pub mod cpu; pub mod paging; pub mod serial_port; +pub mod smmu; use crate::serial_port::SerialPortInfo; @@ -53,7 +54,9 @@ pub struct EcamInfo { /// For communicating about system registers between hypervisor_bootloader and hypervisor_kernel pub struct SystemInformation { pub vbar_el2: u64, + pub acpi_rsdp_address: Option, pub memory_pool: &'static ([MaybeUninit; ALLOC_SIZE / PAGE_SIZE], usize), pub serial_port: Option, pub ecam_info: Option, + pub smmu_v3_base_address: Option, } diff --git a/src/common/src/paging.rs b/src/common/src/paging.rs index baaa652..580f406 100644 --- a/src/common/src/paging.rs +++ b/src/common/src/paging.rs @@ -39,6 +39,13 @@ const STAGE_2_PAGE_ENTRY_ATTRIBUTE: u64 = 0b11 << 8 /* SH bits (Inner sharable) */| 0b1111 << 2 /* MemAttr(Write-back) */; +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Shareability { + NonShareable, + OuterShareable, + InterShareable, +} + #[derive(Copy, Clone)] pub struct TTBR(u64); /* Translation Table Base Register */ diff --git a/src/common/src/smmu.rs b/src/common/src/smmu.rs new file mode 100644 index 0000000..5e71a3e --- /dev/null +++ b/src/common/src/smmu.rs @@ -0,0 +1,173 @@ +// Copyright (c) 2022 RIKEN +// All rights reserved. +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +//! +//! System Memory Management Unit +//! +//! Supported Version: 3.3 + +use crate::bitmask; +use crate::paging::Shareability; + +pub const SMMU_MEMORY_MAP_SIZE: usize = 64 * 0x1000; +pub const SMMU_IDR0: usize = 0x00; +pub const SMMU_IDR1: usize = 0x04; +pub const SMMU_IDR2: usize = 0x08; +pub const SMMU_IDR3: usize = 0x0C; +pub const SMMU_IDR4: usize = 0x10; +pub const SMMU_IDR5: usize = 0x14; + +pub const SMMU_CR0: usize = 0x20; +pub const SMMU_CR0ACK: usize = 0x24; +pub const SMMU_CR1: usize = 0x28; +pub const SMMU_GBPA: usize = 0x44; +pub const SMMU_STRTAB_BASE: usize = 0x80; +pub const SMMU_STRTAB_BASE_CFG: usize = 0x88; + +pub const SMMU_IDR0_HYP: u32 = 1 << 9; +pub const SMMU_IDR0_S1P: u32 = 1 << 1; +pub const SMMU_IDR0_S2P: u32 = 1 << 0; +pub const SMMU_IDR0_TTENDIAN_BITS_OFFSET: u32 = 21; +pub const SMMU_IDR0_TTENDIAN: u32 = 0b11 << SMMU_IDR0_TTENDIAN_BITS_OFFSET; +pub const SMMU_IDR0_ST_LEVEL_BITS_OFFSET: u32 = 27; +pub const SMMU_IDR0_ST_LEVEL: u32 = 0b11 << SMMU_IDR0_ST_LEVEL_BITS_OFFSET; + +pub const SMMU_IDR5_GRAN4K: u32 = 1 << 4; + +pub const SMMU_CR0_SMMUEN: u32 = 1 << 0; + +pub const SMMU_CR1_TABLE_SH_BITS_OFFSET: u32 = 10; + +pub const SMMU_GBPA_SHCFG_BITS_OFFSET: u32 = 12; + +type SteArrayBaseType = u64; +const STE_ARRAY_BASE_TYPE_BITS: SteArrayBaseType = + (core::mem::size_of::() * 8) as SteArrayBaseType; + +const STE_V: SteArrayBaseType = 1 << 0; + +const STE_CONFIG_OFFSET: SteArrayBaseType = 1; +const STE_CONFIG_INDEX: usize = (1 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_CONFIG: SteArrayBaseType = 0b111 << STE_CONFIG_OFFSET; + +const STE_S2T0SZ_OFFSET: SteArrayBaseType = 160 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2T0SZ_INDEX: usize = (160 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2T0SZ: SteArrayBaseType = 0b111111 << STE_S2T0SZ_OFFSET; + +const STE_S2SL0_OFFSET: SteArrayBaseType = 166 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2SL0_INDEX: usize = (166 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2SL0: SteArrayBaseType = 0b11 << STE_S2SL0_OFFSET; + +const STE_S2IR0_OFFSET: SteArrayBaseType = 168 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2IR0_INDEX: usize = (168 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2IR0: SteArrayBaseType = 0b11 << STE_S2IR0_OFFSET; + +const STE_S2OR0_OFFSET: SteArrayBaseType = 170 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2OR0_INDEX: usize = (170 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2OR0: SteArrayBaseType = 0b11 << STE_S2OR0_OFFSET; + +const STE_S2SH0_OFFSET: SteArrayBaseType = 172 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2SH0_INDEX: usize = (172 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2SH0: SteArrayBaseType = 0b11 << STE_S2SH0_OFFSET; + +const STE_S2TG_OFFSET: SteArrayBaseType = 174 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2TG_INDEX: usize = (174 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2TG: SteArrayBaseType = 0b11 << STE_S2TG_OFFSET; + +const STE_S2PS_OFFSET: SteArrayBaseType = 176 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2PS_INDEX: usize = (176 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2PS: SteArrayBaseType = 0b111 << STE_S2PS_OFFSET; + +const STE_S2AA64_OFFSET: SteArrayBaseType = 179 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2AA64_INDEX: usize = (179 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2AA64: SteArrayBaseType = 0b1 << STE_S2AA64_OFFSET; + +const STE_S2TTB_OFFSET: SteArrayBaseType = 196 % STE_ARRAY_BASE_TYPE_BITS; +const STE_S2TTB_INDEX: usize = (196 / STE_ARRAY_BASE_TYPE_BITS) as usize; +const STE_S2TTB: SteArrayBaseType = (bitmask!(51, 4) >> 4) << STE_S2TTB_OFFSET; +// MEMO: Set S2HWU** to 0 because the page table is shared with CPUs. + +#[derive(Clone)] +pub struct StreamTableEntry([SteArrayBaseType; 8]); + +impl StreamTableEntry { + pub const fn new() -> Self { + Self([0; 8]) + } + + pub fn validate(&mut self) { + self.0[0] |= STE_V; + } + + pub fn set_config(&mut self, is_stage1_bypassed: bool, is_stage2_bypassed: bool) { + self.0[STE_CONFIG_INDEX] = (self.0[STE_CONFIG_INDEX] & (!STE_CONFIG)) + | ((0b100 + | (!is_stage1_bypassed as SteArrayBaseType) + | ((!is_stage2_bypassed as SteArrayBaseType) << 1)) + << STE_CONFIG_OFFSET); + } + + pub fn set_s2t0sz(&mut self, s2t0sz: u32) { + self.0[STE_S2T0SZ_INDEX] = (self.0[STE_S2T0SZ_INDEX] & (!STE_S2T0SZ)) + | ((s2t0sz as SteArrayBaseType) << STE_S2T0SZ_OFFSET); + } + + pub fn set_s2sl0(&mut self, s2sl0: u32) { + self.0[STE_S2SL0_INDEX] = (self.0[STE_S2SL0_INDEX] & (!STE_S2SL0)) + | ((s2sl0 as SteArrayBaseType) << STE_S2SL0_OFFSET); + } + + pub fn set_s2ir0(&mut self, is_write_back: bool, is_write_allocate: bool) { + self.0[STE_S2IR0_INDEX] = (self.0[STE_S2IR0_INDEX] & (!STE_S2IR0)) + | (((is_write_back as SteArrayBaseType) + | ((!is_write_allocate as SteArrayBaseType) << 1)) + << STE_S2IR0_OFFSET); + } + + pub fn set_s2or0(&mut self, is_write_back: bool, is_write_allocate: bool) { + self.0[STE_S2OR0_INDEX] = (self.0[STE_S2OR0_INDEX] & (!STE_S2OR0)) + | (((is_write_back as SteArrayBaseType) + | ((!is_write_allocate as SteArrayBaseType) << 1)) + << STE_S2OR0_OFFSET); + } + + pub fn set_s2sh0(&mut self, sharaebility: Shareability) { + let s = match sharaebility { + Shareability::NonShareable => 0b00, + Shareability::OuterShareable => 0b10, + Shareability::InterShareable => 0b11, + }; + self.0[STE_S2SH0_INDEX] = + (self.0[STE_S2SH0_INDEX] & (!STE_S2SH0)) | (s << STE_S2OR0_OFFSET); + } + + pub fn set_s2tg(&mut self, granule_size: usize) { + let g = match granule_size { + 0x1000 => 0b00, + 0x4000 => 0b10, + 0x10000 => 0b01, + _ => unimplemented!(), + }; + self.0[STE_S2TG_INDEX] = (self.0[STE_S2TG_INDEX] & (!STE_S2TG)) | (g << STE_S2TG_OFFSET); + } + + pub fn set_s2ps(&mut self, s2ps: u8) { + self.0[STE_S2PS_INDEX] = (self.0[STE_S2PS_INDEX] & (!STE_S2PS)) + | ((s2ps as SteArrayBaseType) << STE_S2PS_OFFSET); + } + + pub fn set_s2aa64(&mut self, is_aa64: bool) { + self.0[STE_S2AA64_INDEX] = (self.0[STE_S2AA64_INDEX] & (!STE_S2AA64)) + | ((is_aa64 as SteArrayBaseType) << STE_S2AA64_OFFSET); + } + + pub fn set_stage2_translation_table(&mut self, table_address: usize) { + assert_eq!(table_address & !(bitmask!(51, 4)), 0); + self.0[STE_S2TTB_INDEX] = + (self.0[STE_S2TTB_INDEX] & (!STE_S2TTB)) | (table_address as SteArrayBaseType); + self.set_s2aa64(true); + } +} diff --git a/src/hypervisor_bootloader/.cargo/config b/src/hypervisor_bootloader/.cargo/config index e37f508..387643f 100644 --- a/src/hypervisor_bootloader/.cargo/config +++ b/src/hypervisor_bootloader/.cargo/config @@ -1,5 +1,6 @@ [build] target = "aarch64-unknown-uefi" +rustflags = ["-C", "target-feature=+v8.1a"] [unstable] build-std-features = ["compiler-builtins-mem"] diff --git a/src/hypervisor_bootloader/rust-toolchain.toml b/src/hypervisor_bootloader/rust-toolchain.toml index a630489..b442dc7 100644 --- a/src/hypervisor_bootloader/rust-toolchain.toml +++ b/src/hypervisor_bootloader/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly" -targets = ["aarch64-unknown-none"] +targets = ["aarch64-unknown-none"] diff --git a/src/hypervisor_bootloader/src/main.rs b/src/hypervisor_bootloader/src/main.rs index 6bce497..2f28a60 100644 --- a/src/hypervisor_bootloader/src/main.rs +++ b/src/hypervisor_bootloader/src/main.rs @@ -20,6 +20,7 @@ mod paging; mod panic; mod pci; mod serial_port; +mod smmu; use common::cpu::*; use common::{ @@ -104,6 +105,12 @@ extern "C" fn efi_main(image_handle: EfiHandle, system_table: *mut EfiSystemTabl None }; + let smmu_v3_base_address = if let Some(acpi_address) = unsafe { ACPI_20_TABLE_ADDRESS } { + smmu::detect_smmu(acpi_address) + } else { + None + }; + let stack_address = allocate_memory(STACK_PAGES).expect("Failed to alloc stack"); println!( "Stack for BSP: {:#X}", @@ -112,10 +119,12 @@ extern "C" fn efi_main(image_handle: EfiHandle, system_table: *mut EfiSystemTabl println!("Call the hypervisor(Entry Point: {:#X})", entry_point); let mut system_info = SystemInformation { + acpi_rsdp_address: unsafe { ACPI_20_TABLE_ADDRESS }, vbar_el2: 0, memory_pool: unsafe { &MEMORY_POOL }, serial_port: serial, ecam_info, + smmu_v3_base_address, }; unsafe { (transmute::(entry_point))(&mut system_info) }; println!("Returned from the hypervisor"); @@ -555,7 +564,6 @@ extern "C" fn el2_to_el1(stack_pointer: usize) { mov x8, sp msr sp_el1, x8 mov sp, x0 // x0 contains stack_pointer - mov x0, xzr mov x0, (1 << 7) |(1 << 6) | (1 << 2) | (1) // EL1h(EL1 + Use SP_EL1) msr spsr_el2, x0 isb diff --git a/src/hypervisor_bootloader/src/smmu.rs b/src/hypervisor_bootloader/src/smmu.rs new file mode 100644 index 0000000..a7b2fb8 --- /dev/null +++ b/src/hypervisor_bootloader/src/smmu.rs @@ -0,0 +1,188 @@ +// Copyright (c) 2022 RIKEN +// All rights reserved. +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +//! +//! System Memory Management Unit +//! + +use crate::allocate_memory; +use crate::paging::map_address; + +use common::acpi::{get_acpi_table, iort::IORT, AcpiError}; +use common::cpu::{ + get_vtcr_el2, get_vttbr_el2, VTCR_EL2_PS, VTCR_EL2_PS_BITS_OFFSET, VTCR_EL2_SL0, + VTCR_EL2_SL0_BITS_OFFSET, VTCR_EL2_T0SZ, VTCR_EL2_T0SZ_BITS_OFFSET, +}; +use common::paging::Shareability; +use common::smmu::{ + StreamTableEntry, SMMU_CR0, SMMU_CR0ACK, SMMU_CR0_SMMUEN, SMMU_CR1, + SMMU_CR1_TABLE_SH_BITS_OFFSET, SMMU_GBPA, SMMU_GBPA_SHCFG_BITS_OFFSET, SMMU_IDR0, + SMMU_IDR0_S2P, SMMU_IDR0_ST_LEVEL, SMMU_IDR0_TTENDIAN, SMMU_IDR0_TTENDIAN_BITS_OFFSET, + SMMU_IDR1, SMMU_IDR5, SMMU_IDR5_GRAN4K, SMMU_MEMORY_MAP_SIZE, SMMU_STRTAB_BASE, + SMMU_STRTAB_BASE_CFG, +}; +use common::{bitmask, PAGE_SIZE, STAGE_2_PAGE_SIZE}; + +/// SMMUの初期化および設定を行います +/// +/// 渡されたACPIテーブルの中からIORTを捜索し、その中からSMMUv3のベースアドレスを発見した場合に +/// 以下の初期化を行います。 +/// +/// 1. SMMU領域をマップ(マップするサイズ: [`SMMU_MEMORY_MAP_SIZE`]) +/// 2. SMMUがStage2ページングと2段階Stream Tableをサポートし利用可能か確認 +/// 3. CPUのStage2ページングの設定をコピーしSTEを作成する +/// 4. 作成したSTEを複製した2段目のStream Tableを一つ作成 +/// 5. 1段目のStream Tableの各Descriptorに作成した2段目のStream Tableのアドレスを設定 +/// 6. IORTに存在するStream IDの全てをマップできているか確認 +/// 7. SMMUの有効化 +/// +/// # Arguments +/// acpi_address: ACPI 2.0以降のRSDPのアドレス +/// +/// # Return Value +/// 上記すべての初期化に成功した場合にSome(smmuのベースアドレス)、そうでなければNone +pub fn detect_smmu(acpi_address: usize) -> Option { + match get_acpi_table(acpi_address, &IORT::SIGNATURE) { + Ok(address) => { + let iort = unsafe { &*(address as *const IORT) }; + if let Some(smmu_v3) = iort.get_smmu_v3_information() { + let base_address = smmu_v3.base_address as usize; + println!("SMMU Base Address: {:#X}", base_address); + + map_address( + base_address, + base_address, + SMMU_MEMORY_MAP_SIZE, + true, + true, + false, + true, + ) + .expect("Failed to map SMMU Memory Area"); + let smmu_idr0 = unsafe { *((base_address + SMMU_IDR0) as *const u32) }; + let s2p = (smmu_idr0 & SMMU_IDR0_S2P) != 0; + let is_supported_2level_stream_table = (smmu_idr0 & SMMU_IDR0_ST_LEVEL) != 0; + println!( + "SMMU_IDR0: {:#X}(2Level: {}, S2P: {})", + smmu_idr0, is_supported_2level_stream_table, s2p + ); + if ((smmu_idr0 & SMMU_IDR0_TTENDIAN) >> SMMU_IDR0_TTENDIAN_BITS_OFFSET) == 0b11 { + println!("Big Endian is not supported."); + return None; + } else if !s2p { + println!("Stage 2 paging is not supported."); + return None; + } else if !is_supported_2level_stream_table { + println!("2Level stream table is not supported."); + return None; + } + let smmu_idr5 = unsafe { *((base_address + SMMU_IDR5) as *const u32) }; + if (smmu_idr5 & SMMU_IDR5_GRAN4K) == 0 { + println!("4K Paging is not supported."); + return None; + } + let smmu_cr0 = unsafe { *((base_address + SMMU_CR0) as *const u32) }; + if (smmu_cr0 & SMMU_CR0_SMMUEN) != 0 { + println!("SMMU is already enabled."); + return None; + } + let mut smmu_cr1 = unsafe { *((base_address + SMMU_CR1) as *const u32) }; + smmu_cr1 |= (0b11) << SMMU_CR1_TABLE_SH_BITS_OFFSET; + unsafe { *((base_address + SMMU_CR1) as *mut u32) = smmu_cr1 }; + + let smmu_gbpa = 0b01 << SMMU_GBPA_SHCFG_BITS_OFFSET; + unsafe { *((base_address + SMMU_GBPA) as *mut u32) = smmu_gbpa }; + + /* Create STE */ + let mut ste = StreamTableEntry::new(); + ste.set_config(true, false); + ste.set_s2ir0(false, true); + ste.set_s2or0(false, true); + ste.set_s2sh0(Shareability::OuterShareable); + ste.set_s2tg(STAGE_2_PAGE_SIZE); + let vtcr_el2 = get_vtcr_el2(); + ste.set_s2t0sz(((vtcr_el2 & VTCR_EL2_T0SZ) >> VTCR_EL2_T0SZ_BITS_OFFSET) as u32); + ste.set_s2sl0(((vtcr_el2 & VTCR_EL2_SL0) >> VTCR_EL2_SL0_BITS_OFFSET) as u32); + ste.set_s2ps(((vtcr_el2 & VTCR_EL2_PS) >> VTCR_EL2_PS_BITS_OFFSET) as u8); + let vttbr_el2 = get_vttbr_el2(); + ste.set_stage2_translation_table(vttbr_el2 as usize); + ste.validate(); + + let level2_table_address = + allocate_memory(1).expect("Failed to allocate a table for SMMU"); + let level1_table_address = + allocate_memory(1).expect("Failed to allocate a table for SMMU"); + assert_eq!(core::mem::size_of::(), 64); + let level2_table = unsafe { + &mut *(level2_table_address + as *mut [StreamTableEntry; + PAGE_SIZE / core::mem::size_of::()]) + }; + for e in level2_table { + *e = ste.clone(); + } + for e in unsafe { &mut *(level1_table_address as *mut [u64; PAGE_SIZE / 8]) } { + *e = level2_table_address as u64 | 7; /* Level2 Table contains 2^(7- 1) (= 64) STEs */ + } + const MAX_STREAM_ID: u32 = (64 * (PAGE_SIZE / 8) - 1) as u32; + const TABLE_LOG2_SIZE: u32 = (MAX_STREAM_ID + 1).trailing_zeros(); + + for e in smmu_v3.get_array_of_id_mappings() { + if e.is_single_map() { + println!("Single Map StreamID: {:#X}", e.output_base); + if e.output_base > MAX_STREAM_ID { + panic!("Unsupported StreamID: {:X}", e.output_base); + } + } else { + let max_stream_id = e.output_base + e.number_of_ids - 1; + println!("StreamID: {:#X}~{:#X}", e.output_base, max_stream_id); + if max_stream_id > MAX_STREAM_ID { + panic!("Unsupported StreamID: {:X}", max_stream_id); + } + } + } + let smmu_idr1 = unsafe { *((base_address + SMMU_IDR1) as *const u32) }; + let stream_id_size = smmu_idr1 & bitmask!(5, 0); + let number_of_stream_ids = if (1 << stream_id_size) - 1 < MAX_STREAM_ID { + stream_id_size + } else { + TABLE_LOG2_SIZE + }; + println!( + "Number of Stream Ids: 2^{:#X} - 1({:#X})", + number_of_stream_ids, + 2u32.pow(number_of_stream_ids) - 1 + ); + let strtab_base_cfg = (1 << 16) | (6 << 6) | number_of_stream_ids; + unsafe { *((base_address + SMMU_STRTAB_BASE_CFG) as *mut u32) = strtab_base_cfg }; + unsafe { + *((base_address + SMMU_STRTAB_BASE) as *mut u64) = + (level1_table_address as u64) & bitmask!(51, 6) + }; + /* Enable SMMU */ + let smmu_cr0 = SMMU_CR0_SMMUEN; + unsafe { *((base_address + SMMU_CR0) as *mut u32) = smmu_cr0 }; + + let smmu_cr0ack = unsafe { *((base_address + SMMU_CR0ACK) as *const u32) }; + if (smmu_cr0ack & SMMU_CR0_SMMUEN) == 0 { + panic!("Failed to enable SMMU(SMMU_CR0ACK: {:#X})", smmu_cr0ack); + } + Some(base_address) + } else { + println!("SMMUv3 is not found"); + None + } + } + Err(AcpiError::TableNotFound) => { + println!("IORT is not found."); + None + } + Err(e) => { + println!("Failed to get IORT table: {:?}", e); + None + } + } +} diff --git a/src/hypervisor_kernel/.cargo/config b/src/hypervisor_kernel/.cargo/config index 3d8d9e4..56adf49 100644 --- a/src/hypervisor_kernel/.cargo/config +++ b/src/hypervisor_kernel/.cargo/config @@ -1,10 +1,6 @@ [build] target = "aarch64-unknown-none" +rustflags = ["-C", "target-feature=+v8.1a", "-C", "link-arg=-Tconfig/linkerscript.ld"] [unstable] build-std = ["core", "compiler_builtins"] - -[target.aarch64-unknown-none] -rustflags = [ - "-C", "link-arg=-Tconfig/linkerscript.ld", -] diff --git a/src/hypervisor_kernel/rust-toolchain.toml b/src/hypervisor_kernel/rust-toolchain.toml index a630489..b442dc7 100644 --- a/src/hypervisor_kernel/rust-toolchain.toml +++ b/src/hypervisor_kernel/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly" -targets = ["aarch64-unknown-none"] +targets = ["aarch64-unknown-none"] diff --git a/src/hypervisor_kernel/src/emulation.rs b/src/hypervisor_kernel/src/emulation.rs index 34ef685..26c0528 100644 --- a/src/hypervisor_kernel/src/emulation.rs +++ b/src/hypervisor_kernel/src/emulation.rs @@ -4,9 +4,10 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -/// -/// A64 Instructions' Emulator -/// +//! +//! A64 Instructions' Emulator +//! + mod load; mod store; diff --git a/src/hypervisor_kernel/src/emulation/load.rs b/src/hypervisor_kernel/src/emulation/load.rs index 45f48bc..a2459c2 100644 --- a/src/hypervisor_kernel/src/emulation/load.rs +++ b/src/hypervisor_kernel/src/emulation/load.rs @@ -4,11 +4,12 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -/// -/// A64 Load Instructions' Emulator -/// -/// Supported: ldr, ldp (except Atomic, SIMD) -/// +//! +//! A64 Load Instructions' Emulator +//! +//! Supported: ldr, ldp (except Atomic, SIMD) +//! + use super::{ advance_elr_el2, faulting_virtual_address_to_intermediate_physical_address, get_register_reference_mut, write_back_index_register_imm7, write_back_index_register_imm9, diff --git a/src/hypervisor_kernel/src/emulation/store.rs b/src/hypervisor_kernel/src/emulation/store.rs index 41a4ac5..40c0e42 100644 --- a/src/hypervisor_kernel/src/emulation/store.rs +++ b/src/hypervisor_kernel/src/emulation/store.rs @@ -4,11 +4,12 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -/// -/// A64 Store Instructions' Emulator -/// -/// Supported: str, stp (except Atomic, SIMD) -/// +//! +//! A64 Store Instructions' Emulator +//! +//! Supported: str, stp (except Atomic, SIMD) +//! + use super::{ advance_elr_el2, faulting_virtual_address_to_intermediate_physical_address, get_register_reference_mut, write_back_index_register_imm7, write_back_index_register_imm9, diff --git a/src/hypervisor_kernel/src/main.rs b/src/hypervisor_kernel/src/main.rs index a626d3a..2557778 100644 --- a/src/hypervisor_kernel/src/main.rs +++ b/src/hypervisor_kernel/src/main.rs @@ -24,11 +24,12 @@ mod paging; mod panic; mod pci; mod psci; +mod smmu; -use crate::pci::init_pci; use crate::psci::{handle_psci_call, PsciFunctionId}; use crate::serial_port::DEFAULT_SERIAL_PORT; +use common::acpi; use common::cpu::secure_monitor_call; use common::{SystemInformation, ALLOC_SIZE, PAGE_SIZE}; @@ -97,9 +98,17 @@ fn hypervisor_main(system_information: &mut SystemInformation) { println!("Hello,world from Hypervisor Kernel!!"); if let Some(ecam_info) = &system_information.ecam_info { - init_pci(ecam_info.address, ecam_info.start_bus, ecam_info.end_bus); + pci::init_pci(ecam_info.address, ecam_info.start_bus, ecam_info.end_bus); } - unsafe { asm!("adr {:x}, vector_table_el2", out(reg)system_information.vbar_el2 ) }; + if let Some(smmu_base_address) = system_information.smmu_v3_base_address { + smmu::init_smmu( + smmu_base_address, + system_information + .acpi_rsdp_address + .and_then(|rsdp| acpi::get_acpi_table(rsdp, &acpi::iort::IORT::SIGNATURE).ok()), + ); + } + unsafe { asm!("adr {:x}, vector_table_el2", out(reg) system_information.vbar_el2 ) }; return; } diff --git a/src/hypervisor_kernel/src/memory_hook.rs b/src/hypervisor_kernel/src/memory_hook.rs index 013f23f..e8e1260 100644 --- a/src/hypervisor_kernel/src/memory_hook.rs +++ b/src/hypervisor_kernel/src/memory_hook.rs @@ -4,9 +4,10 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -/// -/// Memory Hook Handler -/// +//! +//! Memory Hook Handler +//! + use crate::StoredRegisters; #[allow(dead_code)] diff --git a/src/hypervisor_kernel/src/pci.rs b/src/hypervisor_kernel/src/pci.rs index 1f54aa4..a95872f 100644 --- a/src/hypervisor_kernel/src/pci.rs +++ b/src/hypervisor_kernel/src/pci.rs @@ -4,9 +4,10 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -/// -/// PCI -/// +//! +//! PCI +//! + use crate::drivers; pub fn init_pci(ecam_address: usize, start_bus_number: u8, end_bus_number: u8) { diff --git a/src/hypervisor_kernel/src/smmu.rs b/src/hypervisor_kernel/src/smmu.rs new file mode 100644 index 0000000..2f978ba --- /dev/null +++ b/src/hypervisor_kernel/src/smmu.rs @@ -0,0 +1,113 @@ +// Copyright (c) 2022 RIKEN +// All rights reserved. +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +//! +//! System Memory Management Unit +//! + +use crate::memory_hook::{ + add_memory_load_hook_handler, add_memory_store_hook_handler, LoadAccessHandlerEntry, + LoadHookResult, StoreAccessHandlerEntry, StoreHookResult, +}; +use crate::paging::add_memory_access_trap; +use crate::StoredRegisters; + +use common::smmu::{SMMU_IDR0, SMMU_IDR0_HYP, SMMU_IDR0_S1P, SMMU_IDR0_S2P, SMMU_MEMORY_MAP_SIZE}; +use common::{STAGE_2_PAGE_MASK, STAGE_2_PAGE_SIZE}; + +static mut SMMU_BASE_ADDRESS: usize = 0; + +/// SMMU領域の保護の設定を行います +/// +/// SMMUのMMIO領域をEL1からアクセス不能にします。 +/// またEL1からはStage1&2が使用不能のSMMUであるかのように見せます。 +/// IORTのアドレスが渡された場合はEL1からIORTのエントリがゼロクリアされた状態に見えるように設定します。 +/// +/// # Arguments +/// base_address: SMMUのベースアドレス +/// iort_address: IORTエントリのアドレス(Optional) +/// +pub fn init_smmu(base_address: usize, iort_address: Option) { + /* base_address must be mapped, accessible, and enabled. */ + unsafe { SMMU_BASE_ADDRESS = base_address }; + + add_memory_access_trap(base_address, SMMU_MEMORY_MAP_SIZE, false, false) + .expect("Failed to trap the memory access to SMMU"); + + add_memory_load_hook_handler(LoadAccessHandlerEntry::new( + base_address, + SMMU_MEMORY_MAP_SIZE, + smmu_registers_load_handler, + )) + .expect("Failed to add the load handler"); + add_memory_store_hook_handler(StoreAccessHandlerEntry::new( + base_address, + SMMU_MEMORY_MAP_SIZE, + smmu_registers_store_handler, + )) + .expect("Failed to add the store handler"); + + if let Some(iort_address) = iort_address { + let iort_length = unsafe { *((iort_address + 4) as *const u32) } as usize; + let aligned_iort_address = iort_address & STAGE_2_PAGE_MASK; + let aligned_iort_size = (((iort_length + (iort_address - aligned_iort_address)) - 1) + & STAGE_2_PAGE_MASK) + + STAGE_2_PAGE_SIZE; + add_memory_access_trap(aligned_iort_address, aligned_iort_size, false, true) + .expect("Failed to trap the IORT area."); + add_memory_load_hook_handler(LoadAccessHandlerEntry::new( + iort_address, + iort_length, + iort_load_handler, + )) + .expect("Failed to add the load handler"); + println!( + "Delete IORT(Address: {:#X}, Size: {:#X}) from EL1.", + iort_address, iort_length + ); + } +} + +fn smmu_registers_load_handler( + accessing_memory_address: usize, + _stored_registers: &mut StoredRegisters, + _access_size: u8, + _is_64bit_register: bool, + _is_sign_extend_required: bool, +) -> Result { + let register_offset = accessing_memory_address - unsafe { SMMU_BASE_ADDRESS }; + println!("SMMU Load Access Handler: Offset: {:#X}", register_offset); + match register_offset { + SMMU_IDR0 => { + println!("SMMU_IDR0"); + Ok(LoadHookResult::Data( + (unsafe { *(accessing_memory_address as *const u32) } + & (!(SMMU_IDR0_S2P | SMMU_IDR0_S1P | SMMU_IDR0_HYP))) as u64, + )) + } + _ => Ok(LoadHookResult::PassThrough), + } +} + +fn smmu_registers_store_handler( + _accessing_memory_address: usize, + _stored_registers: &mut StoredRegisters, + _access_size: u8, + _data: u64, +) -> Result { + println!("SMMU Store Access Handler"); + return Ok(StoreHookResult::Cancel); +} + +fn iort_load_handler( + _accessing_memory_address: usize, + _stored_registers: &mut StoredRegisters, + _access_size: u8, + _is_64bit_register: bool, + _is_sign_extend_required: bool, +) -> Result { + return Ok(LoadHookResult::Data(0)); +}