diff --git a/Cargo.lock b/Cargo.lock index 8f40b3066..371132372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,6 +837,7 @@ dependencies = [ "packit", "syscall", "test", + "virtio-drivers", ] [[package]] @@ -969,6 +970,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtio-drivers" +version = "0.7.1" +source = "git+https://github.com/osteffenrh/virtio-drivers?branch=virtio-pr#942fea8ce907f66af5e347192f2241c1478b50e4" +dependencies = [ + "bitflags 2.4.2", + "log", + "zerocopy 0.7.32", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 3031edf9c..500dd3203 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -33,6 +33,7 @@ intrusive-collections.workspace = true log = { workspace = true, features = ["max_level_info", "release_max_level_info"] } packit.workspace = true libmstpm = { workspace = true, optional = true } +virtio-drivers = { git = "https://github.com/osteffenrh/virtio-drivers", branch = "virtio-pr" } [target."x86_64-unknown-none".dev-dependencies] test.workspace = true diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 9dacc338a..dd4dfa5f0 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -42,6 +42,8 @@ pub mod utils; #[cfg(all(feature = "mstpm", not(test)))] pub mod vtpm; +pub mod virtio; + #[test] fn test_nop() {} diff --git a/kernel/src/svsm.rs b/kernel/src/svsm.rs index 486d43d1b..3f4abe828 100755 --- a/kernel/src/svsm.rs +++ b/kernel/src/svsm.rs @@ -453,6 +453,13 @@ pub extern "C" fn svsm_main() { #[cfg(all(feature = "mstpm", not(test)))] vtpm_init().expect("vTPM failed to initialize"); + { + // Virtio drivers experiments + log::info!("Virtio test trace"); + use svsm::virtio::test_mmio; + test_mmio(); + } + virt_log_usage(); if config.should_launch_fw() { diff --git a/kernel/src/virtio/mod.rs b/kernel/src/virtio/mod.rs new file mode 100644 index 000000000..c13cd715f --- /dev/null +++ b/kernel/src/virtio/mod.rs @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2024 Red Hat, Inc. +// +// Author: Oliver Steffen + +use core::ptr::NonNull; + +use virtio_drivers::{ + device::blk::{VirtIOBlk, SECTOR_SIZE}, + transport::{ + mmio::{MmioTransport, VirtIOHeader}, + DeviceType, Transport, + }, +}; + +use crate::{ + address::PhysAddr, + cpu, + mm::{alloc::*, page_visibility::*, *}, +}; + +struct SvsmHal; + +/// Implementation of virtio-drivers MMIO hardware abstraction for AMD SEV-SNP +/// in the Coconut-SVSM context. Due to missing #VC handler for MMIO, use ghcb exits +/// instead. +unsafe impl virtio_drivers::Hal for SvsmHal { + fn dma_alloc( + pages: usize, + _direction: virtio_drivers::BufferDirection, + ) -> (virtio_drivers::PhysAddr, NonNull) { + // TODO: allow more than one page. + // This currently works, becasue in "modern" virtio mode the crate only allocates + // one page at a time. + assert!(pages == 1); + + let mem = allocate_zeroed_page().expect("Error allocating page"); + make_page_shared(mem).expect("Error making page shared"); + + (virt_to_phys(mem).into(), unsafe { + NonNull::::new_unchecked(mem.as_mut_ptr()) + }) + } + + unsafe fn dma_dealloc( + _paddr: virtio_drivers::PhysAddr, + vaddr: NonNull, + pages: usize, + ) -> i32 { + //TODO: allow more than one page + assert!(pages == 1); + + make_page_private(vaddr.as_ptr().into()).expect("Error making page private"); + free_page(vaddr.as_ptr().into()); + + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: virtio_drivers::PhysAddr, _size: usize) -> NonNull { + NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()) + .expect("Error getting VirtAddr from PhysAddr") + } + + unsafe fn share( + buffer: NonNull<[u8]>, + direction: virtio_drivers::BufferDirection, + ) -> virtio_drivers::PhysAddr { + // TODO: allow more than one page + assert!(buffer.len() <= PAGE_SIZE); + + let mem = allocate_zeroed_page().expect("Error allocating zeroed page"); + let phys = virt_to_phys(mem); + + make_page_shared(mem).expect("Error making page shared"); + + if direction == virtio_drivers::BufferDirection::DriverToDevice { + unsafe { + let src = buffer.as_ptr().cast::(); + let dst = mem.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(src, dst, buffer.len()); + } + } + + phys.into() + } + + unsafe fn unshare( + paddr: virtio_drivers::PhysAddr, + buffer: NonNull<[u8]>, + direction: virtio_drivers::BufferDirection, + ) { + assert!(buffer.len() <= PAGE_SIZE); + + let vaddr = phys_to_virt(paddr.into()); + + if direction == virtio_drivers::BufferDirection::DeviceToDriver { + unsafe { + let dst = buffer.as_ptr().cast::(); + let src = vaddr.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(src, dst, buffer.len()); + } + } + make_page_private(vaddr).expect("Error making page private"); + + free_page(phys_to_virt(paddr.into())); + } + + unsafe fn mmio_read(src: &T) -> T { + let paddr = PhysAddr::from((src as *const T) as u64); + + cpu::ghcb::current_ghcb() + .mmio_read::(paddr) + .expect("GHCB MMIO Read failed") + } + + unsafe fn mmio_write(dst: &mut T, v: T) { + let paddr = PhysAddr::from((dst as *mut T) as u64); + + cpu::ghcb::current_ghcb() + .mmio_write::(paddr, &v) + .expect("GHCB MMIO Write failed"); + } +} + +/// virtio-blk via mmio demo. +pub fn test_mmio() { + static MMIO_BASE: u64 = 0xfef03000; // Hard-coded in Qemu + + // Test code below taken from virtio-drivers aarch64 example. + let header = NonNull::new(MMIO_BASE as *mut VirtIOHeader).unwrap(); + match unsafe { MmioTransport::::new(header) } { + Err(e) => log::warn!( + "Error creating VirtIO MMIO transport at {:016x}: {}", + MMIO_BASE, + e + ), + Ok(transport) => { + log::info!( + target: "virtio", + "Detected virtio MMIO device with vendor id {:#X}, device type {:?}, version {:?}", + transport.vendor_id(), + transport.device_type(), + transport.version(), + ); + match transport.device_type() { + DeviceType::Block => virtio_blk(transport), + t => log::warn!(target: "virtio", "Unrecognized virtio device: {:?}", t), + } + } + } + + log::info!(target: "virtio", "Virtio test end"); +} + +/// Run some basic smoke tests on the virtio-blk device +fn virtio_blk(transport: T) { + let mut blk = VirtIOBlk::::new(transport).expect("Failed to create blk driver"); + assert!(!blk.readonly()); + + // IO Tests copied from virtio-drivers example + { + log::info!("Write+Read Test Start"); + let mut input = [0xffu8; 512]; + let mut output = [0; 512]; + for i in 0..32 { + for x in input.iter_mut() { + *x = i as u8; + } + blk.write_blocks(i, &input).expect("failed to write"); + blk.read_blocks(i, &mut output).expect("failed to read"); + assert_eq!(input, output); + } + log::info!("Write+Read Test End"); + } + + // Write Speed Benchmark. Requires external time measurement. + { + log::info!("Write Benchmark Start"); + const MAX_SIZE: usize = 4096; + let input = [0xffu8; MAX_SIZE]; + let capacity = blk.capacity() as usize * SECTOR_SIZE; + const REWRITES: usize = 1; + + for write_size in [512, 4096] { + assert!(write_size <= MAX_SIZE); + let n_blocks = capacity / write_size; + + log::info!("virtio-blk start write. Block size = {}", write_size); + for _ in 0..REWRITES { + for block in 0..n_blocks { + blk.write_blocks(block * write_size / SECTOR_SIZE, &input[0..write_size]) + .expect("Write Error"); + } + } + log::info!( + "virtio-blk end write. Block size = {}, Total bytes = {}", + write_size, + write_size * n_blocks * REWRITES + ); + } + log::info!("Write Benchmark End"); + } +}