├── .github └── workflows │ └── main.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── aarch64 │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── Makefile │ ├── build.rs │ ├── crosvm.ld │ ├── qemu.ld │ ├── rust-toolchain.toml │ └── src │ │ ├── exceptions.rs │ │ ├── hal.rs │ │ ├── logger.rs │ │ ├── main.rs │ │ └── uart8250.rs ├── riscv │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── Makefile │ ├── linker32.ld │ ├── linker64.ld │ ├── music_44100Hz_u8_stereo.raw │ ├── rust-toolchain.toml │ ├── src │ │ ├── main.rs │ │ ├── tcp.rs │ │ └── virtio_impl.rs │ └── virtio-test-gpu.png ├── vsock_server │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs └── x86_64 │ ├── .cargo │ └── config.toml │ ├── Cargo.toml │ ├── Makefile │ ├── linker.ld │ ├── rust-toolchain.toml │ └── src │ ├── boot.rs │ ├── hal.rs │ ├── heap.rs │ ├── logger.rs │ ├── main.rs │ ├── multiboot.S │ ├── tcp.rs │ └── trap.rs ├── rust-toolchain.toml └── src ├── config.rs ├── device ├── blk.rs ├── common.rs ├── console.rs ├── console │ └── embedded_io.rs ├── gpu.rs ├── input.rs ├── mod.rs ├── net │ ├── dev.rs │ ├── dev_raw.rs │ ├── mod.rs │ └── net_buf.rs ├── rng.rs ├── socket │ ├── connectionmanager.rs │ ├── error.rs │ ├── mod.rs │ ├── protocol.rs │ └── vsock.rs ├── sound.rs └── sound │ └── fake.rs ├── embedded_io.rs ├── hal.rs ├── hal └── fake.rs ├── lib.rs ├── queue.rs ├── queue └── owning.rs └── transport ├── fake.rs ├── mmio.rs ├── mod.rs ├── pci.rs ├── pci └── bus.rs ├── some.rs ├── x86_64.rs └── x86_64 ├── cam.rs └── hypercalls.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Check code format 14 | uses: actions-rs/cargo@v1 15 | with: 16 | command: fmt 17 | args: --all -- --check 18 | - name: Clippy 19 | uses: actions-rs/clippy-check@v1 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | build: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Build with no features 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: build 31 | args: --no-default-features 32 | - name: Build with all features 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: build 36 | args: --all-features 37 | - name: Docs 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: doc 41 | - name: Test with no features 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: --no-default-features 46 | - name: Test with all features 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: test 50 | args: --all-features 51 | 52 | examples: 53 | runs-on: ubuntu-24.04 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | example: 58 | - aarch64 59 | - riscv 60 | - x86_64 61 | include: 62 | - example: aarch64 63 | packages: qemu-system-arm gcc-aarch64-linux-gnu 64 | - example: riscv 65 | packages: qemu-system-misc 66 | - example: x86_64 67 | packages: qemu-system-x86 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Install QEMU 71 | run: sudo apt update && sudo apt install ${{ matrix.packages }} && sudo chmod 666 /dev/vhost-vsock 72 | - name: Check code format 73 | working-directory: examples/${{ matrix.example }} 74 | run: cargo fmt --all -- --check 75 | - name: Build 76 | working-directory: examples/${{ matrix.example }} 77 | run: make kernel 78 | - name: Run 79 | working-directory: examples/${{ matrix.example }} 80 | run: QEMU_ARGS="-display none -audiodev none,id=audio0" make qemu accel="off" 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtio-drivers" 3 | version = "0.11.0" 4 | license = "MIT" 5 | authors = [ 6 | "Jiajie Chen ", 7 | "Runji Wang ", 8 | "Yuekai Jia ", 9 | "Andrew Walbran ", 10 | ] 11 | edition = "2021" 12 | description = "VirtIO guest drivers." 13 | repository = "https://github.com/rcore-os/virtio-drivers" 14 | keywords = ["virtio"] 15 | categories = ["hardware-support", "no-std"] 16 | 17 | [dependencies] 18 | log = "0.4.27" 19 | bitflags = "2.9.0" 20 | enumn = "0.1.14" 21 | embedded-io = { version = "0.6.1", optional = true } 22 | safe-mmio = "0.2.4" 23 | thiserror = { version = "2.0.12", default-features = false } 24 | zerocopy = { version = "0.8.24", features = ["derive"] } 25 | 26 | [features] 27 | default = ["alloc", "embedded-io"] 28 | alloc = ["zerocopy/alloc"] 29 | embedded-io = ["dep:embedded-io"] 30 | 31 | [dev-dependencies] 32 | zerocopy = { version = "0.8.24", features = ["alloc"] } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 rCore Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VirtIO-drivers-rs 2 | 3 | [![crates.io page](https://img.shields.io/crates/v/virtio-drivers.svg)](https://crates.io/crates/virtio-drivers) 4 | [![docs.rs page](https://docs.rs/virtio-drivers/badge.svg)](https://docs.rs/virtio-drivers) 5 | [![CI](https://github.com/rcore-os/virtio-drivers/workflows/CI/badge.svg?branch=master)](https://github.com/rcore-os/virtio-drivers/actions) 6 | 7 | VirtIO guest drivers in Rust. For **no_std** environment. 8 | 9 | ## Support status 10 | 11 | ### Device types 12 | 13 | | Device | Supported | 14 | | ------- | --------- | 15 | | Block | ✅ | 16 | | Net | ✅ | 17 | | GPU | ✅ | 18 | | Input | ✅ | 19 | | Console | ✅ | 20 | | Socket | ✅ | 21 | | Sound | ✅ | 22 | | RNG | ✅ | 23 | | ... | ❌ | 24 | 25 | ### Transports 26 | 27 | | Transport | Supported | | 28 | | ----------- | --------- | ------------------------------------------------- | 29 | | Legacy MMIO | ✅ | version 1 | 30 | | MMIO | ✅ | version 2 | 31 | | PCI | ✅ | Memory-mapped CAM only, e.g. aarch64 or PCIe ECAM | 32 | 33 | ### Device-independent features 34 | 35 | | Feature flag | Supported | | 36 | | ---------------------------- | --------- | --------------------------------------- | 37 | | `VIRTIO_F_INDIRECT_DESC` | ✅ | Indirect descriptors | 38 | | `VIRTIO_F_EVENT_IDX` | ✅ | `avail_event` and `used_event` fields | 39 | | `VIRTIO_F_VERSION_1` | TODO | VirtIO version 1 compliance | 40 | | `VIRTIO_F_ACCESS_PLATFORM` | ❌ | Limited device access to memory | 41 | | `VIRTIO_F_RING_PACKED` | ❌ | Packed virtqueue layout | 42 | | `VIRTIO_F_IN_ORDER` | ❌ | Optimisations for in-order buffer usage | 43 | | `VIRTIO_F_ORDER_PLATFORM` | ❌ | Platform ordering for memory access | 44 | | `VIRTIO_F_SR_IOV` | ❌ | Single root I/O virtualization | 45 | | `VIRTIO_F_NOTIFICATION_DATA` | ❌ | Extra data in device notifications | 46 | 47 | ## Examples & Tests 48 | 49 | ### [x86_64](./examples/x86_64) 50 | 51 | ```bash 52 | cd examples/x86_64 53 | make qemu 54 | ``` 55 | 56 | ### [aarch64](./examples/aarch64) 57 | 58 | ```bash 59 | cd examples/aarch64 60 | make qemu 61 | ``` 62 | 63 | ### [RISCV](./examples/riscv) 64 | 65 | ```bash 66 | cd examples/riscv 67 | make qemu 68 | ``` 69 | 70 | You will see device info & GUI Window in qemu. 71 | 72 | 73 | -------------------------------------------------------------------------------- /examples/aarch64/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "aarch64-unknown-none" 3 | -------------------------------------------------------------------------------- /examples/aarch64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aarch64" 3 | version = "0.1.0" 4 | authors = ["Andrew Walbran "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | aarch64-rt = "0.1.0" 9 | aarch64-paging = "0.8.0" 10 | arm-pl011-uart = "0.3.0" 11 | buddy_system_allocator = "0.11.0" 12 | flat_device_tree = "3.1.1" 13 | log = "0.4.27" 14 | safe-mmio = "0.2.4" 15 | smccc = "0.2.0" 16 | spin = "0.10.0" 17 | virtio-drivers = { path = "../.." } 18 | -------------------------------------------------------------------------------- /examples/aarch64/Makefile: -------------------------------------------------------------------------------- 1 | target := aarch64-unknown-none 2 | mode := release 3 | kernel := target/$(target)/$(mode)/aarch64 4 | kernel_qemu_bin := target/$(target)/$(mode)/aarch64_qemu.bin 5 | kernel_crosvm_bin := target/$(target)/$(mode)/aarch64_crosvm.bin 6 | img := target/$(target)/$(mode)/img 7 | vsock_server_path := ../vsock_server 8 | vsock_server_bin := $(vsock_server_path)/target/$(mode)/vsock_server 9 | 10 | sysroot := $(shell rustc --print sysroot) 11 | objdump := $(shell find $(sysroot) -name llvm-objdump) --arch-name=aarch64 12 | objcopy := $(shell find $(sysroot) -name llvm-objcopy) 13 | 14 | BUILD_ARGS += --target $(target) 15 | ifeq ($(mode), release) 16 | BUILD_ARGS += --release 17 | endif 18 | 19 | VSOCK_BUILD_ARGS = 20 | ifeq ($(mode), release) 21 | VSOCK_BUILD_ARGS += --release 22 | endif 23 | 24 | .PHONY: kernel clean qemu run env 25 | 26 | env: 27 | rustup component add llvm-tools-preview rustfmt 28 | rustup target add $(target) 29 | 30 | kernel_qemu: 31 | cargo clean 32 | cargo build $(BUILD_ARGS) --config 'build.rustflags="--cfg platform=\"qemu\""' 33 | 34 | kernel_crosvm: 35 | cargo clean 36 | cargo build $(BUILD_ARGS) --config 'build.rustflags="--cfg platform=\"crosvm\""' 37 | 38 | $(kernel_qemu_bin): kernel_qemu 39 | aarch64-linux-gnu-objcopy -O binary $(kernel) $(kernel_qemu_bin) 40 | 41 | $(kernel_crosvm_bin): kernel_crosvm 42 | aarch64-linux-gnu-objcopy -O binary $(kernel) $(kernel_crosvm_bin) 43 | 44 | $(vsock_server_bin): 45 | (cd $(vsock_server_path) && cargo build $(VSOCK_BUILD_ARGS)) 46 | 47 | asm: kernel 48 | $(objdump) -d $(kernel) | less 49 | 50 | sym: kernel 51 | $(objdump) -t $(kernel) | less 52 | 53 | header: kernel 54 | $(objdump) -x $(kernel) | less 55 | 56 | clean: 57 | cargo clean 58 | 59 | # This target is used to test the vsock driver manually. See vsock_server/README.md 60 | # for more information. 61 | qemu-vsock: $(kernel_qemu_bin) $(img) 62 | qemu-system-aarch64 \ 63 | $(QEMU_ARGS) \ 64 | -machine virt \ 65 | -cpu max \ 66 | -serial chardev:char0 \ 67 | -kernel $(kernel_qemu_bin) \ 68 | -global virtio-mmio.force-legacy=false \ 69 | -nic none \ 70 | -drive file=$(img),if=none,format=raw,id=x0 \ 71 | -device vhost-vsock-device,id=virtiosocket0,guest-cid=102 \ 72 | -chardev stdio,id=char0,mux=on 73 | 74 | qemu: $(kernel_qemu_bin) $(img) $(vsock_server_bin) 75 | $(vsock_server_bin) & 76 | qemu-system-aarch64 \ 77 | $(QEMU_ARGS) \ 78 | -machine virt \ 79 | -cpu max \ 80 | -serial chardev:char0 \ 81 | -kernel $(kernel_qemu_bin) \ 82 | -global virtio-mmio.force-legacy=false \ 83 | -nic none \ 84 | -drive file=$(img),if=none,format=raw,id=x0 \ 85 | -device vhost-vsock-device,id=virtiosocket0,guest-cid=102 \ 86 | -device virtio-blk-device,drive=x0 \ 87 | -device virtio-rng-device \ 88 | -device virtio-gpu-device \ 89 | -device virtio-serial,id=virtio-serial0 \ 90 | -chardev stdio,id=char0,mux=on \ 91 | -device virtconsole,chardev=char0 92 | 93 | qemu-pci: $(kernel_qemu_bin) $(img) 94 | $(vsock_server_bin) & 95 | qemu-system-aarch64 \ 96 | $(QEMU_ARGS) \ 97 | -machine virt \ 98 | -cpu max \ 99 | -serial chardev:char0 \ 100 | -kernel $(kernel_qemu_bin) \ 101 | -nic none \ 102 | -drive file=$(img),if=none,format=raw,id=x0 \ 103 | -device vhost-vsock-pci,id=virtiosocket0,guest-cid=103 \ 104 | -device virtio-blk-pci,drive=x0 \ 105 | -device virtio-rng-pci \ 106 | -device virtio-gpu-pci \ 107 | -device virtio-serial,id=virtio-serial0 \ 108 | -chardev stdio,id=char0,mux=on \ 109 | -device virtconsole,chardev=char0 110 | 111 | crosvm: $(kernel_crosvm_bin) $(img) 112 | adb shell 'mkdir -p /data/local/tmp/virt_raw' 113 | adb push $(kernel_crosvm_bin) /data/local/tmp/virt_raw/aarch64_example 114 | adb push $(img) /data/local/tmp/virt_raw/disk_img 115 | adb shell "/apex/com.android.virt/bin/crosvm --log-level=info --extended-status run --disable-sandbox --serial=stdout,hardware=serial,num=1 --rwdisk=/data/local/tmp/virt_raw/disk_img --bios=/data/local/tmp/virt_raw/aarch64_example" 116 | 117 | $(img): 118 | dd if=/dev/zero of=$@ bs=512 count=32 119 | 120 | run: qemu 121 | -------------------------------------------------------------------------------- /examples/aarch64/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | const PLATFORMS: [&str; 2] = ["crosvm", "qemu"]; 4 | 5 | fn main() { 6 | println!( 7 | "cargo::rustc-check-cfg=cfg(platform, values(\"{}\"))", 8 | PLATFORMS.join("\", \"") 9 | ); 10 | 11 | let platform = env::var("CARGO_CFG_PLATFORM").expect("Missing platform name"); 12 | assert!( 13 | PLATFORMS.contains(&platform.as_str()), 14 | "Unexpected platform name {:?}. Supported platforms: {:?}", 15 | platform, 16 | PLATFORMS, 17 | ); 18 | println!("cargo:rustc-link-arg=-Timage.ld"); 19 | println!("cargo:rustc-link-arg=-T{platform}.ld"); 20 | println!("cargo:rerun-if-changed={platform}.ld"); 21 | } 22 | -------------------------------------------------------------------------------- /examples/aarch64/crosvm.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | image : ORIGIN = 0x80200000, LENGTH = 32M 4 | } 5 | -------------------------------------------------------------------------------- /examples/aarch64/qemu.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | image : ORIGIN = 0x40080000, LENGTH = 32M 4 | } 5 | -------------------------------------------------------------------------------- /examples/aarch64/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "llvm-tools"] 4 | targets = ["aarch64-unknown-none"] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /examples/aarch64/src/exceptions.rs: -------------------------------------------------------------------------------- 1 | //! Exception handlers. 2 | 3 | use core::arch::asm; 4 | use log::error; 5 | use smccc::{psci::system_off, Hvc}; 6 | 7 | #[no_mangle] 8 | extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { 9 | error!("sync_exception_current"); 10 | print_esr(); 11 | system_off::().unwrap(); 12 | } 13 | 14 | #[no_mangle] 15 | extern "C" fn irq_current(_elr: u64, _spsr: u64) { 16 | error!("irq_current"); 17 | system_off::().unwrap(); 18 | } 19 | 20 | #[no_mangle] 21 | extern "C" fn fiq_current(_elr: u64, _spsr: u64) { 22 | error!("fiq_current"); 23 | system_off::().unwrap(); 24 | } 25 | 26 | #[no_mangle] 27 | extern "C" fn serr_current(_elr: u64, _spsr: u64) { 28 | error!("serr_current"); 29 | print_esr(); 30 | system_off::().unwrap(); 31 | } 32 | 33 | #[no_mangle] 34 | extern "C" fn sync_lower(_elr: u64, _spsr: u64) { 35 | error!("sync_lower"); 36 | print_esr(); 37 | system_off::().unwrap(); 38 | } 39 | 40 | #[no_mangle] 41 | extern "C" fn irq_lower(_elr: u64, _spsr: u64) { 42 | error!("irq_lower"); 43 | system_off::().unwrap(); 44 | } 45 | 46 | #[no_mangle] 47 | extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { 48 | error!("fiq_lower"); 49 | system_off::().unwrap(); 50 | } 51 | 52 | #[no_mangle] 53 | extern "C" fn serr_lower(_elr: u64, _spsr: u64) { 54 | error!("serr_lower"); 55 | print_esr(); 56 | system_off::().unwrap(); 57 | } 58 | 59 | #[inline] 60 | fn print_esr() { 61 | let mut esr: u64; 62 | unsafe { 63 | asm!("mrs {esr}, esr_el1", esr = out(reg) esr); 64 | } 65 | log::error!("esr={:#08x}", esr); 66 | } 67 | -------------------------------------------------------------------------------- /examples/aarch64/src/hal.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error}; 2 | use core::{alloc::Layout, ptr::NonNull}; 3 | use log::trace; 4 | use virtio_drivers::{BufferDirection, Hal, PhysAddr, PAGE_SIZE}; 5 | 6 | pub struct HalImpl; 7 | 8 | unsafe impl Hal for HalImpl { 9 | fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { 10 | let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap(); 11 | // Safe because the layout has a non-zero size. 12 | let vaddr = unsafe { alloc_zeroed(layout) }; 13 | let vaddr = if let Some(vaddr) = NonNull::new(vaddr) { 14 | vaddr 15 | } else { 16 | handle_alloc_error(layout) 17 | }; 18 | let paddr = virt_to_phys(vaddr.as_ptr() as _); 19 | trace!("alloc DMA: paddr={:#x}, pages={}", paddr, pages); 20 | (paddr, vaddr) 21 | } 22 | 23 | unsafe fn dma_dealloc(paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { 24 | trace!("dealloc DMA: paddr={:#x}, pages={}", paddr, pages); 25 | let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap(); 26 | // Safe because the memory was allocated by `dma_alloc` above using the same allocator, and 27 | // the layout is the same as was used then. 28 | unsafe { 29 | dealloc(vaddr.as_ptr(), layout); 30 | } 31 | 0 32 | } 33 | 34 | unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { 35 | NonNull::new(paddr as _).unwrap() 36 | } 37 | 38 | unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { 39 | let vaddr = buffer.as_ptr() as *mut u8 as usize; 40 | // Nothing to do, as the host already has access to all memory. 41 | virt_to_phys(vaddr) 42 | } 43 | 44 | unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { 45 | // Nothing to do, as the host already has access to all memory and we didn't copy the buffer 46 | // anywhere else. 47 | } 48 | } 49 | 50 | fn virt_to_phys(vaddr: usize) -> PhysAddr { 51 | vaddr 52 | } 53 | -------------------------------------------------------------------------------- /examples/aarch64/src/logger.rs: -------------------------------------------------------------------------------- 1 | //! Log implementation using the UART. 2 | 3 | use crate::uart::Uart; 4 | use core::fmt::Write; 5 | use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; 6 | use spin::mutex::SpinMutex; 7 | 8 | static LOGGER: Logger = Logger { 9 | uart: SpinMutex::new(None), 10 | }; 11 | 12 | struct Logger { 13 | uart: SpinMutex>>, 14 | } 15 | 16 | /// Initialises UART logger. 17 | pub fn init(uart: Uart<'static>, max_level: LevelFilter) -> Result<(), SetLoggerError> { 18 | LOGGER.uart.lock().replace(uart); 19 | 20 | log::set_logger(&LOGGER)?; 21 | log::set_max_level(max_level); 22 | Ok(()) 23 | } 24 | 25 | impl Log for Logger { 26 | fn enabled(&self, _metadata: &Metadata) -> bool { 27 | true 28 | } 29 | 30 | fn log(&self, record: &Record) { 31 | writeln!( 32 | LOGGER.uart.lock().as_mut().unwrap(), 33 | "[{}] {}", 34 | record.level(), 35 | record.args() 36 | ) 37 | .unwrap(); 38 | } 39 | 40 | fn flush(&self) {} 41 | } 42 | -------------------------------------------------------------------------------- /examples/aarch64/src/uart8250.rs: -------------------------------------------------------------------------------- 1 | //! Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250 2 | //! provided by crosvm, and won't work with real hardware. 3 | 4 | use core::fmt::{self, Write}; 5 | use safe_mmio::{fields::WriteOnly, UniqueMmioPointer}; 6 | 7 | /// Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250 8 | /// provided by crosvm, and won't work with real hardware. 9 | pub struct Uart<'a> { 10 | base_address: UniqueMmioPointer<'a, WriteOnly>, 11 | } 12 | 13 | impl<'a> Uart<'a> { 14 | /// Constructs a new instance of the UART driver for a device at the given base address. 15 | pub fn new(base_address: UniqueMmioPointer<'a, WriteOnly>) -> Self { 16 | Self { base_address } 17 | } 18 | 19 | /// Writes a single byte to the UART. 20 | pub fn write_byte(&mut self, byte: u8) { 21 | self.base_address.write(byte); 22 | } 23 | } 24 | 25 | impl Write for Uart<'_> { 26 | fn write_str(&mut self, s: &str) -> fmt::Result { 27 | for c in s.as_bytes() { 28 | self.write_byte(*c); 29 | } 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/riscv/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv64imac-unknown-none-elf" 3 | 4 | [target.riscv32imac-unknown-none-elf] 5 | rustflags = [ 6 | "-C", "link-arg=-Tlinker32.ld", 7 | ] 8 | 9 | [target.riscv64imac-unknown-none-elf] 10 | rustflags = [ 11 | "-C", "link-arg=-Tlinker64.ld", 12 | ] 13 | -------------------------------------------------------------------------------- /examples/riscv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "riscv" 3 | version = "0.1.0" 4 | authors = ["Runji Wang "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [features] 10 | tcp = ["smoltcp"] 11 | default = ["tcp"] 12 | 13 | [dependencies] 14 | log = "0.4" 15 | riscv = "0.10" 16 | opensbi-rt = { git = "https://github.com/rcore-os/opensbi-rt.git", rev = "abdfeb72" } 17 | flat_device_tree = "3.1.1" 18 | virtio-drivers = { path = "../.." } 19 | lazy_static = { version = "1.4", features = ["spin_no_std"] } 20 | 21 | [dependencies.smoltcp] 22 | version = "0.9.1" 23 | optional = true 24 | default-features = false 25 | features = [ 26 | "alloc", "log", # no std 27 | "medium-ethernet", 28 | "proto-ipv4", 29 | "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", 30 | ] 31 | -------------------------------------------------------------------------------- /examples/riscv/Makefile: -------------------------------------------------------------------------------- 1 | arch ?= riscv64 2 | target := $(arch)imac-unknown-none-elf 3 | mode := release 4 | kernel := target/$(target)/$(mode)/riscv 5 | img := target/$(target)/$(mode)/img 6 | 7 | tcp ?= on 8 | 9 | sysroot := $(shell rustc --print sysroot) 10 | objdump := $(shell find $(sysroot) -name llvm-objdump) --arch-name=$(arch) 11 | objcopy := $(shell find $(sysroot) -name llvm-objcopy) 12 | 13 | BUILD_ARGS += --target $(target) 14 | ifeq ($(mode), release) 15 | BUILD_ARGS += --release 16 | endif 17 | 18 | ifeq ($(tcp), on) 19 | BUILD_ARGS += --features tcp 20 | else 21 | BUILD_ARGS += --no-default-features 22 | endif 23 | 24 | .PHONY: kernel build clean qemu run env 25 | 26 | build: $(bin) 27 | 28 | env: 29 | rustup component add llvm-tools-preview rustfmt 30 | rustup target add $(target) 31 | 32 | kernel: 33 | cargo build $(BUILD_ARGS) 34 | 35 | asm: 36 | $(objdump) -d $(kernel) | less 37 | 38 | sym: 39 | $(objdump) -t $(kernel) | less 40 | 41 | header: 42 | $(objdump) -x $(kernel) | less 43 | 44 | clean: 45 | cargo clean 46 | 47 | qemu-legacy: kernel $(img) 48 | # Wait a few seconds, then try to open a connection to the VM so it can test its networking. 49 | ( sleep 4 && echo "hello" | nc localhost 5555 -N -v) & 50 | qemu-system-$(arch) \ 51 | $(QEMU_ARGS) \ 52 | -machine virt \ 53 | -serial mon:stdio \ 54 | -bios default \ 55 | -kernel $(kernel) \ 56 | -drive file=$(img),if=none,format=raw,id=x0 \ 57 | -device virtio-blk-device,drive=x0 \ 58 | -device virtio-rng-device \ 59 | -device virtio-gpu-device \ 60 | -device virtio-mouse-device \ 61 | -device virtio-net-device,netdev=net0 \ 62 | -netdev user,id=net0,hostfwd=tcp::5555-:5555\ 63 | -device virtio-sound-device,audiodev=audio0 \ 64 | -audiodev alsa,id=audio0 65 | 66 | qemu: kernel $(img) 67 | # Wait a few seconds, then try to open a connection to the VM so it can test its networking. 68 | ( sleep 4 && echo "hello" | nc localhost 5555 -N -v) & 69 | qemu-system-$(arch) \ 70 | $(QEMU_ARGS) \ 71 | -machine virt \ 72 | -serial mon:stdio \ 73 | -bios default \ 74 | -kernel $(kernel) \ 75 | -global virtio-mmio.force-legacy=false \ 76 | -drive file=$(img),if=none,format=raw,id=x0 \ 77 | -device virtio-blk-device,drive=x0 \ 78 | -device virtio-rng-device \ 79 | -device virtio-gpu-device \ 80 | -device virtio-mouse-device \ 81 | -device virtio-net-device,netdev=net0 \ 82 | -netdev user,id=net0,hostfwd=tcp::5555-:5555\ 83 | -device virtio-sound-device,audiodev=audio0 \ 84 | -audiodev alsa,id=audio0 85 | 86 | $(img): 87 | dd if=/dev/zero of=$@ bs=512 count=32 88 | 89 | run: build qemu-legacy qemu 90 | -------------------------------------------------------------------------------- /examples/riscv/linker32.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(riscv) 2 | ENTRY(_start) 3 | 4 | BASE_ADDRESS = 0x80400000; 5 | 6 | SECTIONS 7 | { 8 | /* Load the kernel at this address: "." means the current address */ 9 | . = BASE_ADDRESS; 10 | start = .; 11 | 12 | .text : { 13 | stext = .; 14 | *(.text.entry) 15 | *(.text .text.*) 16 | . = ALIGN(4K); 17 | etext = .; 18 | } 19 | 20 | .rodata : { 21 | srodata = .; 22 | *(.rodata .rodata.*) 23 | . = ALIGN(4K); 24 | erodata = .; 25 | } 26 | 27 | .data : { 28 | sdata = .; 29 | *(.data .data.*) 30 | *(.sdata .sdata.*) 31 | edata = .; 32 | } 33 | 34 | .stack : { 35 | *(.bss.stack) 36 | } 37 | 38 | .bss : { 39 | sbss = .; 40 | *(.bss .bss.*) 41 | *(.sbss .sbss.*) 42 | ebss = .; 43 | } 44 | 45 | . = ALIGN(4K); 46 | PROVIDE(end = .); 47 | } 48 | -------------------------------------------------------------------------------- /examples/riscv/linker64.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(riscv) 2 | ENTRY(_start) 3 | 4 | BASE_ADDRESS = 0x80200000; 5 | 6 | SECTIONS 7 | { 8 | /* Load the kernel at this address: "." means the current address */ 9 | . = BASE_ADDRESS; 10 | start = .; 11 | 12 | .text : { 13 | stext = .; 14 | *(.text.entry) 15 | *(.text .text.*) 16 | . = ALIGN(4K); 17 | etext = .; 18 | } 19 | 20 | .rodata : { 21 | srodata = .; 22 | *(.rodata .rodata.*) 23 | . = ALIGN(4K); 24 | erodata = .; 25 | } 26 | 27 | .data : { 28 | sdata = .; 29 | *(.data .data.*) 30 | *(.sdata .sdata.*) 31 | edata = .; 32 | } 33 | 34 | .stack : { 35 | *(.bss.stack) 36 | } 37 | 38 | .bss : { 39 | sbss = .; 40 | *(.bss .bss.*) 41 | *(.sbss .sbss.*) 42 | ebss = .; 43 | } 44 | 45 | . = ALIGN(4K); 46 | PROVIDE(end = .); 47 | } 48 | -------------------------------------------------------------------------------- /examples/riscv/music_44100Hz_u8_stereo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/virtio-drivers/3fc66fab639a8e1058c2d050e5c0b544db094a65/examples/riscv/music_44100Hz_u8_stereo.raw -------------------------------------------------------------------------------- /examples/riscv/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rustfmt", "llvm-tools"] 4 | targets = ["riscv64imac-unknown-none-elf"] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /examples/riscv/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![deny(warnings)] 4 | 5 | #[macro_use] 6 | extern crate log; 7 | 8 | extern crate alloc; 9 | extern crate opensbi_rt; 10 | 11 | use alloc::vec; 12 | use core::ptr::NonNull; 13 | use flat_device_tree::{node::FdtNode, standard_nodes::Compatible, Fdt}; 14 | use log::LevelFilter; 15 | use virtio_drivers::{ 16 | device::{ 17 | blk::VirtIOBlk, 18 | gpu::VirtIOGpu, 19 | input::VirtIOInput, 20 | rng::VirtIORng, 21 | sound::{PcmFormat, PcmRate, VirtIOSound}, 22 | }, 23 | transport::{ 24 | mmio::{MmioTransport, VirtIOHeader}, 25 | DeviceType, Transport, 26 | }, 27 | }; 28 | use virtio_impl::HalImpl; 29 | 30 | mod virtio_impl; 31 | 32 | #[cfg(feature = "tcp")] 33 | mod tcp; 34 | 35 | const NET_QUEUE_SIZE: usize = 16; 36 | 37 | #[no_mangle] 38 | extern "C" fn main(_hartid: usize, device_tree_paddr: usize) { 39 | log::set_max_level(LevelFilter::Info); 40 | init_dt(device_tree_paddr); 41 | info!("test end"); 42 | } 43 | 44 | fn init_dt(dtb: usize) { 45 | info!("device tree @ {:#x}", dtb); 46 | // Safe because the pointer is a valid pointer to unaliased memory. 47 | let fdt = unsafe { Fdt::from_ptr(dtb as *const u8).unwrap() }; 48 | walk_dt(fdt); 49 | } 50 | 51 | fn walk_dt(fdt: Fdt) { 52 | for node in fdt.all_nodes() { 53 | if let Some(compatible) = node.compatible() { 54 | if compatible.all().any(|s| s == "virtio,mmio") { 55 | virtio_probe(node); 56 | } 57 | } 58 | } 59 | } 60 | 61 | fn virtio_probe(node: FdtNode) { 62 | if let Some(reg) = node.reg().next() { 63 | let paddr = reg.starting_address as usize; 64 | let size = reg.size.unwrap(); 65 | let vaddr = paddr; 66 | info!("walk dt addr={:#x}, size={:#x}", paddr, size); 67 | info!( 68 | "Device tree node {}: {:?}", 69 | node.name, 70 | node.compatible().map(Compatible::first), 71 | ); 72 | let header = NonNull::new(vaddr as *mut VirtIOHeader).unwrap(); 73 | match unsafe { MmioTransport::new(header, size) } { 74 | Err(e) => warn!("Error creating VirtIO MMIO transport: {}", e), 75 | Ok(transport) => { 76 | info!( 77 | "Detected virtio MMIO device with vendor id {:#X}, device type {:?}, version {:?}", 78 | transport.vendor_id(), 79 | transport.device_type(), 80 | transport.version(), 81 | ); 82 | virtio_device(transport); 83 | } 84 | } 85 | } 86 | } 87 | 88 | fn virtio_device(transport: impl Transport) { 89 | match transport.device_type() { 90 | DeviceType::Block => virtio_blk(transport), 91 | DeviceType::GPU => virtio_gpu(transport), 92 | DeviceType::Input => virtio_input(transport), 93 | DeviceType::Network => virtio_net(transport), 94 | DeviceType::Sound => virtio_sound(transport), 95 | DeviceType::EntropySource => virtio_rng(transport), 96 | t => warn!("Unrecognized virtio device: {:?}", t), 97 | } 98 | } 99 | 100 | fn virtio_rng(transport: T) { 101 | let mut bytes = [0u8; 8]; 102 | let mut rng = VirtIORng::::new(transport).expect("failed to create rng driver"); 103 | let len = rng 104 | .request_entropy(&mut bytes) 105 | .expect("failed to receive entropy"); 106 | info!("received {len} random bytes: {:?}", &bytes[..len]); 107 | info!("virtio-rng test finished"); 108 | } 109 | 110 | fn virtio_blk(transport: T) { 111 | let mut blk = VirtIOBlk::::new(transport).expect("failed to create blk driver"); 112 | let mut input = vec![0xffu8; 512]; 113 | let mut output = vec![0; 512]; 114 | for i in 0..32 { 115 | for x in input.iter_mut() { 116 | *x = i as u8; 117 | } 118 | blk.write_blocks(i, &input).expect("failed to write"); 119 | blk.read_blocks(i, &mut output).expect("failed to read"); 120 | assert_eq!(input, output); 121 | } 122 | info!("virtio-blk test finished"); 123 | } 124 | 125 | fn virtio_gpu(transport: T) { 126 | let mut gpu = VirtIOGpu::::new(transport).expect("failed to create gpu driver"); 127 | let (width, height) = gpu.resolution().expect("failed to get resolution"); 128 | let width = width as usize; 129 | let height = height as usize; 130 | info!("GPU resolution is {}x{}", width, height); 131 | let fb = gpu.setup_framebuffer().expect("failed to get fb"); 132 | for y in 0..height { 133 | for x in 0..width { 134 | let idx = (y * width + x) * 4; 135 | fb[idx] = x as u8; 136 | fb[idx + 1] = y as u8; 137 | fb[idx + 2] = (x + y) as u8; 138 | } 139 | } 140 | gpu.flush().expect("failed to flush"); 141 | //delay some time 142 | info!("virtio-gpu show graphics...."); 143 | for _ in 0..10000 { 144 | for _ in 0..100000 { 145 | unsafe { 146 | core::arch::asm!("nop"); 147 | } 148 | } 149 | } 150 | 151 | info!("virtio-gpu test finished"); 152 | } 153 | 154 | fn virtio_input(transport: T) { 155 | //let mut event_buf = [0u64; 32]; 156 | let mut _input = 157 | VirtIOInput::::new(transport).expect("failed to create input driver"); 158 | // loop { 159 | // input.ack_interrupt().expect("failed to ack"); 160 | // info!("mouse: {:?}", input.mouse_xy()); 161 | // } 162 | // TODO: handle external interrupt 163 | } 164 | 165 | fn virtio_net(transport: T) { 166 | #[cfg(not(feature = "tcp"))] 167 | { 168 | let mut net = 169 | virtio_drivers::device::net::VirtIONetRaw::::new(transport) 170 | .expect("failed to create net driver"); 171 | info!("MAC address: {:02x?}", net.mac_address()); 172 | 173 | let mut buf = [0u8; 2048]; 174 | let (hdr_len, pkt_len) = net.receive_wait(&mut buf).expect("failed to recv"); 175 | info!( 176 | "recv {} bytes: {:02x?}", 177 | pkt_len, 178 | &buf[hdr_len..hdr_len + pkt_len] 179 | ); 180 | net.send(&buf[..hdr_len + pkt_len]).expect("failed to send"); 181 | info!("virtio-net test finished"); 182 | } 183 | 184 | #[cfg(feature = "tcp")] 185 | { 186 | const NET_BUFFER_LEN: usize = 2048; 187 | let net = virtio_drivers::device::net::VirtIONet::::new( 188 | transport, 189 | NET_BUFFER_LEN, 190 | ) 191 | .expect("failed to create net driver"); 192 | info!("MAC address: {:02x?}", net.mac_address()); 193 | tcp::test_echo_server(net); 194 | } 195 | } 196 | 197 | fn virtio_sound(transport: T) { 198 | let mut sound = 199 | VirtIOSound::::new(transport).expect("failed to create sound driver"); 200 | let output_streams = sound.output_streams().unwrap(); 201 | if !output_streams.is_empty() { 202 | let output_stream_id = *output_streams.first().unwrap(); 203 | let rates = sound.rates_supported(output_stream_id).unwrap(); 204 | let formats = sound.formats_supported(output_stream_id).unwrap(); 205 | let channel_range = sound.channel_range_supported(output_stream_id).unwrap(); 206 | let features = sound.features_supported(output_stream_id).unwrap(); 207 | 208 | let rate = if rates.contains(PcmRate::Rate44100.into()) { 209 | PcmRate::Rate44100 210 | } else { 211 | PcmRate::Rate32000 212 | }; 213 | let format = if formats.contains(PcmFormat::U8.into()) { 214 | PcmFormat::U8 215 | } else { 216 | PcmFormat::U32 217 | }; 218 | let channel = if channel_range.contains(&2) { 219 | 2 220 | } else { 221 | *channel_range.start() 222 | }; 223 | sound 224 | .pcm_set_params( 225 | output_stream_id, 226 | 4410 * 2, 227 | 4410, 228 | features, 229 | channel, 230 | format, 231 | rate, 232 | ) 233 | .expect("pcm_set_params error"); 234 | sound 235 | .pcm_prepare(output_stream_id) 236 | .expect("pcm_prepare error"); 237 | sound.pcm_start(output_stream_id).expect("pcm_start error"); 238 | let music = include_bytes!("../music_44100Hz_u8_stereo.raw"); 239 | info!("[sound device] music len is {} bytes.", music.len()); 240 | // xfer buffer 241 | sound 242 | .pcm_xfer(output_stream_id, &music[..]) 243 | .expect("pcm_xfer error"); 244 | sound.pcm_stop(output_stream_id).expect("pcm_stop error"); 245 | sound 246 | .pcm_release(output_stream_id) 247 | .expect("pcm_release error"); 248 | match sound.latest_notification() { 249 | Ok(notification) => info!("{:?}", notification), 250 | Err(e) => warn!("{}", e), 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /examples/riscv/src/tcp.rs: -------------------------------------------------------------------------------- 1 | //! Simple echo server over TCP. 2 | //! 3 | //! Ref: https://github.com/smoltcp-rs/smoltcp/blob/master/examples/server.rs 4 | 5 | use alloc::{borrow::ToOwned, rc::Rc, vec, vec::Vec}; 6 | use core::{cell::RefCell, str::FromStr}; 7 | 8 | use smoltcp::iface::{Config, Interface, SocketSet}; 9 | use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; 10 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; 11 | use smoltcp::{socket::tcp, time::Instant}; 12 | use virtio_drivers::device::net::{RxBuffer, VirtIONet}; 13 | use virtio_drivers::{transport::Transport, Error}; 14 | 15 | use super::{HalImpl, NET_QUEUE_SIZE}; 16 | 17 | type DeviceImpl = VirtIONet; 18 | 19 | const IP: &str = "10.0.2.15"; // QEMU user networking default IP 20 | const GATEWAY: &str = "10.0.2.2"; // QEMU user networking gateway 21 | const PORT: u16 = 5555; 22 | 23 | struct DeviceWrapper { 24 | inner: Rc>>, 25 | } 26 | 27 | impl DeviceWrapper { 28 | fn new(dev: DeviceImpl) -> Self { 29 | DeviceWrapper { 30 | inner: Rc::new(RefCell::new(dev)), 31 | } 32 | } 33 | 34 | fn mac_address(&self) -> EthernetAddress { 35 | EthernetAddress(self.inner.borrow().mac_address()) 36 | } 37 | } 38 | 39 | impl Device for DeviceWrapper { 40 | type RxToken<'a> 41 | = VirtioRxToken 42 | where 43 | Self: 'a; 44 | type TxToken<'a> 45 | = VirtioTxToken 46 | where 47 | Self: 'a; 48 | 49 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 50 | match self.inner.borrow_mut().receive() { 51 | Ok(buf) => Some(( 52 | VirtioRxToken(self.inner.clone(), buf), 53 | VirtioTxToken(self.inner.clone()), 54 | )), 55 | Err(Error::NotReady) => None, 56 | Err(err) => panic!("receive failed: {}", err), 57 | } 58 | } 59 | 60 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 61 | Some(VirtioTxToken(self.inner.clone())) 62 | } 63 | 64 | fn capabilities(&self) -> DeviceCapabilities { 65 | let mut caps = DeviceCapabilities::default(); 66 | caps.max_transmission_unit = 1536; 67 | caps.max_burst_size = Some(1); 68 | caps.medium = Medium::Ethernet; 69 | caps 70 | } 71 | } 72 | 73 | struct VirtioRxToken(Rc>>, RxBuffer); 74 | struct VirtioTxToken(Rc>>); 75 | 76 | impl RxToken for VirtioRxToken { 77 | fn consume(self, f: F) -> R 78 | where 79 | F: FnOnce(&mut [u8]) -> R, 80 | { 81 | let mut rx_buf = self.1; 82 | trace!( 83 | "RECV {} bytes: {:02X?}", 84 | rx_buf.packet_len(), 85 | rx_buf.packet() 86 | ); 87 | let result = f(rx_buf.packet_mut()); 88 | self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); 89 | result 90 | } 91 | } 92 | 93 | impl TxToken for VirtioTxToken { 94 | fn consume(self, len: usize, f: F) -> R 95 | where 96 | F: FnOnce(&mut [u8]) -> R, 97 | { 98 | let mut dev = self.0.borrow_mut(); 99 | let mut tx_buf = dev.new_tx_buffer(len); 100 | let result = f(tx_buf.packet_mut()); 101 | trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); 102 | dev.send(tx_buf).unwrap(); 103 | result 104 | } 105 | } 106 | 107 | pub fn test_echo_server(dev: DeviceImpl) { 108 | let mut device = DeviceWrapper::new(dev); 109 | 110 | // Create interface 111 | let mut config = Config::new(); 112 | config.random_seed = 0x2333; 113 | if device.capabilities().medium == Medium::Ethernet { 114 | config.hardware_addr = Some(device.mac_address().into()); 115 | } 116 | 117 | let mut iface = Interface::new(config, &mut device); 118 | iface.update_ip_addrs(|ip_addrs| { 119 | ip_addrs 120 | .push(IpCidr::new(IpAddress::from_str(IP).unwrap(), 24)) 121 | .unwrap(); 122 | }); 123 | 124 | iface 125 | .routes_mut() 126 | .add_default_ipv4_route(Ipv4Address::from_str(GATEWAY).unwrap()) 127 | .unwrap(); 128 | 129 | // Create sockets 130 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 131 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 132 | let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); 133 | 134 | let mut sockets = SocketSet::new(vec![]); 135 | let tcp_handle = sockets.add(tcp_socket); 136 | 137 | info!("start a reverse echo server..."); 138 | let mut tcp_active = false; 139 | loop { 140 | let timestamp = Instant::from_micros_const(riscv::register::time::read() as i64 / 10); 141 | iface.poll(timestamp, &mut device, &mut sockets); 142 | 143 | // tcp:PORT: echo with reverse 144 | let socket = sockets.get_mut::(tcp_handle); 145 | if !socket.is_open() { 146 | info!("listening on port {}...", PORT); 147 | socket.listen(PORT).unwrap(); 148 | } 149 | 150 | if socket.is_active() && !tcp_active { 151 | info!("tcp:{} connected", PORT); 152 | } else if !socket.is_active() && tcp_active { 153 | info!("tcp:{} disconnected", PORT); 154 | } 155 | tcp_active = socket.is_active(); 156 | 157 | if socket.may_recv() { 158 | let data = socket 159 | .recv(|buffer| { 160 | let recvd_len = buffer.len(); 161 | if !buffer.is_empty() { 162 | debug!("tcp:{} recv {} bytes: {:?}", PORT, recvd_len, buffer); 163 | let mut lines = buffer 164 | .split(|&b| b == b'\n') 165 | .map(ToOwned::to_owned) 166 | .collect::>(); 167 | for line in lines.iter_mut() { 168 | line.reverse(); 169 | } 170 | let data = lines.join(&b'\n'); 171 | (recvd_len, data) 172 | } else { 173 | (0, vec![]) 174 | } 175 | }) 176 | .unwrap(); 177 | if socket.can_send() && !data.is_empty() { 178 | debug!("tcp:{} send data: {:?}", PORT, data); 179 | socket.send_slice(&data[..]).unwrap(); 180 | } 181 | } else if socket.may_send() { 182 | info!("tcp:{} close", PORT); 183 | socket.close(); 184 | break; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /examples/riscv/src/virtio_impl.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | ptr::NonNull, 3 | sync::atomic::{AtomicUsize, Ordering}, 4 | }; 5 | use lazy_static::lazy_static; 6 | use log::trace; 7 | use virtio_drivers::{BufferDirection, Hal, PhysAddr, PAGE_SIZE}; 8 | 9 | extern "C" { 10 | fn end(); 11 | } 12 | 13 | lazy_static! { 14 | static ref DMA_PADDR: AtomicUsize = AtomicUsize::new(end as usize); 15 | } 16 | 17 | pub struct HalImpl; 18 | 19 | unsafe impl Hal for HalImpl { 20 | fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { 21 | let paddr = DMA_PADDR.fetch_add(PAGE_SIZE * pages, Ordering::SeqCst); 22 | trace!("alloc DMA: paddr={:#x}, pages={}", paddr, pages); 23 | let vaddr = NonNull::new(paddr as _).unwrap(); 24 | (paddr, vaddr) 25 | } 26 | 27 | unsafe fn dma_dealloc(paddr: PhysAddr, _vaddr: NonNull, pages: usize) -> i32 { 28 | trace!("dealloc DMA: paddr={:#x}, pages={}", paddr, pages); 29 | 0 30 | } 31 | 32 | unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { 33 | NonNull::new(paddr as _).unwrap() 34 | } 35 | 36 | unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { 37 | let vaddr = buffer.as_ptr() as *mut u8 as usize; 38 | // Nothing to do, as the host already has access to all memory. 39 | virt_to_phys(vaddr) 40 | } 41 | 42 | unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { 43 | // Nothing to do, as the host already has access to all memory and we didn't copy the buffer 44 | // anywhere else. 45 | } 46 | } 47 | 48 | fn virt_to_phys(vaddr: usize) -> PhysAddr { 49 | vaddr 50 | } 51 | -------------------------------------------------------------------------------- /examples/riscv/virtio-test-gpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/virtio-drivers/3fc66fab639a8e1058c2d050e5c0b544db094a65/examples/riscv/virtio-test-gpu.png -------------------------------------------------------------------------------- /examples/vsock_server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vsock_server" 3 | version = "0.1.0" 4 | authors = ["Alice Wang "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | vsock = "0.3.0" 9 | -------------------------------------------------------------------------------- /examples/vsock_server/README.md: -------------------------------------------------------------------------------- 1 | # Running Virtio Vsock Example 2 | 3 | The binary `vsock_server` sets up a vsock server on the host. It can be used to run the virtio vsock example in `examples/aarch64` 4 | 5 | ## Build and Run the Example 6 | 7 | Run the server on the host: 8 | ```bash 9 | examples/vsock_server$ cargo run 10 | ``` 11 | 12 | Run the guest: 13 | ```bash 14 | examples/aarch64$ make qemu-vsock 15 | ``` 16 | 17 | ## Sample Log 18 | 19 | The example demonstrates two rounds of message exchange between the host and the guest. 20 | 21 | Host: 22 | ``` 23 | [Host] Setting up listening socket on port 1221 24 | [Host] Accept connection: VsockStream { socket: 4 }, peer addr: Ok(cid: 102 port: 1221), local addr: Ok(cid: 2 port: 1221) 25 | [Host] Sent message: "0-Hello from host". 26 | [Host] Flushed. 27 | [Host] Received message: [48, 45, 65, 99, 107, 46, 32, 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 103, 117, 101, 115, 116, 46, 0, 0, 0, 0, 0, 0](Ok("0-Ack. Hello from guest.")), len: 24 28 | [Host] Sent message: "1-Hello from host". 29 | [Host] Flushed. 30 | [Host] Received message: [49, 45, 65, 99, 107, 46, 32, 82, 101, 99, 101, 105, 118, 101, 100, 32, 97, 103, 97, 105, 110, 46, 0, 0, 0, 0, 0, 0, 0, 0](Ok("1-Ack. Received again.")), len: 22 31 | [Host] End. 32 | ``` 33 | 34 | Guest: 35 | ``` 36 | [INFO] guest cid: 102 37 | [INFO] Connecting to host on port 1221... 38 | [DEBUG] Connection established: Some(ConnectionInfo { dst: VsockAddr { cid: 2, port: 1221 }, src_port: 1221, peer_buf_alloc: 0, peer_fwd_cnt: 0 }) 39 | [INFO] Connected to the host 40 | ... 41 | [INFO] Received message: [48, 45, 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 104, 111, 115, 116, 0, 0, 0, 0, 0, 0, 0](Ok("0-Hello from host")), len: 17 42 | [DEBUG] Connection info updated: Some(ConnectionInfo { dst: VsockAddr { cid: 2, port: 1221 }, src_port: 1221, peer_buf_alloc: 262144, peer_fwd_cnt: 0 }) 43 | [INFO] Sent message: "0-Ack. Hello from guest." 44 | [INFO] Received message: [49, 45, 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 104, 111, 115, 116, 0, 0, 0, 0, 0, 0, 0](Ok("1-Hello from host")), len: 17 45 | [INFO] Sent message: "1-Ack. Received again." 46 | [INFO] Disconnected from the peer 47 | [INFO] Shutdown the connection 48 | ``` 49 | -------------------------------------------------------------------------------- /examples/vsock_server/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Sets up a listening socket on host. 2 | use std::{ 3 | io::{Read, Write}, 4 | time::Duration, 5 | }; 6 | use vsock::{VsockAddr, VsockListener, VMADDR_CID_HOST}; 7 | 8 | const PORT: u32 = 1221; 9 | 10 | fn main() { 11 | println!("[Host] Setting up listening socket on port {PORT}"); 12 | let listener = VsockListener::bind(&VsockAddr::new(VMADDR_CID_HOST, PORT)) 13 | .expect("Failed to set up listening port"); 14 | 15 | let Some(Ok(mut vsock_stream)) = listener.incoming().next() else { 16 | println!("[Host] Failed to get vsock_stream"); 17 | return; 18 | }; 19 | println!( 20 | "[Host] Accept connection: {:?}, peer addr: {:?}, local addr: {:?}", 21 | vsock_stream, 22 | vsock_stream.peer_addr(), 23 | vsock_stream.local_addr() 24 | ); 25 | 26 | const EXCHANGE_NUM: usize = 2; 27 | for k in 0..EXCHANGE_NUM { 28 | let message = &format!("{k}-Hello from host"); 29 | vsock_stream 30 | .write_all(message.as_bytes()) 31 | .expect("write_all"); 32 | println!("[Host] Sent message: {:?}.", message); 33 | vsock_stream.flush().expect("flush"); 34 | println!("[Host] Flushed."); 35 | 36 | let mut message = vec![0u8; 30]; 37 | vsock_stream 38 | .set_read_timeout(Some(Duration::from_millis(3_000))) 39 | .expect("set_read_timeout"); 40 | for i in 0..10 { 41 | match vsock_stream.read(&mut message) { 42 | Ok(len) => { 43 | println!( 44 | "[Host] Received message: {:?}({:?}), len: {:?}", 45 | message, 46 | std::str::from_utf8(&message[..len]), 47 | len, 48 | ); 49 | break; 50 | } 51 | Err(e) => { 52 | println!("{i} {e:?}"); 53 | std::thread::sleep(Duration::from_millis(200)) 54 | } 55 | } 56 | } 57 | } 58 | println!("[Host] End."); 59 | } 60 | -------------------------------------------------------------------------------- /examples/x86_64/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-none" 3 | -------------------------------------------------------------------------------- /examples/x86_64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x86_64" 3 | version = "0.1.0" 4 | authors = ["Yuekai Jia "] 5 | edition = "2021" 6 | 7 | [features] 8 | tcp = ["smoltcp"] 9 | default = ["tcp"] 10 | 11 | [dependencies] 12 | log = "0.4.17" 13 | spin = "0.9" 14 | x86_64 = { version = "0.14.12", default-features = false, features = [ 15 | "instructions", 16 | "abi_x86_interrupt", 17 | ] } 18 | uart_16550 = "0.2" 19 | linked_list_allocator = "0.10" 20 | lazy_static = { version = "1.4.0", features = ["spin_no_std"] } 21 | virtio-drivers = { path = "../.." } 22 | 23 | [dependencies.smoltcp] 24 | version = "0.9.1" 25 | optional = true 26 | default-features = false 27 | features = [ 28 | "alloc", "log", # no std 29 | "medium-ethernet", 30 | "proto-ipv4", 31 | "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", 32 | ] 33 | -------------------------------------------------------------------------------- /examples/x86_64/Makefile: -------------------------------------------------------------------------------- 1 | arch := x86_64 2 | target := x86_64-unknown-none 3 | mode := release 4 | kernel := target/$(target)/$(mode)/$(arch) 5 | img := target/$(target)/$(mode)/img 6 | accel ?= on 7 | tcp ?= on 8 | 9 | sysroot := $(shell rustc --print sysroot) 10 | objdump := $(shell find $(sysroot) -name llvm-objdump) --arch-name=$(arch) 11 | objcopy := $(shell find $(sysroot) -name llvm-objcopy) 12 | 13 | BUILD_ARGS += --target $(target) 14 | ifeq ($(mode), release) 15 | BUILD_ARGS += --release 16 | endif 17 | ifeq ($(tcp), on) 18 | BUILD_ARGS += --features tcp 19 | else 20 | BUILD_ARGS += --no-default-features 21 | endif 22 | 23 | QEMU_ARGS += \ 24 | -machine q35 \ 25 | -serial mon:stdio \ 26 | -kernel $(kernel) \ 27 | -device virtio-gpu-pci -vga none \ 28 | -device virtio-blk-pci,drive=x0 -drive file=$(img),if=none,format=raw,id=x0 \ 29 | -device virtio-rng-pci \ 30 | -device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555 31 | 32 | ifeq ($(accel), on) 33 | QEMU_ARGS += -cpu host -accel kvm 34 | endif 35 | 36 | .PHONY: kernel clean qemu run env 37 | 38 | kernel: 39 | cargo build $(BUILD_ARGS) --config 'build.rustflags="--cfg platform=\"qemu\" -Clink-args=-Tlinker.ld -Clink-args=-no-pie"' 40 | 41 | env: 42 | rustup component add llvm-tools-preview rustfmt 43 | rustup target add $(target) 44 | 45 | asm: kernel 46 | $(objdump) -d $(kernel) | less 47 | 48 | sym: kernel 49 | $(objdump) -t $(kernel) | less 50 | 51 | header: kernel 52 | $(objdump) -x $(kernel) | less 53 | 54 | clean: 55 | cargo clean 56 | 57 | qemu: kernel $(img) 58 | # Wait a few seconds, then try to open a connection to the VM so it can test its networking. 59 | ( sleep 4 && echo "hello" | nc localhost 5555 -N -v) & 60 | qemu-system-$(arch) $(QEMU_ARGS) 61 | 62 | $(img): 63 | dd if=/dev/zero of=$@ bs=512 count=32 64 | 65 | run: qemu 66 | -------------------------------------------------------------------------------- /examples/x86_64/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(i386:x86-64) 2 | ENTRY(_start) 3 | 4 | BASE_ADDRESS = 0x200000; 5 | 6 | SECTIONS 7 | { 8 | /* Load the kernel at this address: "." means the current address */ 9 | . = BASE_ADDRESS; 10 | start = .; 11 | 12 | .text : { 13 | stext = .; 14 | *(.text.entry) 15 | *(.text .text.*) 16 | . = ALIGN(4K); 17 | etext = .; 18 | } 19 | 20 | .rodata : { 21 | srodata = .; 22 | *(.rodata .rodata.*) 23 | . = ALIGN(4K); 24 | erodata = .; 25 | } 26 | 27 | .data : { 28 | sdata = .; 29 | *(.data .data.*) 30 | *(.sdata .sdata.*) 31 | edata = .; 32 | } 33 | 34 | .stack : { 35 | *(.bss.stack) 36 | } 37 | 38 | .bss : { 39 | sbss = .; 40 | *(.bss .bss.*) 41 | *(.sbss .sbss.*) 42 | ebss = .; 43 | } 44 | 45 | . = ALIGN(4K); 46 | PROVIDE(dma_region = .); 47 | } 48 | -------------------------------------------------------------------------------- /examples/x86_64/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rustfmt", "llvm-tools"] 4 | targets = ["x86_64-unknown-none"] 5 | profile = "minimal" 6 | -------------------------------------------------------------------------------- /examples/x86_64/src/boot.rs: -------------------------------------------------------------------------------- 1 | use core::arch::global_asm; 2 | 3 | use x86_64::registers::control::{Cr0Flags, Cr4Flags}; 4 | use x86_64::registers::model_specific::EferFlags; 5 | 6 | const BOOT_STACK_SIZE: usize = 0x4000; // 16K 7 | 8 | /// Flags set in the ’flags’ member of the multiboot header. 9 | /// 10 | /// (bits 1, 16: memory information, address fields in header) 11 | const MULTIBOOT_HEADER_FLAGS: usize = 0x0001_0002; 12 | 13 | /// The magic field should contain this. 14 | const MULTIBOOT_HEADER_MAGIC: usize = 0x1BADB002; 15 | 16 | /// This should be in EAX. 17 | const MULTIBOOT_BOOTLOADER_MAGIC: usize = 0x2BADB002; 18 | 19 | const CR0: u64 = Cr0Flags::PROTECTED_MODE_ENABLE.bits() 20 | | Cr0Flags::MONITOR_COPROCESSOR.bits() 21 | | Cr0Flags::NUMERIC_ERROR.bits() 22 | | Cr0Flags::WRITE_PROTECT.bits() 23 | | Cr0Flags::PAGING.bits(); 24 | 25 | const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() | Cr4Flags::PAGE_GLOBAL.bits(); 26 | 27 | const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits(); 28 | 29 | global_asm!( 30 | include_str!("multiboot.S"), 31 | mb_magic = const MULTIBOOT_BOOTLOADER_MAGIC, 32 | mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC, 33 | mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS, 34 | entry = sym super::main, 35 | 36 | offset = const 0, // virt_addr == phys_addr 37 | boot_stack_size = const BOOT_STACK_SIZE, 38 | 39 | cr0 = const CR0, 40 | cr4 = const CR4, 41 | efer_msr = const 0xC000_0080u32, 42 | efer = const EFER, 43 | ); 44 | -------------------------------------------------------------------------------- /examples/x86_64/src/hal.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | ptr::NonNull, 3 | sync::atomic::{AtomicUsize, Ordering}, 4 | }; 5 | use lazy_static::lazy_static; 6 | use log::trace; 7 | use virtio_drivers::{BufferDirection, Hal, PhysAddr, PAGE_SIZE}; 8 | 9 | extern "C" { 10 | static dma_region: u8; 11 | } 12 | 13 | lazy_static! { 14 | static ref DMA_PADDR: AtomicUsize = 15 | AtomicUsize::new(unsafe { &dma_region as *const u8 as usize }); 16 | } 17 | 18 | pub struct HalImpl; 19 | 20 | unsafe impl Hal for HalImpl { 21 | fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { 22 | let paddr = DMA_PADDR.fetch_add(PAGE_SIZE * pages, Ordering::SeqCst); 23 | trace!("alloc DMA: paddr={:#x}, pages={}", paddr, pages); 24 | let vaddr = NonNull::new(paddr as _).unwrap(); 25 | (paddr, vaddr) 26 | } 27 | 28 | unsafe fn dma_dealloc(paddr: PhysAddr, _vaddr: NonNull, pages: usize) -> i32 { 29 | trace!("dealloc DMA: paddr={:#x}, pages={}", paddr, pages); 30 | 0 31 | } 32 | 33 | unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { 34 | NonNull::new(paddr as _).unwrap() 35 | } 36 | 37 | unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { 38 | let vaddr = buffer.as_ptr() as *mut u8 as usize; 39 | // Nothing to do, as the host already has access to all memory. 40 | virt_to_phys(vaddr) 41 | } 42 | 43 | unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { 44 | // Nothing to do, as the host already has access to all memory and we didn't copy the buffer 45 | // anywhere else. 46 | } 47 | } 48 | 49 | fn virt_to_phys(vaddr: usize) -> PhysAddr { 50 | vaddr 51 | } 52 | -------------------------------------------------------------------------------- /examples/x86_64/src/heap.rs: -------------------------------------------------------------------------------- 1 | use linked_list_allocator::LockedHeap; 2 | 3 | #[global_allocator] 4 | static ALLOCATOR: LockedHeap = LockedHeap::empty(); 5 | 6 | const HEAP_SIZE: usize = 0x10000; // 64K 7 | 8 | static mut HEAP: [u64; HEAP_SIZE / 8] = [0; HEAP_SIZE / 8]; 9 | 10 | pub fn init_heap() { 11 | unsafe { 12 | let heap_start = HEAP.as_ptr() as *mut u8; 13 | ALLOCATOR.lock().init(heap_start, HEAP_SIZE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/x86_64/src/logger.rs: -------------------------------------------------------------------------------- 1 | //! Log implementation using the UART. 2 | 3 | use core::fmt::Write; 4 | use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; 5 | use spin::mutex::SpinMutex; 6 | use uart_16550::SerialPort; 7 | 8 | static LOGGER: Logger = Logger { 9 | uart: SpinMutex::new(None), 10 | }; 11 | 12 | struct Logger { 13 | uart: SpinMutex>, 14 | } 15 | 16 | /// Initialises UART logger. 17 | pub fn init(max_level: LevelFilter) -> Result<(), SetLoggerError> { 18 | // Safe because `0x3f8` is COM1 I/O port. 19 | let mut uart = unsafe { SerialPort::new(0x3f8) }; 20 | uart.init(); 21 | LOGGER.uart.lock().replace(uart); 22 | 23 | log::set_logger(&LOGGER)?; 24 | log::set_max_level(max_level); 25 | Ok(()) 26 | } 27 | 28 | impl Log for Logger { 29 | fn enabled(&self, _metadata: &Metadata) -> bool { 30 | true 31 | } 32 | 33 | fn log(&self, record: &Record) { 34 | writeln!( 35 | LOGGER.uart.lock().as_mut().unwrap(), 36 | "[{}] {}", 37 | record.level(), 38 | record.args() 39 | ) 40 | .unwrap(); 41 | } 42 | 43 | fn flush(&self) {} 44 | } 45 | -------------------------------------------------------------------------------- /examples/x86_64/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(asm_const)] 4 | #![feature(abi_x86_interrupt)] 5 | 6 | #[macro_use] 7 | extern crate log; 8 | extern crate alloc; 9 | 10 | mod boot; 11 | mod hal; 12 | mod heap; 13 | mod logger; 14 | mod trap; 15 | 16 | #[cfg(feature = "tcp")] 17 | mod tcp; 18 | 19 | use self::hal::HalImpl; 20 | use virtio_drivers::{ 21 | device::{blk::VirtIOBlk, gpu::VirtIOGpu, rng::VirtIORng}, 22 | transport::{ 23 | pci::{ 24 | bus::{BarInfo, Cam, Command, ConfigurationAccess, DeviceFunction, MmioCam, PciRoot}, 25 | virtio_device_type, PciTransport, 26 | }, 27 | DeviceType, Transport, 28 | }, 29 | }; 30 | 31 | /// Memory mapped address space to access PCI configuration. 32 | /// 33 | /// Currently it is hardcoded (from qemu/roms/seabios/src/hw/dev-q35.h) 34 | /// 35 | /// TODO: get it from ACPI MCFG table. 36 | const MMCONFIG_BASE: usize = 0xB000_0000; 37 | 38 | const NET_QUEUE_SIZE: usize = 16; 39 | 40 | fn system_off() -> ! { 41 | use x86_64::instructions::{hlt, port::PortWriteOnly}; 42 | unsafe { 43 | PortWriteOnly::new(0x604).write(0x2000u16); 44 | loop { 45 | hlt(); 46 | } 47 | } 48 | } 49 | 50 | #[no_mangle] 51 | extern "C" fn main(_mbi: *const u8) -> ! { 52 | logger::init(log::LevelFilter::Info).unwrap(); 53 | info!("virtio-drivers example started."); 54 | 55 | trap::init(); 56 | heap::init_heap(); 57 | 58 | enumerate_pci(MMCONFIG_BASE as _); 59 | 60 | info!("test end"); 61 | system_off(); 62 | } 63 | 64 | fn virtio_device(transport: impl Transport) { 65 | match transport.device_type() { 66 | DeviceType::Block => virtio_blk(transport), 67 | DeviceType::GPU => virtio_gpu(transport), 68 | DeviceType::Network => virtio_net(transport), 69 | DeviceType::EntropySource => virtio_rng(transport), 70 | t => warn!("Unrecognized virtio device: {:?}", t), 71 | } 72 | } 73 | 74 | fn virtio_rng(transport: T) { 75 | let mut bytes = [0u8; 8]; 76 | let mut rng = VirtIORng::::new(transport).expect("failed to create rng driver"); 77 | let len = rng 78 | .request_entropy(&mut bytes) 79 | .expect("failed to receive entropy"); 80 | info!("received {len} random bytes: {:?}", &bytes[..len]); 81 | info!("virtio-rng test finished"); 82 | } 83 | 84 | fn virtio_blk(transport: T) { 85 | let mut blk = VirtIOBlk::::new(transport).expect("failed to create blk driver"); 86 | assert!(!blk.readonly()); 87 | let mut input = [0xffu8; 512]; 88 | let mut output = [0; 512]; 89 | for i in 0..32 { 90 | for x in input.iter_mut() { 91 | *x = i as u8; 92 | } 93 | blk.write_blocks(i, &input).expect("failed to write"); 94 | blk.read_blocks(i, &mut output).expect("failed to read"); 95 | assert_eq!(input, output); 96 | } 97 | info!("virtio-blk test finished"); 98 | } 99 | 100 | fn virtio_gpu(transport: T) { 101 | let mut gpu = VirtIOGpu::::new(transport).expect("failed to create gpu driver"); 102 | let (width, height) = gpu.resolution().expect("failed to get resolution"); 103 | let width = width as usize; 104 | let height = height as usize; 105 | info!("GPU resolution is {}x{}", width, height); 106 | let fb = gpu.setup_framebuffer().expect("failed to get fb"); 107 | for y in 0..height { 108 | for x in 0..width { 109 | let idx = (y * width + x) * 4; 110 | fb[idx] = x as u8; 111 | fb[idx + 1] = y as u8; 112 | fb[idx + 2] = (x + y) as u8; 113 | } 114 | } 115 | gpu.flush().expect("failed to flush"); 116 | //delay some time 117 | info!("virtio-gpu show graphics...."); 118 | for _ in 0..10000 { 119 | for _ in 0..100000 { 120 | unsafe { 121 | core::arch::asm!("nop"); 122 | } 123 | } 124 | } 125 | 126 | info!("virtio-gpu test finished"); 127 | } 128 | 129 | fn virtio_net(transport: T) { 130 | #[cfg(not(feature = "tcp"))] 131 | { 132 | let mut net = 133 | virtio_drivers::device::net::VirtIONetRaw::::new(transport) 134 | .expect("failed to create net driver"); 135 | info!("MAC address: {:02x?}", net.mac_address()); 136 | 137 | let mut buf = [0u8; 2048]; 138 | let (hdr_len, pkt_len) = net.receive_wait(&mut buf).expect("failed to recv"); 139 | info!( 140 | "recv {} bytes: {:02x?}", 141 | pkt_len, 142 | &buf[hdr_len..hdr_len + pkt_len] 143 | ); 144 | net.send(&buf[..hdr_len + pkt_len]).expect("failed to send"); 145 | info!("virtio-net test finished"); 146 | } 147 | 148 | #[cfg(feature = "tcp")] 149 | { 150 | const NET_BUFFER_LEN: usize = 2048; 151 | let net = virtio_drivers::device::net::VirtIONet::::new( 152 | transport, 153 | NET_BUFFER_LEN, 154 | ) 155 | .expect("failed to create net driver"); 156 | info!("MAC address: {:02x?}", net.mac_address()); 157 | tcp::test_echo_server(net); 158 | } 159 | } 160 | 161 | fn enumerate_pci(mmconfig_base: *mut u8) { 162 | info!("mmconfig_base = {:#x}", mmconfig_base as usize); 163 | 164 | let mut pci_root = PciRoot::new(unsafe { MmioCam::new(mmconfig_base, Cam::Ecam) }); 165 | for (device_function, info) in pci_root.enumerate_bus(0) { 166 | let (status, command) = pci_root.get_status_command(device_function); 167 | info!( 168 | "Found {} at {}, status {:?} command {:?}", 169 | info, device_function, status, command 170 | ); 171 | if let Some(virtio_type) = virtio_device_type(&info) { 172 | info!(" VirtIO {:?}", virtio_type); 173 | 174 | // Enable the device to use its BARs. 175 | pci_root.set_command( 176 | device_function, 177 | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, 178 | ); 179 | dump_bar_contents(&mut pci_root, device_function, 4); 180 | 181 | let mut transport = 182 | PciTransport::new::(&mut pci_root, device_function).unwrap(); 183 | info!( 184 | "Detected virtio PCI device with device type {:?}, features {:#018x}", 185 | transport.device_type(), 186 | transport.read_device_features(), 187 | ); 188 | virtio_device(transport); 189 | } 190 | } 191 | } 192 | 193 | fn dump_bar_contents( 194 | root: &mut PciRoot, 195 | device_function: DeviceFunction, 196 | bar_index: u8, 197 | ) { 198 | let bar_info = root.bar_info(device_function, bar_index).unwrap(); 199 | trace!("Dumping bar {}: {:#x?}", bar_index, bar_info); 200 | if let Some(BarInfo::Memory { address, size, .. }) = bar_info { 201 | let start = address as *const u8; 202 | unsafe { 203 | let mut buf = [0u8; 32]; 204 | for i in 0..size / 32 { 205 | let ptr = start.add(i as usize * 32); 206 | core::ptr::copy(ptr, buf.as_mut_ptr(), 32); 207 | if buf.iter().any(|b| *b != 0xff) { 208 | trace!(" {:?}: {:x?}", ptr, buf); 209 | } 210 | } 211 | } 212 | } 213 | trace!("End of dump"); 214 | } 215 | 216 | #[panic_handler] 217 | fn panic(info: &core::panic::PanicInfo) -> ! { 218 | error!("{}", info); 219 | system_off() 220 | } 221 | -------------------------------------------------------------------------------- /examples/x86_64/src/multiboot.S: -------------------------------------------------------------------------------- 1 | # Bootstrapping from 32-bit with the Multiboot specification. 2 | # See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html 3 | 4 | .section .text.entry 5 | .code32 6 | .global _start 7 | _start: 8 | mov ecx, {mb_magic} 9 | cmp ecx, eax 10 | jnz 1f 11 | mov edi, ebx # arg1: multiboot info 12 | jmp entry32 13 | 1: hlt 14 | jmp 1b 15 | 16 | .balign 4 17 | .type multiboot_header, STT_OBJECT 18 | multiboot_header: 19 | .int {mb_hdr_magic} # magic: 0x1BADB002 20 | .int {mb_hdr_flags} # flags 21 | .int -({mb_hdr_magic} + {mb_hdr_flags}) # checksum 22 | .int multiboot_header - {offset} # header_addr 23 | .int start - {offset} # load_addr 24 | .int edata - {offset} # load_end 25 | .int ebss - {offset} # bss_end_addr 26 | .int _start - {offset} # entry_addr 27 | 28 | .code32 29 | entry32: 30 | lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT 31 | 32 | # set data segment selectors 33 | mov ax, 0x18 34 | mov ss, ax 35 | mov ds, ax 36 | mov es, ax 37 | mov fs, ax 38 | mov gs, ax 39 | 40 | # set PAE, PGE bit in CR4 41 | mov eax, {cr4} 42 | mov cr4, eax 43 | 44 | # load the temporary page table 45 | lea eax, [.Ltmp_pml4 - {offset}] 46 | mov cr3, eax 47 | 48 | # set LME, NXE bit in IA32_EFER 49 | mov ecx, {efer_msr} 50 | mov edx, 0 51 | mov eax, {efer} 52 | wrmsr 53 | 54 | # set protected mode, write protect, paging bit in CR0 55 | mov eax, {cr0} 56 | mov cr0, eax 57 | 58 | ljmp 0x10, offset entry64 - {offset} # 0x10 is code64 segment 59 | 60 | .code64 61 | entry64: 62 | # clear segment selectors 63 | xor ax, ax 64 | mov ss, ax 65 | mov ds, ax 66 | mov es, ax 67 | mov fs, ax 68 | mov gs, ax 69 | 70 | # set RSP to boot stack top 71 | movabs rsp, offset boot_stack_top 72 | 73 | # call main(magic, mbi) 74 | movabs rax, offset {entry} 75 | call rax 76 | jmp .Lhlt 77 | 78 | .Lhlt: 79 | hlt 80 | jmp .Lhlt 81 | 82 | .section .rodata 83 | .balign 8 84 | .Ltmp_gdt_desc: 85 | .short .Ltmp_gdt_end - .Ltmp_gdt - 1 # limit 86 | .long .Ltmp_gdt - {offset} # base 87 | 88 | .section .data 89 | .balign 16 90 | .Ltmp_gdt: 91 | .quad 0x0000000000000000 # 0x00: null 92 | .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) 93 | .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) 94 | .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) 95 | .Ltmp_gdt_end: 96 | 97 | .balign 4096 98 | .Ltmp_pml4: 99 | # 0x0000_0000 ~ 0xffff_ffff 100 | .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) 101 | .zero 8 * 511 102 | 103 | .Ltmp_pdpt_low: 104 | .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) 105 | .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) 106 | .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) 107 | .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) 108 | .zero 8 * 508 109 | 110 | .section .bss.stack 111 | .balign 4096 112 | .boot_stack: 113 | .space {boot_stack_size} 114 | boot_stack_top: 115 | -------------------------------------------------------------------------------- /examples/x86_64/src/tcp.rs: -------------------------------------------------------------------------------- 1 | //! Simple echo server over TCP. 2 | //! 3 | //! Ref: 4 | 5 | use alloc::{borrow::ToOwned, rc::Rc, vec, vec::Vec}; 6 | use core::{cell::RefCell, str::FromStr}; 7 | 8 | use smoltcp::iface::{Config, Interface, SocketSet}; 9 | use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; 10 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; 11 | use smoltcp::{socket::tcp, time::Instant}; 12 | use virtio_drivers::device::net::{RxBuffer, VirtIONet}; 13 | use virtio_drivers::{transport::Transport, Error}; 14 | 15 | use super::{HalImpl, NET_QUEUE_SIZE}; 16 | 17 | type DeviceImpl = VirtIONet; 18 | 19 | const IP: &str = "10.0.2.15"; // QEMU user networking default IP 20 | const GATEWAY: &str = "10.0.2.2"; // QEMU user networking gateway 21 | const PORT: u16 = 5555; 22 | 23 | struct DeviceWrapper { 24 | inner: Rc>>, 25 | } 26 | 27 | impl DeviceWrapper { 28 | fn new(dev: DeviceImpl) -> Self { 29 | DeviceWrapper { 30 | inner: Rc::new(RefCell::new(dev)), 31 | } 32 | } 33 | 34 | fn mac_address(&self) -> EthernetAddress { 35 | EthernetAddress(self.inner.borrow().mac_address()) 36 | } 37 | } 38 | 39 | impl Device for DeviceWrapper { 40 | type RxToken<'a> 41 | = VirtioRxToken 42 | where 43 | Self: 'a; 44 | type TxToken<'a> 45 | = VirtioTxToken 46 | where 47 | Self: 'a; 48 | 49 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 50 | match self.inner.borrow_mut().receive() { 51 | Ok(buf) => Some(( 52 | VirtioRxToken(self.inner.clone(), buf), 53 | VirtioTxToken(self.inner.clone()), 54 | )), 55 | Err(Error::NotReady) => None, 56 | Err(err) => panic!("receive failed: {}", err), 57 | } 58 | } 59 | 60 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 61 | Some(VirtioTxToken(self.inner.clone())) 62 | } 63 | 64 | fn capabilities(&self) -> DeviceCapabilities { 65 | let mut caps = DeviceCapabilities::default(); 66 | caps.max_transmission_unit = 1536; 67 | caps.max_burst_size = Some(1); 68 | caps.medium = Medium::Ethernet; 69 | caps 70 | } 71 | } 72 | 73 | struct VirtioRxToken(Rc>>, RxBuffer); 74 | struct VirtioTxToken(Rc>>); 75 | 76 | impl RxToken for VirtioRxToken { 77 | fn consume(self, f: F) -> R 78 | where 79 | F: FnOnce(&mut [u8]) -> R, 80 | { 81 | let mut rx_buf = self.1; 82 | trace!( 83 | "RECV {} bytes: {:02X?}", 84 | rx_buf.packet_len(), 85 | rx_buf.packet() 86 | ); 87 | let result = f(rx_buf.packet_mut()); 88 | self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); 89 | result 90 | } 91 | } 92 | 93 | impl TxToken for VirtioTxToken { 94 | fn consume(self, len: usize, f: F) -> R 95 | where 96 | F: FnOnce(&mut [u8]) -> R, 97 | { 98 | let mut dev = self.0.borrow_mut(); 99 | let mut tx_buf = dev.new_tx_buffer(len); 100 | let result = f(tx_buf.packet_mut()); 101 | trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); 102 | dev.send(tx_buf).unwrap(); 103 | result 104 | } 105 | } 106 | 107 | pub fn test_echo_server(dev: DeviceImpl) { 108 | let mut device = DeviceWrapper::new(dev); 109 | 110 | // Create interface 111 | let mut config = Config::new(); 112 | config.random_seed = 0x2333; 113 | if device.capabilities().medium == Medium::Ethernet { 114 | config.hardware_addr = Some(device.mac_address().into()); 115 | } 116 | 117 | let mut iface = Interface::new(config, &mut device); 118 | iface.update_ip_addrs(|ip_addrs| { 119 | ip_addrs 120 | .push(IpCidr::new(IpAddress::from_str(IP).unwrap(), 24)) 121 | .unwrap(); 122 | }); 123 | 124 | iface 125 | .routes_mut() 126 | .add_default_ipv4_route(Ipv4Address::from_str(GATEWAY).unwrap()) 127 | .unwrap(); 128 | 129 | // Create sockets 130 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 131 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 132 | let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); 133 | 134 | let mut sockets = SocketSet::new(vec![]); 135 | let tcp_handle = sockets.add(tcp_socket); 136 | 137 | info!("start a reverse echo server..."); 138 | let mut tcp_active = false; 139 | loop { 140 | let timestamp = 141 | unsafe { Instant::from_micros_const(core::arch::x86_64::_rdtsc() as i64 / 2_500) }; 142 | iface.poll(timestamp, &mut device, &mut sockets); 143 | 144 | // tcp:PORT: echo with reverse 145 | let socket = sockets.get_mut::(tcp_handle); 146 | if !socket.is_open() { 147 | info!("listening on port {}...", PORT); 148 | socket.listen(PORT).unwrap(); 149 | } 150 | 151 | if socket.is_active() && !tcp_active { 152 | info!("tcp:{} connected", PORT); 153 | } else if !socket.is_active() && tcp_active { 154 | info!("tcp:{} disconnected", PORT); 155 | } 156 | tcp_active = socket.is_active(); 157 | 158 | if socket.may_recv() { 159 | let data = socket 160 | .recv(|buffer| { 161 | let recvd_len = buffer.len(); 162 | if !buffer.is_empty() { 163 | debug!("tcp:{} recv {} bytes: {:?}", PORT, recvd_len, buffer); 164 | let mut lines = buffer 165 | .split(|&b| b == b'\n') 166 | .map(ToOwned::to_owned) 167 | .collect::>(); 168 | for line in lines.iter_mut() { 169 | line.reverse(); 170 | } 171 | let data = lines.join(&b'\n'); 172 | (recvd_len, data) 173 | } else { 174 | (0, vec![]) 175 | } 176 | }) 177 | .unwrap(); 178 | if socket.can_send() && !data.is_empty() { 179 | debug!("tcp:{} send data: {:?}", PORT, data); 180 | socket.send_slice(&data[..]).unwrap(); 181 | } 182 | } else if socket.may_send() { 183 | info!("tcp:{} close", PORT); 184 | socket.close(); 185 | break; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /examples/x86_64/src/trap.rs: -------------------------------------------------------------------------------- 1 | use x86_64::set_general_handler; 2 | use x86_64::structures::idt::{ExceptionVector, InterruptDescriptorTable, InterruptStackFrame}; 3 | 4 | static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); 5 | 6 | fn trap_handler(isf: InterruptStackFrame, index: u8, error_code: Option) { 7 | match index { 8 | x if x == ExceptionVector::Page as u8 => { 9 | let cr2 = x86_64::registers::control::Cr2::read(); 10 | panic!( 11 | "#PF at {:#x}, fault_vaddr={:#x}, err_code={:#x?}", 12 | isf.instruction_pointer, cr2, error_code 13 | ); 14 | } 15 | _ => { 16 | panic!( 17 | "Unhandled exception {} (error_code = {:#x?}) at {:#x}:\n{:#x?}", 18 | index, error_code, isf.instruction_pointer, isf 19 | ); 20 | } 21 | } 22 | } 23 | 24 | pub fn init() { 25 | unsafe { 26 | set_general_handler!(&mut IDT, trap_handler); 27 | IDT.load(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "clippy"] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Types and macros for VirtIO device configuration space. 2 | 3 | use crate::{transport::Transport, Error}; 4 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 5 | 6 | /// A configuration space register from which the driver can only read. 7 | #[derive(Default, FromBytes, Immutable, IntoBytes)] 8 | #[repr(transparent)] 9 | pub struct ReadOnly(pub T); 10 | 11 | impl ReadOnly { 12 | /// Constructs a new instance for testing. 13 | pub const fn new(value: T) -> Self { 14 | Self(value) 15 | } 16 | } 17 | 18 | /// A configuration space register to which the driver can only write. 19 | #[derive(Default, FromBytes, Immutable, IntoBytes)] 20 | #[repr(transparent)] 21 | pub struct WriteOnly(pub T); 22 | 23 | impl WriteOnly { 24 | /// Constructs a new instance for testing. 25 | pub const fn new(value: T) -> Self { 26 | Self(value) 27 | } 28 | } 29 | 30 | /// A configuration space register which the driver may both read and write. 31 | #[derive(Default, FromBytes, Immutable, IntoBytes)] 32 | #[repr(transparent)] 33 | pub struct ReadWrite(pub T); 34 | 35 | impl ReadWrite { 36 | /// Constructs a new instance for testing. 37 | pub const fn new(value: T) -> Self { 38 | Self(value) 39 | } 40 | } 41 | 42 | /// Marker trait for configuration space registers from which the driver may read. 43 | pub trait ConfigReadable {} 44 | 45 | /// Marker trait for configuration space registers to which the driver may write. 46 | pub trait ConfigWritable {} 47 | 48 | impl ConfigReadable for ReadOnly {} 49 | impl ConfigReadable for ReadWrite {} 50 | impl ConfigWritable for ReadWrite {} 51 | impl ConfigWritable for WriteOnly {} 52 | 53 | /// Wrapper for `Transport::read_config_space`` with an extra dummy parameter to force the correct 54 | /// type to be inferred. 55 | #[inline(always)] 56 | pub(crate) fn read_help( 57 | transport: &T, 58 | offset: usize, 59 | _dummy_r: Option, 60 | ) -> Result 61 | where 62 | T: Transport, 63 | V: FromBytes + IntoBytes, 64 | R: ConfigReadable, 65 | { 66 | transport.read_config_space(offset) 67 | } 68 | 69 | /// Wrapper for Transport::write_config_space with an extra dummy parameter to force the correct 70 | /// type to be inferred. 71 | #[inline(always)] 72 | pub(crate) fn write_help( 73 | transport: &mut T, 74 | offset: usize, 75 | value: V, 76 | _dummy_w: Option, 77 | ) -> Result<(), Error> 78 | where 79 | T: Transport, 80 | V: Immutable + IntoBytes, 81 | W: ConfigWritable, 82 | { 83 | transport.write_config_space(offset, value) 84 | } 85 | 86 | /// Reads the given field of the given struct from the device config space via the given transport. 87 | macro_rules! read_config { 88 | ($transport:expr, $struct:ty, $field:ident) => {{ 89 | let dummy_struct: Option<$struct> = None; 90 | let dummy_field = dummy_struct.map(|s| s.$field); 91 | crate::config::read_help( 92 | &$transport, 93 | core::mem::offset_of!($struct, $field), 94 | dummy_field, 95 | ) 96 | }}; 97 | } 98 | 99 | /// Writes the given field of the given struct from the device config space via the given transport. 100 | macro_rules! write_config { 101 | ($transport:expr, $struct:ty, $field:ident, $value:expr) => {{ 102 | let dummy_struct: Option<$struct> = None; 103 | let dummy_field = dummy_struct.map(|s| s.$field); 104 | crate::config::write_help( 105 | &mut $transport, 106 | core::mem::offset_of!($struct, $field), 107 | $value, 108 | dummy_field, 109 | ) 110 | }}; 111 | } 112 | 113 | pub(crate) use read_config; 114 | pub(crate) use write_config; 115 | -------------------------------------------------------------------------------- /src/device/common.rs: -------------------------------------------------------------------------------- 1 | //! Common part shared across all the devices. 2 | 3 | use bitflags::bitflags; 4 | 5 | bitflags! { 6 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 7 | pub(crate) struct Feature: u64 { 8 | // device independent 9 | const NOTIFY_ON_EMPTY = 1 << 24; // legacy 10 | const ANY_LAYOUT = 1 << 27; // legacy 11 | const RING_INDIRECT_DESC = 1 << 28; 12 | const RING_EVENT_IDX = 1 << 29; 13 | const UNUSED = 1 << 30; // legacy 14 | const VERSION_1 = 1 << 32; // detect legacy 15 | 16 | // since virtio v1.1 17 | const ACCESS_PLATFORM = 1 << 33; 18 | const RING_PACKED = 1 << 34; 19 | const IN_ORDER = 1 << 35; 20 | const ORDER_PLATFORM = 1 << 36; 21 | const SR_IOV = 1 << 37; 22 | const NOTIFICATION_DATA = 1 << 38; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/device/console/embedded_io.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of `embedded-io` traits for `VirtIOConsole`. 2 | 3 | use super::VirtIOConsole; 4 | use crate::{transport::Transport, Error, Hal}; 5 | use core::cmp::min; 6 | use embedded_io::{BufRead, ErrorType, Read, ReadReady, Write}; 7 | 8 | impl ErrorType for VirtIOConsole { 9 | type Error = Error; 10 | } 11 | 12 | impl Write for VirtIOConsole { 13 | fn write(&mut self, buf: &[u8]) -> Result { 14 | if buf.is_empty() { 15 | Ok(0) 16 | } else { 17 | self.send_bytes(buf)?; 18 | Ok(buf.len()) 19 | } 20 | } 21 | 22 | fn flush(&mut self) -> Result<(), Self::Error> { 23 | // We don't buffer writes, so nothing to do here. 24 | Ok(()) 25 | } 26 | } 27 | 28 | impl ReadReady for VirtIOConsole { 29 | fn read_ready(&mut self) -> Result { 30 | self.finish_receive()?; 31 | Ok(self.cursor != self.pending_len) 32 | } 33 | } 34 | 35 | impl Read for VirtIOConsole { 36 | fn read(&mut self, buf: &mut [u8]) -> Result { 37 | if buf.is_empty() { 38 | Ok(0) 39 | } else { 40 | self.wait_for_receive()?; 41 | let read_length = min(buf.len(), self.pending_len - self.cursor); 42 | buf[..read_length] 43 | .copy_from_slice(&self.queue_buf_rx[self.cursor..self.cursor + read_length]); 44 | Ok(read_length) 45 | } 46 | } 47 | } 48 | 49 | impl BufRead for VirtIOConsole { 50 | fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { 51 | self.wait_for_receive()?; 52 | Ok(&self.queue_buf_rx[self.cursor..self.pending_len]) 53 | } 54 | 55 | fn consume(&mut self, amt: usize) { 56 | assert!(self.cursor + amt <= self.pending_len); 57 | self.cursor += amt; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/device/input.rs: -------------------------------------------------------------------------------- 1 | //! Driver for VirtIO input devices. 2 | 3 | use super::common::Feature; 4 | use crate::config::{read_config, write_config, ReadOnly, WriteOnly}; 5 | use crate::hal::Hal; 6 | use crate::queue::VirtQueue; 7 | use crate::transport::Transport; 8 | use crate::Error; 9 | use alloc::{boxed::Box, string::String}; 10 | use core::cmp::min; 11 | use core::mem::{offset_of, size_of}; 12 | use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; 13 | 14 | /// Virtual human interface devices such as keyboards, mice and tablets. 15 | /// 16 | /// An instance of the virtio device represents one such input device. 17 | /// Device behavior mirrors that of the evdev layer in Linux, 18 | /// making pass-through implementations on top of evdev easy. 19 | pub struct VirtIOInput { 20 | transport: T, 21 | event_queue: VirtQueue, 22 | status_queue: VirtQueue, 23 | event_buf: Box<[InputEvent; 32]>, 24 | } 25 | 26 | impl VirtIOInput { 27 | /// Create a new VirtIO-Input driver. 28 | pub fn new(mut transport: T) -> Result { 29 | let mut event_buf = Box::new([InputEvent::default(); QUEUE_SIZE]); 30 | 31 | let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); 32 | 33 | let mut event_queue = VirtQueue::new( 34 | &mut transport, 35 | QUEUE_EVENT, 36 | negotiated_features.contains(Feature::RING_INDIRECT_DESC), 37 | negotiated_features.contains(Feature::RING_EVENT_IDX), 38 | )?; 39 | let status_queue = VirtQueue::new( 40 | &mut transport, 41 | QUEUE_STATUS, 42 | negotiated_features.contains(Feature::RING_INDIRECT_DESC), 43 | negotiated_features.contains(Feature::RING_EVENT_IDX), 44 | )?; 45 | for (i, event) in event_buf.as_mut().iter_mut().enumerate() { 46 | // SAFETY: The buffer lasts as long as the queue. 47 | let token = unsafe { event_queue.add(&[], &mut [event.as_mut_bytes()])? }; 48 | assert_eq!(token, i as u16); 49 | } 50 | if event_queue.should_notify() { 51 | transport.notify(QUEUE_EVENT); 52 | } 53 | 54 | transport.finish_init(); 55 | 56 | Ok(VirtIOInput { 57 | transport, 58 | event_queue, 59 | status_queue, 60 | event_buf, 61 | }) 62 | } 63 | 64 | /// Acknowledge interrupt and process events. 65 | pub fn ack_interrupt(&mut self) -> bool { 66 | self.transport.ack_interrupt() 67 | } 68 | 69 | /// Pop the pending event. 70 | pub fn pop_pending_event(&mut self) -> Option { 71 | if let Some(token) = self.event_queue.peek_used() { 72 | let event = &mut self.event_buf[token as usize]; 73 | // SAFETY: We are passing the same buffer as we passed to `VirtQueue::add` and it is still valid. 74 | unsafe { 75 | self.event_queue 76 | .pop_used(token, &[], &mut [event.as_mut_bytes()]) 77 | .ok()?; 78 | } 79 | let event_saved = *event; 80 | // requeue 81 | // SAFETY: The buffer lasts as long as the queue. 82 | if let Ok(new_token) = unsafe { self.event_queue.add(&[], &mut [event.as_mut_bytes()]) } 83 | { 84 | // This only works because nothing happen between `pop_used` and `add` that affects 85 | // the list of free descriptors in the queue, so `add` reuses the descriptor which 86 | // was just freed by `pop_used`. 87 | assert_eq!(new_token, token); 88 | if self.event_queue.should_notify() { 89 | self.transport.notify(QUEUE_EVENT); 90 | } 91 | return Some(event_saved); 92 | } 93 | } 94 | None 95 | } 96 | 97 | /// Query a specific piece of information by `select` and `subsel`, and write 98 | /// result to `out`, return the result size. 99 | pub fn query_config_select( 100 | &mut self, 101 | select: InputConfigSelect, 102 | subsel: u8, 103 | out: &mut [u8], 104 | ) -> Result { 105 | write_config!(self.transport, Config, select, select as u8)?; 106 | write_config!(self.transport, Config, subsel, subsel)?; 107 | let size: u8 = read_config!(self.transport, Config, size)?; 108 | // Safe because config points to a valid MMIO region for the config space. 109 | let size_to_copy = min(usize::from(size), out.len()); 110 | for (i, out_item) in out.iter_mut().take(size_to_copy).enumerate() { 111 | *out_item = self 112 | .transport 113 | .read_config_space(offset_of!(Config, data) + i * size_of::())?; 114 | } 115 | 116 | Ok(size) 117 | } 118 | 119 | /// Queries a specific piece of information by `select` and `subsel`, allocates a sufficiently 120 | /// large byte buffer for it, and returns it. 121 | fn query_config_select_alloc( 122 | &mut self, 123 | select: InputConfigSelect, 124 | subsel: u8, 125 | ) -> Result, Error> { 126 | write_config!(self.transport, Config, select, select as u8)?; 127 | write_config!(self.transport, Config, subsel, subsel)?; 128 | let size = usize::from(read_config!(self.transport, Config, size)?); 129 | if size > CONFIG_DATA_MAX_LENGTH { 130 | return Err(Error::IoError); 131 | } 132 | let mut buf = <[u8]>::new_box_zeroed_with_elems(size).unwrap(); 133 | for i in 0..size { 134 | buf[i] = self 135 | .transport 136 | .read_config_space(offset_of!(Config, data) + i * size_of::())?; 137 | } 138 | Ok(buf) 139 | } 140 | 141 | /// Queries a specific piece of information by `select` and `subsel` into a newly-allocated 142 | /// buffer, and tries to convert it to a string. 143 | /// 144 | /// Returns an error if it is not valid UTF-8. 145 | fn query_config_string( 146 | &mut self, 147 | select: InputConfigSelect, 148 | subsel: u8, 149 | ) -> Result { 150 | Ok(String::from_utf8( 151 | self.query_config_select_alloc(select, subsel)?.into(), 152 | )?) 153 | } 154 | 155 | /// Queries and returns the name of the device, or an error if it is not valid UTF-8. 156 | pub fn name(&mut self) -> Result { 157 | self.query_config_string(InputConfigSelect::IdName, 0) 158 | } 159 | 160 | /// Queries and returns the serial number of the device, or an error if it is not valid UTF-8. 161 | pub fn serial_number(&mut self) -> Result { 162 | self.query_config_string(InputConfigSelect::IdSerial, 0) 163 | } 164 | 165 | /// Queries and returns the ID information of the device. 166 | pub fn ids(&mut self) -> Result { 167 | let mut ids = DevIDs::default(); 168 | let size = self.query_config_select(InputConfigSelect::IdDevids, 0, ids.as_mut_bytes())?; 169 | if usize::from(size) == size_of::() { 170 | Ok(ids) 171 | } else { 172 | Err(Error::IoError) 173 | } 174 | } 175 | 176 | /// Queries and returns the input properties of the device. 177 | pub fn prop_bits(&mut self) -> Result, Error> { 178 | self.query_config_select_alloc(InputConfigSelect::PropBits, 0) 179 | } 180 | 181 | /// Queries and returns a bitmap of supported event codes for the given event type. 182 | /// 183 | /// If the event type is not supported an empty slice will be returned. 184 | pub fn ev_bits(&mut self, event_type: u8) -> Result, Error> { 185 | self.query_config_select_alloc(InputConfigSelect::EvBits, event_type) 186 | } 187 | 188 | /// Queries and returns information about the given axis of the device. 189 | pub fn abs_info(&mut self, axis: u8) -> Result { 190 | let mut info = AbsInfo::default(); 191 | let size = 192 | self.query_config_select(InputConfigSelect::AbsInfo, axis, info.as_mut_bytes())?; 193 | if usize::from(size) == size_of::() { 194 | Ok(info) 195 | } else { 196 | Err(Error::IoError) 197 | } 198 | } 199 | } 200 | 201 | // SAFETY: The config space can be accessed from any thread. 202 | unsafe impl Send for VirtIOInput where 203 | VirtQueue: Send 204 | { 205 | } 206 | 207 | // SAFETY: An '&VirtIOInput` can't do anything, all methods take `&mut self`. 208 | unsafe impl Sync for VirtIOInput where 209 | VirtQueue: Sync 210 | { 211 | } 212 | 213 | impl Drop for VirtIOInput { 214 | fn drop(&mut self) { 215 | // Clear any pointers pointing to DMA regions, so the device doesn't try to access them 216 | // after they have been freed. 217 | self.transport.queue_unset(QUEUE_EVENT); 218 | self.transport.queue_unset(QUEUE_STATUS); 219 | } 220 | } 221 | 222 | const CONFIG_DATA_MAX_LENGTH: usize = 128; 223 | 224 | /// Select value used for [`VirtIOInput::query_config_select()`]. 225 | #[repr(u8)] 226 | #[derive(Debug, Clone, Copy)] 227 | pub enum InputConfigSelect { 228 | /// Returns the name of the device, in u.string. subsel is zero. 229 | IdName = 0x01, 230 | /// Returns the serial number of the device, in u.string. subsel is zero. 231 | IdSerial = 0x02, 232 | /// Returns ID information of the device, in u.ids. subsel is zero. 233 | IdDevids = 0x03, 234 | /// Returns input properties of the device, in u.bitmap. subsel is zero. 235 | /// Individual bits in the bitmap correspond to INPUT_PROP_* constants used 236 | /// by the underlying evdev implementation. 237 | PropBits = 0x10, 238 | /// subsel specifies the event type using EV_* constants in the underlying 239 | /// evdev implementation. If size is non-zero the event type is supported 240 | /// and a bitmap of supported event codes is returned in u.bitmap. Individual 241 | /// bits in the bitmap correspond to implementation-defined input event codes, 242 | /// for example keys or pointing device axes. 243 | EvBits = 0x11, 244 | /// subsel specifies the absolute axis using ABS_* constants in the underlying 245 | /// evdev implementation. Information about the axis will be returned in u.abs. 246 | AbsInfo = 0x12, 247 | } 248 | 249 | #[derive(FromBytes, Immutable, IntoBytes)] 250 | #[repr(C)] 251 | struct Config { 252 | select: WriteOnly, 253 | subsel: WriteOnly, 254 | size: ReadOnly, 255 | _reserved: [ReadOnly; 5], 256 | data: [ReadOnly; CONFIG_DATA_MAX_LENGTH], 257 | } 258 | 259 | /// Information about an axis of an input device, typically a joystick. 260 | #[repr(C)] 261 | #[derive(Clone, Debug, Default, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)] 262 | pub struct AbsInfo { 263 | /// The minimum value for the axis. 264 | pub min: u32, 265 | /// The maximum value for the axis. 266 | pub max: u32, 267 | /// The fuzz value used to filter noise from the event stream. 268 | pub fuzz: u32, 269 | /// The size of the dead zone; values less than this will be reported as 0. 270 | pub flat: u32, 271 | /// The resolution for values reported for the axis. 272 | pub res: u32, 273 | } 274 | 275 | /// The identifiers of a VirtIO input device. 276 | #[repr(C)] 277 | #[derive(Clone, Debug, Default, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)] 278 | pub struct DevIDs { 279 | /// The bustype identifier. 280 | pub bustype: u16, 281 | /// The vendor identifier. 282 | pub vendor: u16, 283 | /// The product identifier. 284 | pub product: u16, 285 | /// The version identifier. 286 | pub version: u16, 287 | } 288 | 289 | /// Both queues use the same `virtio_input_event` struct. `type`, `code` and `value` 290 | /// are filled according to the Linux input layer (evdev) interface. 291 | #[repr(C)] 292 | #[derive(Clone, Copy, Debug, Default, FromBytes, Immutable, IntoBytes, KnownLayout)] 293 | pub struct InputEvent { 294 | /// Event type. 295 | pub event_type: u16, 296 | /// Event code. 297 | pub code: u16, 298 | /// Event value. 299 | pub value: u32, 300 | } 301 | 302 | const QUEUE_EVENT: u16 = 0; 303 | const QUEUE_STATUS: u16 = 1; 304 | const SUPPORTED_FEATURES: Feature = Feature::RING_EVENT_IDX.union(Feature::RING_INDIRECT_DESC); 305 | 306 | // a parameter that can change 307 | const QUEUE_SIZE: usize = 32; 308 | 309 | #[cfg(test)] 310 | mod tests { 311 | use super::*; 312 | use crate::{ 313 | hal::fake::FakeHal, 314 | transport::{ 315 | fake::{FakeTransport, QueueStatus, State}, 316 | DeviceType, 317 | }, 318 | }; 319 | use alloc::{sync::Arc, vec}; 320 | use core::convert::TryInto; 321 | use std::sync::Mutex; 322 | 323 | #[test] 324 | fn config() { 325 | const DEFAULT_DATA: ReadOnly = ReadOnly::new(0); 326 | let config_space = Config { 327 | select: WriteOnly::default(), 328 | subsel: WriteOnly::default(), 329 | size: ReadOnly::new(0), 330 | _reserved: Default::default(), 331 | data: [DEFAULT_DATA; 128], 332 | }; 333 | let state = Arc::new(Mutex::new(State::new( 334 | vec![QueueStatus::default(), QueueStatus::default()], 335 | config_space, 336 | ))); 337 | let transport = FakeTransport { 338 | device_type: DeviceType::Block, 339 | max_queue_size: QUEUE_SIZE.try_into().unwrap(), 340 | device_features: 0, 341 | state: state.clone(), 342 | }; 343 | let mut input = VirtIOInput::>::new(transport).unwrap(); 344 | 345 | set_data( 346 | &mut state.lock().unwrap().config_space, 347 | "Test input device".as_bytes(), 348 | ); 349 | assert_eq!(input.name().unwrap(), "Test input device"); 350 | assert_eq!( 351 | state.lock().unwrap().config_space.select.0, 352 | InputConfigSelect::IdName as u8 353 | ); 354 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); 355 | 356 | set_data( 357 | &mut state.lock().unwrap().config_space, 358 | "Serial number".as_bytes(), 359 | ); 360 | assert_eq!(input.serial_number().unwrap(), "Serial number"); 361 | assert_eq!( 362 | state.lock().unwrap().config_space.select.0, 363 | InputConfigSelect::IdSerial as u8 364 | ); 365 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); 366 | 367 | let ids = DevIDs { 368 | bustype: 0x4242, 369 | product: 0x0067, 370 | vendor: 0x1234, 371 | version: 0x4321, 372 | }; 373 | set_data(&mut state.lock().unwrap().config_space, ids.as_bytes()); 374 | assert_eq!(input.ids().unwrap(), ids); 375 | assert_eq!( 376 | state.lock().unwrap().config_space.select.0, 377 | InputConfigSelect::IdDevids as u8 378 | ); 379 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); 380 | 381 | set_data(&mut state.lock().unwrap().config_space, &[0x12, 0x34, 0x56]); 382 | assert_eq!(input.prop_bits().unwrap().as_ref(), &[0x12, 0x34, 0x56]); 383 | assert_eq!( 384 | state.lock().unwrap().config_space.select.0, 385 | InputConfigSelect::PropBits as u8 386 | ); 387 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 0); 388 | 389 | set_data(&mut state.lock().unwrap().config_space, &[0x42, 0x66]); 390 | assert_eq!(input.ev_bits(3).unwrap().as_ref(), &[0x42, 0x66]); 391 | assert_eq!( 392 | state.lock().unwrap().config_space.select.0, 393 | InputConfigSelect::EvBits as u8 394 | ); 395 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 3); 396 | 397 | let abs_info = AbsInfo { 398 | min: 12, 399 | max: 1234, 400 | fuzz: 4, 401 | flat: 10, 402 | res: 2, 403 | }; 404 | set_data(&mut state.lock().unwrap().config_space, abs_info.as_bytes()); 405 | assert_eq!(input.abs_info(5).unwrap(), abs_info); 406 | assert_eq!( 407 | state.lock().unwrap().config_space.select.0, 408 | InputConfigSelect::AbsInfo as u8 409 | ); 410 | assert_eq!(state.lock().unwrap().config_space.subsel.0, 5); 411 | } 412 | 413 | fn set_data(config_space: &mut Config, value: &[u8]) { 414 | config_space.size.0 = value.len().try_into().unwrap(); 415 | for (i, &byte) in value.into_iter().enumerate() { 416 | config_space.data[i].0 = byte; 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/device/mod.rs: -------------------------------------------------------------------------------- 1 | //! Drivers for specific VirtIO devices. 2 | 3 | pub mod blk; 4 | #[cfg(feature = "alloc")] 5 | pub mod console; 6 | #[cfg(feature = "alloc")] 7 | pub mod gpu; 8 | #[cfg(feature = "alloc")] 9 | pub mod input; 10 | 11 | pub mod net; 12 | 13 | pub mod rng; 14 | 15 | pub mod socket; 16 | #[cfg(feature = "alloc")] 17 | pub mod sound; 18 | 19 | pub(crate) mod common; 20 | -------------------------------------------------------------------------------- /src/device/net/dev.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | 3 | use super::net_buf::{RxBuffer, TxBuffer}; 4 | use super::{EthernetAddress, VirtIONetRaw}; 5 | use crate::{hal::Hal, transport::Transport, Error, Result}; 6 | 7 | /// Driver for a VirtIO network device. 8 | /// 9 | /// Unlike [`VirtIONetRaw`], it uses [`RxBuffer`]s for transmission and 10 | /// reception rather than the raw slices. On initialization, it pre-allocates 11 | /// all receive buffers and puts them all in the receive queue. 12 | /// 13 | /// The virtio network device is a virtual ethernet card. 14 | /// 15 | /// It has enhanced rapidly and demonstrates clearly how support for new 16 | /// features are added to an existing device. 17 | /// Empty buffers are placed in one virtqueue for receiving packets, and 18 | /// outgoing packets are enqueued into another for transmission in that order. 19 | /// A third command queue is used to control advanced filtering features. 20 | pub struct VirtIONet { 21 | inner: VirtIONetRaw, 22 | rx_buffers: [Option; QUEUE_SIZE], 23 | } 24 | 25 | impl VirtIONet { 26 | /// Create a new VirtIO-Net driver. 27 | pub fn new(transport: T, buf_len: usize) -> Result { 28 | let mut inner = VirtIONetRaw::new(transport)?; 29 | 30 | const NONE_BUF: Option = None; 31 | let mut rx_buffers = [NONE_BUF; QUEUE_SIZE]; 32 | for (i, rx_buf_place) in rx_buffers.iter_mut().enumerate() { 33 | let mut rx_buf = RxBuffer::new(i, buf_len); 34 | // SAFETY: The buffer lives as long as the queue. 35 | let token = unsafe { inner.receive_begin(rx_buf.as_bytes_mut())? }; 36 | assert_eq!(token, i as u16); 37 | *rx_buf_place = Some(rx_buf); 38 | } 39 | 40 | Ok(VirtIONet { inner, rx_buffers }) 41 | } 42 | 43 | /// Acknowledge interrupt. 44 | pub fn ack_interrupt(&mut self) -> bool { 45 | self.inner.ack_interrupt() 46 | } 47 | 48 | /// Disable interrupts. 49 | pub fn disable_interrupts(&mut self) { 50 | self.inner.disable_interrupts() 51 | } 52 | 53 | /// Enable interrupts. 54 | pub fn enable_interrupts(&mut self) { 55 | self.inner.enable_interrupts() 56 | } 57 | 58 | /// Get MAC address. 59 | pub fn mac_address(&self) -> EthernetAddress { 60 | self.inner.mac_address() 61 | } 62 | 63 | /// Whether can send packet. 64 | pub fn can_send(&self) -> bool { 65 | self.inner.can_send() 66 | } 67 | 68 | /// Whether can receive packet. 69 | pub fn can_recv(&self) -> bool { 70 | self.inner.poll_receive().is_some() 71 | } 72 | 73 | /// Receives a [`RxBuffer`] from network. If currently no data, returns an 74 | /// error with type [`Error::NotReady`]. 75 | /// 76 | /// It will try to pop a buffer that completed data reception in the 77 | /// NIC queue. 78 | pub fn receive(&mut self) -> Result { 79 | if let Some(token) = self.inner.poll_receive() { 80 | let mut rx_buf = self.rx_buffers[token as usize] 81 | .take() 82 | .ok_or(Error::WrongToken)?; 83 | if token != rx_buf.idx { 84 | return Err(Error::WrongToken); 85 | } 86 | 87 | // SAFETY: `token` == `rx_buf.idx`, so we are passing the same 88 | // buffer as we passed to `VirtQueue::add` and it is still valid. 89 | let (_hdr_len, pkt_len) = 90 | unsafe { self.inner.receive_complete(token, rx_buf.as_bytes_mut())? }; 91 | rx_buf.set_packet_len(pkt_len); 92 | Ok(rx_buf) 93 | } else { 94 | Err(Error::NotReady) 95 | } 96 | } 97 | 98 | /// Gives back the ownership of `rx_buf`, and recycles it for next use. 99 | /// 100 | /// It will add the buffer back to the NIC queue. 101 | pub fn recycle_rx_buffer(&mut self, mut rx_buf: RxBuffer) -> Result { 102 | // SAFETY: We take the ownership of `rx_buf` back to `rx_buffers`, 103 | // so it lives as long as the queue. 104 | let new_token = unsafe { self.inner.receive_begin(rx_buf.as_bytes_mut()) }?; 105 | // `rx_buffers[new_token]` is expected to be `None` since it was taken 106 | // away at `Self::receive()` and has not been added back. 107 | if self.rx_buffers[new_token as usize].is_some() { 108 | return Err(Error::WrongToken); 109 | } 110 | rx_buf.idx = new_token; 111 | self.rx_buffers[new_token as usize] = Some(rx_buf); 112 | Ok(()) 113 | } 114 | 115 | /// Allocate a new buffer for transmitting. 116 | pub fn new_tx_buffer(&self, buf_len: usize) -> TxBuffer { 117 | TxBuffer(vec![0; buf_len]) 118 | } 119 | 120 | /// Sends a [`TxBuffer`] to the network, and blocks until the request 121 | /// completed. 122 | pub fn send(&mut self, tx_buf: TxBuffer) -> Result { 123 | self.inner.send(tx_buf.packet()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/device/net/dev_raw.rs: -------------------------------------------------------------------------------- 1 | use super::{Config, EthernetAddress, Features, VirtioNetHdr}; 2 | use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT, SUPPORTED_FEATURES}; 3 | use crate::config::read_config; 4 | use crate::hal::Hal; 5 | use crate::queue::VirtQueue; 6 | use crate::transport::Transport; 7 | use crate::{Error, Result}; 8 | use log::{debug, info, warn}; 9 | use zerocopy::IntoBytes; 10 | 11 | /// Raw driver for a VirtIO network device. 12 | /// 13 | /// This is a raw version of the VirtIONet driver. It provides non-blocking 14 | /// methods for transmitting and receiving raw slices, without the buffer 15 | /// management. For more higher-level functions such as receive buffer backing, 16 | /// see [`VirtIONet`]. 17 | /// 18 | /// [`VirtIONet`]: super::VirtIONet 19 | pub struct VirtIONetRaw { 20 | transport: T, 21 | mac: EthernetAddress, 22 | recv_queue: VirtQueue, 23 | send_queue: VirtQueue, 24 | } 25 | 26 | impl VirtIONetRaw { 27 | /// Create a new VirtIO-Net driver. 28 | pub fn new(mut transport: T) -> Result { 29 | let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); 30 | info!("negotiated_features {:?}", negotiated_features); 31 | 32 | // Read configuration space. 33 | let mac = transport.read_consistent(|| read_config!(transport, Config, mac))?; 34 | let status = read_config!(transport, Config, status)?; 35 | debug!("Got MAC={:02x?}, status={:?}", mac, status); 36 | 37 | let send_queue = VirtQueue::new( 38 | &mut transport, 39 | QUEUE_TRANSMIT, 40 | negotiated_features.contains(Features::RING_INDIRECT_DESC), 41 | negotiated_features.contains(Features::RING_EVENT_IDX), 42 | )?; 43 | let recv_queue = VirtQueue::new( 44 | &mut transport, 45 | QUEUE_RECEIVE, 46 | negotiated_features.contains(Features::RING_INDIRECT_DESC), 47 | negotiated_features.contains(Features::RING_EVENT_IDX), 48 | )?; 49 | 50 | transport.finish_init(); 51 | 52 | Ok(VirtIONetRaw { 53 | transport, 54 | mac, 55 | recv_queue, 56 | send_queue, 57 | }) 58 | } 59 | 60 | /// Acknowledge interrupt. 61 | pub fn ack_interrupt(&mut self) -> bool { 62 | self.transport.ack_interrupt() 63 | } 64 | 65 | /// Disable interrupts. 66 | pub fn disable_interrupts(&mut self) { 67 | self.send_queue.set_dev_notify(false); 68 | self.recv_queue.set_dev_notify(false); 69 | } 70 | 71 | /// Enable interrupts. 72 | pub fn enable_interrupts(&mut self) { 73 | self.send_queue.set_dev_notify(true); 74 | self.recv_queue.set_dev_notify(true); 75 | } 76 | 77 | /// Get MAC address. 78 | pub fn mac_address(&self) -> EthernetAddress { 79 | self.mac 80 | } 81 | 82 | /// Whether can send packet. 83 | pub fn can_send(&self) -> bool { 84 | self.send_queue.available_desc() >= 2 85 | } 86 | 87 | /// Whether the length of the receive buffer is valid. 88 | fn check_rx_buf_len(rx_buf: &[u8]) -> Result<()> { 89 | if rx_buf.len() < MIN_BUFFER_LEN { 90 | warn!("Receive buffer len {} is too small", rx_buf.len()); 91 | Err(Error::InvalidParam) 92 | } else { 93 | Ok(()) 94 | } 95 | } 96 | 97 | /// Whether the length of the transmit buffer is valid. 98 | fn check_tx_buf_len(tx_buf: &[u8]) -> Result<()> { 99 | if tx_buf.len() < NET_HDR_SIZE { 100 | warn!("Transmit buffer len {} is too small", tx_buf.len()); 101 | Err(Error::InvalidParam) 102 | } else { 103 | Ok(()) 104 | } 105 | } 106 | 107 | /// Fill the header of the `buffer` with [`VirtioNetHdr`]. 108 | /// 109 | /// If the `buffer` is not large enough, it returns [`Error::InvalidParam`]. 110 | pub fn fill_buffer_header(&self, buffer: &mut [u8]) -> Result { 111 | if buffer.len() < NET_HDR_SIZE { 112 | return Err(Error::InvalidParam); 113 | } 114 | let header = VirtioNetHdr::default(); 115 | buffer[..NET_HDR_SIZE].copy_from_slice(header.as_bytes()); 116 | Ok(NET_HDR_SIZE) 117 | } 118 | 119 | /// Submits a request to transmit a buffer immediately without waiting for 120 | /// the transmission to complete. 121 | /// 122 | /// It will submit request to the VirtIO net device and return a token 123 | /// identifying the position of the first descriptor in the chain. If there 124 | /// are not enough descriptors to allocate, then it returns 125 | /// [`Error::QueueFull`]. 126 | /// 127 | /// The caller needs to fill the `tx_buf` with a header by calling 128 | /// [`fill_buffer_header`] before transmission. Then it calls [`poll_transmit`] 129 | /// with the returned token to check whether the device has finished handling 130 | /// the request. Once it has, the caller must call [`transmit_complete`] with 131 | /// the same buffer before reading the result (transmitted length). 132 | /// 133 | /// # Safety 134 | /// 135 | /// `tx_buf` is still borrowed by the underlying VirtIO net device even after 136 | /// this method returns. Thus, it is the caller's responsibility to guarantee 137 | /// that they are not accessed before the request is completed in order to 138 | /// avoid data races. 139 | /// 140 | /// [`fill_buffer_header`]: Self::fill_buffer_header 141 | /// [`poll_transmit`]: Self::poll_transmit 142 | /// [`transmit_complete`]: Self::transmit_complete 143 | pub unsafe fn transmit_begin(&mut self, tx_buf: &[u8]) -> Result { 144 | Self::check_tx_buf_len(tx_buf)?; 145 | let token = self.send_queue.add(&[tx_buf], &mut [])?; 146 | if self.send_queue.should_notify() { 147 | self.transport.notify(QUEUE_TRANSMIT); 148 | } 149 | Ok(token) 150 | } 151 | 152 | /// Fetches the token of the next completed transmission request from the 153 | /// used ring and returns it, without removing it from the used ring. If 154 | /// there are no pending completed requests it returns [`None`]. 155 | pub fn poll_transmit(&mut self) -> Option { 156 | self.send_queue.peek_used() 157 | } 158 | 159 | /// Completes a transmission operation which was started by [`transmit_begin`]. 160 | /// Returns number of bytes transmitted. 161 | /// 162 | /// # Safety 163 | /// 164 | /// The same buffer must be passed in again as was passed to 165 | /// [`transmit_begin`] when it returned the token. 166 | /// 167 | /// [`transmit_begin`]: Self::transmit_begin 168 | pub unsafe fn transmit_complete(&mut self, token: u16, tx_buf: &[u8]) -> Result { 169 | let len = self.send_queue.pop_used(token, &[tx_buf], &mut [])?; 170 | Ok(len as usize) 171 | } 172 | 173 | /// Submits a request to receive a buffer immediately without waiting for 174 | /// the reception to complete. 175 | /// 176 | /// It will submit request to the VirtIO net device and return a token 177 | /// identifying the position of the first descriptor in the chain. If there 178 | /// are not enough descriptors to allocate, then it returns 179 | /// [`Error::QueueFull`]. 180 | /// 181 | /// The caller can then call [`poll_receive`] with the returned token to 182 | /// check whether the device has finished handling the request. Once it has, 183 | /// the caller must call [`receive_complete`] with the same buffer before 184 | /// reading the response. 185 | /// 186 | /// # Safety 187 | /// 188 | /// `rx_buf` is still borrowed by the underlying VirtIO net device even after 189 | /// this method returns. Thus, it is the caller's responsibility to guarantee 190 | /// that they are not accessed before the request is completed in order to 191 | /// avoid data races. 192 | /// 193 | /// [`poll_receive`]: Self::poll_receive 194 | /// [`receive_complete`]: Self::receive_complete 195 | pub unsafe fn receive_begin(&mut self, rx_buf: &mut [u8]) -> Result { 196 | Self::check_rx_buf_len(rx_buf)?; 197 | let token = self.recv_queue.add(&[], &mut [rx_buf])?; 198 | if self.recv_queue.should_notify() { 199 | self.transport.notify(QUEUE_RECEIVE); 200 | } 201 | Ok(token) 202 | } 203 | 204 | /// Fetches the token of the next completed reception request from the 205 | /// used ring and returns it, without removing it from the used ring. If 206 | /// there are no pending completed requests it returns [`None`]. 207 | pub fn poll_receive(&self) -> Option { 208 | self.recv_queue.peek_used() 209 | } 210 | 211 | /// Completes a transmission operation which was started by [`receive_begin`]. 212 | /// 213 | /// After completion, the `rx_buf` will contain a header followed by the 214 | /// received packet. It returns the length of the header and the length of 215 | /// the packet. 216 | /// 217 | /// # Safety 218 | /// 219 | /// The same buffer must be passed in again as was passed to 220 | /// [`receive_begin`] when it returned the token. 221 | /// 222 | /// [`receive_begin`]: Self::receive_begin 223 | pub unsafe fn receive_complete( 224 | &mut self, 225 | token: u16, 226 | rx_buf: &mut [u8], 227 | ) -> Result<(usize, usize)> { 228 | let len = self.recv_queue.pop_used(token, &[], &mut [rx_buf])? as usize; 229 | let packet_len = len.checked_sub(NET_HDR_SIZE).ok_or(Error::IoError)?; 230 | Ok((NET_HDR_SIZE, packet_len)) 231 | } 232 | 233 | /// Sends a packet to the network, and blocks until the request completed. 234 | pub fn send(&mut self, tx_buf: &[u8]) -> Result { 235 | let header = VirtioNetHdr::default(); 236 | if tx_buf.is_empty() { 237 | // Special case sending an empty packet, to avoid adding an empty buffer to the 238 | // virtqueue. 239 | self.send_queue.add_notify_wait_pop( 240 | &[header.as_bytes()], 241 | &mut [], 242 | &mut self.transport, 243 | )?; 244 | } else { 245 | self.send_queue.add_notify_wait_pop( 246 | &[header.as_bytes(), tx_buf], 247 | &mut [], 248 | &mut self.transport, 249 | )?; 250 | } 251 | Ok(()) 252 | } 253 | 254 | /// Blocks and waits for a packet to be received. 255 | /// 256 | /// After completion, the `rx_buf` will contain a header followed by the 257 | /// received packet. It returns the length of the header and the length of 258 | /// the packet. 259 | pub fn receive_wait(&mut self, rx_buf: &mut [u8]) -> Result<(usize, usize)> { 260 | // SAFETY: After calling `receive_begin`, `rx_buf` is not accessed 261 | // until calling `receive_complete` when the request is complete. 262 | let token = unsafe { self.receive_begin(rx_buf)? }; 263 | while self.poll_receive().is_none() { 264 | core::hint::spin_loop(); 265 | } 266 | // SAFETY: This `rx_buf` is the same one passed to `receive_begin`. 267 | unsafe { self.receive_complete(token, rx_buf) } 268 | } 269 | } 270 | 271 | impl Drop for VirtIONetRaw { 272 | fn drop(&mut self) { 273 | // Clear any pointers pointing to DMA regions, so the device doesn't try to access them 274 | // after they have been freed. 275 | self.transport.queue_unset(QUEUE_RECEIVE); 276 | self.transport.queue_unset(QUEUE_TRANSMIT); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/device/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! Driver for VirtIO network devices. 2 | 3 | #[cfg(feature = "alloc")] 4 | mod dev; 5 | mod dev_raw; 6 | #[cfg(feature = "alloc")] 7 | mod net_buf; 8 | 9 | pub use self::dev_raw::VirtIONetRaw; 10 | #[cfg(feature = "alloc")] 11 | pub use self::{dev::VirtIONet, net_buf::RxBuffer, net_buf::TxBuffer}; 12 | 13 | use crate::config::ReadOnly; 14 | use bitflags::bitflags; 15 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 16 | 17 | const MAX_BUFFER_LEN: usize = 65535; 18 | const MIN_BUFFER_LEN: usize = 1526; 19 | const NET_HDR_SIZE: usize = core::mem::size_of::(); 20 | 21 | bitflags! { 22 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 23 | struct Features: u64 { 24 | /// Device handles packets with partial checksum. 25 | /// This "checksum offload" is a common feature on modern network cards. 26 | const CSUM = 1 << 0; 27 | /// Driver handles packets with partial checksum. 28 | const GUEST_CSUM = 1 << 1; 29 | /// Control channel offloads reconfiguration support. 30 | const CTRL_GUEST_OFFLOADS = 1 << 2; 31 | /// Device maximum MTU reporting is supported. 32 | /// 33 | /// If offered by the device, device advises driver about the value of 34 | /// its maximum MTU. If negotiated, the driver uses mtu as the maximum 35 | /// MTU value. 36 | const MTU = 1 << 3; 37 | /// Device has given MAC address. 38 | const MAC = 1 << 5; 39 | /// Device handles packets with any GSO type. (legacy) 40 | const GSO = 1 << 6; 41 | /// Driver can receive TSOv4. 42 | const GUEST_TSO4 = 1 << 7; 43 | /// Driver can receive TSOv6. 44 | const GUEST_TSO6 = 1 << 8; 45 | /// Driver can receive TSO with ECN. 46 | const GUEST_ECN = 1 << 9; 47 | /// Driver can receive UFO. 48 | const GUEST_UFO = 1 << 10; 49 | /// Device can receive TSOv4. 50 | const HOST_TSO4 = 1 << 11; 51 | /// Device can receive TSOv6. 52 | const HOST_TSO6 = 1 << 12; 53 | /// Device can receive TSO with ECN. 54 | const HOST_ECN = 1 << 13; 55 | /// Device can receive UFO. 56 | const HOST_UFO = 1 << 14; 57 | /// Driver can merge receive buffers. 58 | const MRG_RXBUF = 1 << 15; 59 | /// Configuration status field is available. 60 | const STATUS = 1 << 16; 61 | /// Control channel is available. 62 | const CTRL_VQ = 1 << 17; 63 | /// Control channel RX mode support. 64 | const CTRL_RX = 1 << 18; 65 | /// Control channel VLAN filtering. 66 | const CTRL_VLAN = 1 << 19; 67 | /// Device supports VIRTIO_NET_CTRL_RX_ALLUNI, VIRTIO_NET_CTRL_RX_NOMULTI, 68 | /// VIRTIO_NET_CTRL_RX_NOUNI and VIRTIO_NET_CTRL_RX_NOBCAST. 69 | const CTRL_RX_EXTRA = 1 << 20; 70 | /// Driver can send gratuitous packets. 71 | const GUEST_ANNOUNCE = 1 << 21; 72 | /// Device supports multiqueue with automatic receive steering. 73 | const MQ = 1 << 22; 74 | /// Set MAC address through control channel. 75 | const CTL_MAC_ADDR = 1 << 23; 76 | 77 | // device independent 78 | const RING_INDIRECT_DESC = 1 << 28; 79 | const RING_EVENT_IDX = 1 << 29; 80 | const VERSION_1 = 1 << 32; // legacy 81 | } 82 | } 83 | 84 | #[derive( 85 | Copy, Clone, Debug, Default, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq, 86 | )] 87 | #[repr(transparent)] 88 | struct Status(u16); 89 | 90 | bitflags! { 91 | impl Status: u16 { 92 | const LINK_UP = 1; 93 | const ANNOUNCE = 2; 94 | } 95 | } 96 | 97 | bitflags! { 98 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 99 | struct InterruptStatus : u32 { 100 | const USED_RING_UPDATE = 1 << 0; 101 | const CONFIGURATION_CHANGE = 1 << 1; 102 | } 103 | } 104 | 105 | #[repr(C)] 106 | struct Config { 107 | mac: ReadOnly, 108 | status: ReadOnly, 109 | max_virtqueue_pairs: ReadOnly, 110 | mtu: ReadOnly, 111 | } 112 | 113 | type EthernetAddress = [u8; 6]; 114 | 115 | /// VirtIO 5.1.6 Device Operation: 116 | /// 117 | /// Packets are transmitted by placing them in the transmitq1. . .transmitqN, 118 | /// and buffers for incoming packets are placed in the receiveq1. . .receiveqN. 119 | /// In each case, the packet itself is preceded by a header. 120 | #[repr(C)] 121 | #[derive(Debug, Default, FromBytes, Immutable, IntoBytes, KnownLayout)] 122 | pub struct VirtioNetHdr { 123 | flags: Flags, 124 | gso_type: GsoType, 125 | hdr_len: u16, // cannot rely on this 126 | gso_size: u16, 127 | csum_start: u16, 128 | csum_offset: u16, 129 | // num_buffers: u16, // only available when the feature MRG_RXBUF is negotiated. 130 | // payload starts from here 131 | } 132 | 133 | #[derive( 134 | IntoBytes, Copy, Clone, Debug, Default, Eq, FromBytes, Immutable, KnownLayout, PartialEq, 135 | )] 136 | #[repr(transparent)] 137 | struct Flags(u8); 138 | 139 | bitflags! { 140 | impl Flags: u8 { 141 | const NEEDS_CSUM = 1; 142 | const DATA_VALID = 2; 143 | const RSC_INFO = 4; 144 | } 145 | } 146 | 147 | #[repr(transparent)] 148 | #[derive( 149 | IntoBytes, Debug, Copy, Clone, Default, Eq, FromBytes, Immutable, KnownLayout, PartialEq, 150 | )] 151 | struct GsoType(u8); 152 | 153 | impl GsoType { 154 | const NONE: GsoType = GsoType(0); 155 | const TCPV4: GsoType = GsoType(1); 156 | const UDP: GsoType = GsoType(3); 157 | const TCPV6: GsoType = GsoType(4); 158 | const ECN: GsoType = GsoType(0x80); 159 | } 160 | 161 | const QUEUE_RECEIVE: u16 = 0; 162 | const QUEUE_TRANSMIT: u16 = 1; 163 | const SUPPORTED_FEATURES: Features = Features::MAC 164 | .union(Features::STATUS) 165 | .union(Features::RING_EVENT_IDX) 166 | .union(Features::RING_INDIRECT_DESC); 167 | -------------------------------------------------------------------------------- /src/device/net/net_buf.rs: -------------------------------------------------------------------------------- 1 | use super::{VirtioNetHdr, NET_HDR_SIZE}; 2 | use alloc::{vec, vec::Vec}; 3 | use core::{convert::TryInto, mem::size_of}; 4 | use zerocopy::{FromBytes, IntoBytes}; 5 | 6 | /// A buffer used for transmitting. 7 | pub struct TxBuffer(pub(crate) Vec); 8 | 9 | /// A buffer used for receiving. 10 | pub struct RxBuffer { 11 | pub(crate) buf: Vec, // for alignment 12 | pub(crate) packet_len: usize, 13 | pub(crate) idx: u16, 14 | } 15 | 16 | impl TxBuffer { 17 | /// Constructs the buffer from the given slice. 18 | pub fn from(buf: &[u8]) -> Self { 19 | Self(Vec::from(buf)) 20 | } 21 | 22 | /// Returns the network packet length. 23 | pub fn packet_len(&self) -> usize { 24 | self.0.len() 25 | } 26 | 27 | /// Returns the network packet as a slice. 28 | pub fn packet(&self) -> &[u8] { 29 | self.0.as_slice() 30 | } 31 | 32 | /// Returns the network packet as a mutable slice. 33 | pub fn packet_mut(&mut self) -> &mut [u8] { 34 | self.0.as_mut_slice() 35 | } 36 | } 37 | 38 | impl RxBuffer { 39 | /// Allocates a new buffer with length `buf_len`. 40 | pub(crate) fn new(idx: usize, buf_len: usize) -> Self { 41 | Self { 42 | buf: vec![0; buf_len / size_of::()], 43 | packet_len: 0, 44 | idx: idx.try_into().unwrap(), 45 | } 46 | } 47 | 48 | /// Set the network packet length. 49 | pub(crate) fn set_packet_len(&mut self, packet_len: usize) { 50 | self.packet_len = packet_len 51 | } 52 | 53 | /// Returns the network packet length (witout header). 54 | pub const fn packet_len(&self) -> usize { 55 | self.packet_len 56 | } 57 | 58 | /// Returns all data in the buffer, including both the header and the packet. 59 | pub fn as_bytes(&self) -> &[u8] { 60 | self.buf.as_bytes() 61 | } 62 | 63 | /// Returns all data in the buffer with the mutable reference, 64 | /// including both the header and the packet. 65 | pub fn as_bytes_mut(&mut self) -> &mut [u8] { 66 | self.buf.as_mut_bytes() 67 | } 68 | 69 | /// Returns the reference of the header. 70 | pub fn header(&self) -> &VirtioNetHdr { 71 | FromBytes::ref_from_prefix(self.as_bytes()).unwrap().0 72 | } 73 | 74 | /// Returns the network packet as a slice. 75 | pub fn packet(&self) -> &[u8] { 76 | &self.buf.as_bytes()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] 77 | } 78 | 79 | /// Returns the network packet as a mutable slice. 80 | pub fn packet_mut(&mut self) -> &mut [u8] { 81 | &mut self.buf.as_mut_bytes()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/device/rng.rs: -------------------------------------------------------------------------------- 1 | //! Driver for VirtIO random number generator devices. 2 | use super::common::Feature; 3 | use crate::{queue::VirtQueue, transport::Transport, Hal, Result}; 4 | 5 | // VirtioRNG only uses one queue 6 | const QUEUE_IDX: u16 = 0; 7 | const QUEUE_SIZE: usize = 8; 8 | const SUPPORTED_FEATURES: Feature = Feature::RING_INDIRECT_DESC.union(Feature::RING_EVENT_IDX); 9 | 10 | /// Driver for a VirtIO random number generator device. 11 | pub struct VirtIORng { 12 | transport: T, 13 | queue: VirtQueue, 14 | } 15 | 16 | impl VirtIORng { 17 | /// Create a new driver with the given transport. 18 | pub fn new(mut transport: T) -> Result { 19 | let feat = transport.begin_init(SUPPORTED_FEATURES); 20 | let queue = VirtQueue::new( 21 | &mut transport, 22 | QUEUE_IDX, 23 | feat.contains(Feature::RING_INDIRECT_DESC), 24 | feat.contains(Feature::RING_EVENT_IDX), 25 | )?; 26 | transport.finish_init(); 27 | Ok(Self { transport, queue }) 28 | } 29 | 30 | /// Request random bytes from the device to be stored into `dst`. 31 | pub fn request_entropy(&mut self, dst: &mut [u8]) -> Result { 32 | let num = self 33 | .queue 34 | .add_notify_wait_pop(&[], &mut [dst], &mut self.transport)?; 35 | Ok(num as usize) 36 | } 37 | 38 | /// Enable interrupts. 39 | pub fn enable_interrupts(&mut self) { 40 | self.queue.set_dev_notify(true); 41 | } 42 | 43 | /// Disable interrupts. 44 | pub fn disable_interrupts(&mut self) { 45 | self.queue.set_dev_notify(false); 46 | } 47 | 48 | /// Acknowledge interrupt. 49 | pub fn ack_interrupt(&mut self) -> bool { 50 | self.transport.ack_interrupt() 51 | } 52 | } 53 | 54 | impl Drop for VirtIORng { 55 | fn drop(&mut self) { 56 | self.transport.queue_unset(QUEUE_IDX); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/device/socket/error.rs: -------------------------------------------------------------------------------- 1 | //! This module contain the error from the VirtIO socket driver. 2 | 3 | use core::result; 4 | use thiserror::Error; 5 | 6 | /// The error type of VirtIO socket driver. 7 | #[derive(Copy, Clone, Debug, Eq, Error, PartialEq)] 8 | pub enum SocketError { 9 | /// There is an existing connection. 10 | #[error("There is an existing connection. Please close the current connection before attempting to connect again.")] 11 | ConnectionExists, 12 | /// The device is not connected to any peer. 13 | #[error("The device is not connected to any peer. Please connect it to a peer first.")] 14 | NotConnected, 15 | /// Peer socket is shutdown. 16 | #[error("The peer socket is shutdown.")] 17 | PeerSocketShutdown, 18 | /// The given buffer is shorter than expected. 19 | #[error("The given buffer is shorter than expected")] 20 | BufferTooShort, 21 | /// The given buffer for output is shorter than expected. 22 | #[error("The given output buffer is too short. '{0}' bytes is needed for the output buffer.")] 23 | OutputBufferTooShort(usize), 24 | /// The given buffer has exceeded the maximum buffer size. 25 | #[error("The given buffer length '{0}' has exceeded the maximum allowed buffer length '{1}'")] 26 | BufferTooLong(usize, usize), 27 | /// Unknown operation. 28 | #[error("The operation code '{0}' is unknown")] 29 | UnknownOperation(u16), 30 | /// Invalid operation, 31 | #[error("Invalid operation")] 32 | InvalidOperation, 33 | /// Invalid number. 34 | #[error("Invalid number")] 35 | InvalidNumber, 36 | /// Unexpected data in packet. 37 | #[error("No data is expected in the packet")] 38 | UnexpectedDataInPacket, 39 | /// Peer has insufficient buffer space, try again later. 40 | #[error("Peer has insufficient buffer space, try again later")] 41 | InsufficientBufferSpaceInPeer, 42 | /// Recycled a wrong buffer. 43 | #[error("Recycled a wrong buffer")] 44 | RecycledWrongBuffer, 45 | } 46 | 47 | pub type Result = result::Result; 48 | -------------------------------------------------------------------------------- /src/device/socket/mod.rs: -------------------------------------------------------------------------------- 1 | //! Driver for VirtIO socket devices. 2 | //! 3 | //! To use the driver, you should first create a [`VirtIOSocket`] instance with your VirtIO 4 | //! transport, and then create a [`VsockConnectionManager`] wrapping it to keep track of 5 | //! connections. If you want to manage connections yourself you can use the `VirtIOSocket` directly 6 | //! for a lower-level interface. 7 | //! 8 | //! See [`VsockConnectionManager`] for a usage example. 9 | 10 | #[cfg(feature = "alloc")] 11 | mod connectionmanager; 12 | mod error; 13 | mod protocol; 14 | #[cfg(feature = "alloc")] 15 | mod vsock; 16 | 17 | #[cfg(feature = "alloc")] 18 | pub use connectionmanager::VsockConnectionManager; 19 | pub use error::SocketError; 20 | pub use protocol::{StreamShutdown, VsockAddr, VMADDR_CID_HOST}; 21 | #[cfg(feature = "alloc")] 22 | pub use vsock::{ConnectionInfo, DisconnectReason, VirtIOSocket, VsockEvent, VsockEventType}; 23 | 24 | /// The size in bytes of each buffer used in the RX virtqueue. This must be bigger than 25 | /// `size_of::()`. 26 | const DEFAULT_RX_BUFFER_SIZE: usize = 512; 27 | -------------------------------------------------------------------------------- /src/device/socket/protocol.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the socket device protocol according to the virtio spec v1.1 5.10 Socket Device 2 | 3 | use super::error::{self, SocketError}; 4 | use crate::config::ReadOnly; 5 | use bitflags::bitflags; 6 | use core::{ 7 | convert::{TryFrom, TryInto}, 8 | fmt, 9 | }; 10 | use zerocopy::{ 11 | byteorder::{LittleEndian, U16, U32, U64}, 12 | FromBytes, Immutable, IntoBytes, KnownLayout, 13 | }; 14 | 15 | /// Well-known CID for the host. 16 | pub const VMADDR_CID_HOST: u64 = 2; 17 | 18 | /// Currently only stream sockets are supported. type is 1 for stream socket types. 19 | #[derive(Copy, Clone, Debug)] 20 | #[repr(u16)] 21 | pub enum SocketType { 22 | /// Stream sockets provide in-order, guaranteed, connection-oriented delivery without message boundaries. 23 | Stream = 1, 24 | /// seqpacket socket type introduced in virtio-v1.2. 25 | SeqPacket = 2, 26 | } 27 | 28 | impl From for U16 { 29 | fn from(socket_type: SocketType) -> Self { 30 | (socket_type as u16).into() 31 | } 32 | } 33 | 34 | /// VirtioVsockConfig is the vsock device configuration space. 35 | #[derive(FromBytes, Immutable, IntoBytes)] 36 | #[repr(C)] 37 | pub struct VirtioVsockConfig { 38 | /// The guest_cid field contains the guest’s context ID, which uniquely identifies 39 | /// the device for its lifetime. The upper 32 bits of the CID are reserved and zeroed. 40 | /// 41 | /// According to virtio spec v1.1 2.4.1 Driver Requirements: Device Configuration Space, 42 | /// drivers MUST NOT assume reads from fields greater than 32 bits wide are atomic. 43 | /// So we need to split the u64 guest_cid into two parts. 44 | pub guest_cid_low: ReadOnly, 45 | pub guest_cid_high: ReadOnly, 46 | } 47 | 48 | /// The message header for data packets sent on the tx/rx queues 49 | #[repr(C, packed)] 50 | #[derive(Clone, Copy, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)] 51 | pub struct VirtioVsockHdr { 52 | pub src_cid: U64, 53 | pub dst_cid: U64, 54 | pub src_port: U32, 55 | pub dst_port: U32, 56 | pub len: U32, 57 | pub socket_type: U16, 58 | pub op: U16, 59 | pub flags: U32, 60 | /// Total receive buffer space for this socket. This includes both free and in-use buffers. 61 | pub buf_alloc: U32, 62 | /// Free-running bytes received counter. 63 | pub fwd_cnt: U32, 64 | } 65 | 66 | impl Default for VirtioVsockHdr { 67 | fn default() -> Self { 68 | Self { 69 | src_cid: 0.into(), 70 | dst_cid: 0.into(), 71 | src_port: 0.into(), 72 | dst_port: 0.into(), 73 | len: 0.into(), 74 | socket_type: SocketType::Stream.into(), 75 | op: 0.into(), 76 | flags: 0.into(), 77 | buf_alloc: 0.into(), 78 | fwd_cnt: 0.into(), 79 | } 80 | } 81 | } 82 | 83 | impl VirtioVsockHdr { 84 | /// Returns the length of the data. 85 | pub fn len(&self) -> u32 { 86 | u32::from(self.len) 87 | } 88 | 89 | pub fn op(&self) -> error::Result { 90 | self.op.try_into() 91 | } 92 | 93 | pub fn source(&self) -> VsockAddr { 94 | VsockAddr { 95 | cid: self.src_cid.get(), 96 | port: self.src_port.get(), 97 | } 98 | } 99 | 100 | pub fn destination(&self) -> VsockAddr { 101 | VsockAddr { 102 | cid: self.dst_cid.get(), 103 | port: self.dst_port.get(), 104 | } 105 | } 106 | 107 | pub fn check_data_is_empty(&self) -> error::Result<()> { 108 | if self.len() == 0 { 109 | Ok(()) 110 | } else { 111 | Err(SocketError::UnexpectedDataInPacket) 112 | } 113 | } 114 | } 115 | 116 | /// Socket address. 117 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] 118 | pub struct VsockAddr { 119 | /// Context Identifier. 120 | pub cid: u64, 121 | /// Port number. 122 | pub port: u32, 123 | } 124 | 125 | /// An event sent to the event queue 126 | #[derive(Copy, Clone, Debug, Default, IntoBytes, FromBytes, Immutable, KnownLayout)] 127 | #[repr(C)] 128 | pub struct VirtioVsockEvent { 129 | // ID from the virtio_vsock_event_id struct in the virtio spec 130 | pub id: U32, 131 | } 132 | 133 | #[derive(Copy, Clone, Eq, PartialEq)] 134 | #[repr(u16)] 135 | pub enum VirtioVsockOp { 136 | Invalid = 0, 137 | 138 | /* Connect operations */ 139 | Request = 1, 140 | Response = 2, 141 | Rst = 3, 142 | Shutdown = 4, 143 | 144 | /* To send payload */ 145 | Rw = 5, 146 | 147 | /* Tell the peer our credit info */ 148 | CreditUpdate = 6, 149 | /* Request the peer to send the credit info to us */ 150 | CreditRequest = 7, 151 | } 152 | 153 | impl From for U16 { 154 | fn from(op: VirtioVsockOp) -> Self { 155 | (op as u16).into() 156 | } 157 | } 158 | 159 | impl TryFrom> for VirtioVsockOp { 160 | type Error = SocketError; 161 | 162 | fn try_from(v: U16) -> Result { 163 | let op = match u16::from(v) { 164 | 0 => Self::Invalid, 165 | 1 => Self::Request, 166 | 2 => Self::Response, 167 | 3 => Self::Rst, 168 | 4 => Self::Shutdown, 169 | 5 => Self::Rw, 170 | 6 => Self::CreditUpdate, 171 | 7 => Self::CreditRequest, 172 | _ => return Err(SocketError::UnknownOperation(v.into())), 173 | }; 174 | Ok(op) 175 | } 176 | } 177 | 178 | impl fmt::Debug for VirtioVsockOp { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | match self { 181 | Self::Invalid => write!(f, "VIRTIO_VSOCK_OP_INVALID"), 182 | Self::Request => write!(f, "VIRTIO_VSOCK_OP_REQUEST"), 183 | Self::Response => write!(f, "VIRTIO_VSOCK_OP_RESPONSE"), 184 | Self::Rst => write!(f, "VIRTIO_VSOCK_OP_RST"), 185 | Self::Shutdown => write!(f, "VIRTIO_VSOCK_OP_SHUTDOWN"), 186 | Self::Rw => write!(f, "VIRTIO_VSOCK_OP_RW"), 187 | Self::CreditUpdate => write!(f, "VIRTIO_VSOCK_OP_CREDIT_UPDATE"), 188 | Self::CreditRequest => write!(f, "VIRTIO_VSOCK_OP_CREDIT_REQUEST"), 189 | } 190 | } 191 | } 192 | 193 | bitflags! { 194 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 195 | pub(crate) struct Feature: u64 { 196 | /// stream socket type is supported. 197 | const STREAM = 1 << 0; 198 | /// seqpacket socket type is supported. 199 | const SEQ_PACKET = 1 << 1; 200 | 201 | // device independent 202 | const NOTIFY_ON_EMPTY = 1 << 24; // legacy 203 | const ANY_LAYOUT = 1 << 27; // legacy 204 | const RING_INDIRECT_DESC = 1 << 28; 205 | const RING_EVENT_IDX = 1 << 29; 206 | const UNUSED = 1 << 30; // legacy 207 | const VERSION_1 = 1 << 32; // detect legacy 208 | 209 | // since virtio v1.1 210 | const ACCESS_PLATFORM = 1 << 33; 211 | const RING_PACKED = 1 << 34; 212 | const IN_ORDER = 1 << 35; 213 | const ORDER_PLATFORM = 1 << 36; 214 | const SR_IOV = 1 << 37; 215 | const NOTIFICATION_DATA = 1 << 38; 216 | } 217 | } 218 | 219 | bitflags! { 220 | /// Flags sent with a shutdown request to hint that the peer won't send or receive more data. 221 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 222 | pub struct StreamShutdown: u32 { 223 | /// The peer will not receive any more data. 224 | const RECEIVE = 1 << 0; 225 | /// The peer will not send any more data. 226 | const SEND = 1 << 1; 227 | } 228 | } 229 | 230 | impl From for U32 { 231 | fn from(flags: StreamShutdown) -> Self { 232 | flags.bits().into() 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/device/sound/fake.rs: -------------------------------------------------------------------------------- 1 | //! Fake VirtIO sound device for tests. 2 | 3 | use super::{ 4 | CommandCode, VirtIOSndChmapInfo, VirtIOSndHdr, VirtIOSndJackInfo, VirtIOSndPcmInfo, 5 | VirtIOSndPcmStatus, VirtIOSndPcmXfer, VirtIOSndQueryInfo, VirtIOSoundConfig, CONTROL_QUEUE_IDX, 6 | QUEUE_SIZE, TX_QUEUE_IDX, 7 | }; 8 | use crate::{ 9 | config::ReadOnly, 10 | device::sound::{VirtIOSndPcmHdr, VirtIOSndPcmSetParams}, 11 | transport::{ 12 | fake::{FakeTransport, QueueStatus, State}, 13 | DeviceType, 14 | }, 15 | }; 16 | use alloc::{sync::Arc, vec}; 17 | use core::{ 18 | convert::{TryFrom, TryInto}, 19 | mem::size_of, 20 | time::Duration, 21 | }; 22 | use std::{ 23 | iter::repeat_with, 24 | sync::{ 25 | atomic::{AtomicBool, Ordering}, 26 | Mutex, 27 | }, 28 | thread::{self, JoinHandle}, 29 | }; 30 | use zerocopy::{FromBytes, IntoBytes}; 31 | 32 | #[derive(Clone, Debug)] 33 | pub struct FakeSoundDevice { 34 | pub state: Arc>>, 35 | pub terminate: Arc, 36 | /// The paramaters set for each stream, if any. 37 | pub params: Arc>>>, 38 | /// The bytes send on the TX queue for each channel. 39 | pub played_bytes: Arc>>>, 40 | pub jack_infos: Vec, 41 | pub pcm_infos: Vec, 42 | pub chmap_infos: Vec, 43 | } 44 | 45 | impl FakeSoundDevice { 46 | pub fn new( 47 | jack_infos: Vec, 48 | pcm_infos: Vec, 49 | chmap_infos: Vec, 50 | ) -> (Self, FakeTransport) { 51 | let config_space = VirtIOSoundConfig { 52 | jacks: ReadOnly::new(jack_infos.len().try_into().unwrap()), 53 | streams: ReadOnly::new(pcm_infos.len().try_into().unwrap()), 54 | chmaps: ReadOnly::new(chmap_infos.len().try_into().unwrap()), 55 | }; 56 | let state = Arc::new(Mutex::new(State::new( 57 | vec![ 58 | QueueStatus::default(), 59 | QueueStatus::default(), 60 | QueueStatus::default(), 61 | QueueStatus::default(), 62 | ], 63 | config_space, 64 | ))); 65 | let transport = FakeTransport { 66 | device_type: DeviceType::Socket, 67 | max_queue_size: 32, 68 | device_features: 0, 69 | state: state.clone(), 70 | }; 71 | let params = repeat_with(|| None).take(pcm_infos.len()).collect(); 72 | let played_bytes = vec![vec![]; pcm_infos.len()]; 73 | 74 | ( 75 | Self { 76 | state, 77 | terminate: Arc::new(AtomicBool::new(false)), 78 | params: Arc::new(Mutex::new(params)), 79 | played_bytes: Arc::new(Mutex::new(played_bytes)), 80 | jack_infos, 81 | pcm_infos, 82 | chmap_infos, 83 | }, 84 | transport, 85 | ) 86 | } 87 | 88 | /// Start a background thread simulating the device. 89 | pub fn spawn(&self) -> JoinHandle<()> { 90 | let clone = self.clone(); 91 | thread::spawn(move || clone.run()) 92 | } 93 | 94 | /// Terminate the background thread for the device. 95 | pub fn terminate(&self) { 96 | self.terminate.store(true, Ordering::Release); 97 | } 98 | 99 | fn run(&self) { 100 | while !self.terminate.load(Ordering::Acquire) { 101 | if State::poll_queue_notified(&self.state, CONTROL_QUEUE_IDX) { 102 | println!("Control queue was notified."); 103 | 104 | while self 105 | .state 106 | .lock() 107 | .unwrap() 108 | .read_write_queue::<{ QUEUE_SIZE as usize }>(CONTROL_QUEUE_IDX, |request| { 109 | self.handle_control_request(&request) 110 | }) 111 | {} 112 | } else if State::poll_queue_notified(&self.state, TX_QUEUE_IDX) { 113 | println!("TX queue was notified"); 114 | while self 115 | .state 116 | .lock() 117 | .unwrap() 118 | .read_write_queue::<{ QUEUE_SIZE as usize }>(TX_QUEUE_IDX, |request| { 119 | self.handle_tx(&request) 120 | }) 121 | {} 122 | } else { 123 | thread::sleep(Duration::from_millis(10)); 124 | } 125 | } 126 | } 127 | 128 | fn handle_tx(&self, request: &[u8]) -> Vec { 129 | let header = VirtIOSndPcmXfer::read_from_prefix(&request) 130 | .expect("TX request too short") 131 | .0; 132 | self.played_bytes.lock().unwrap()[usize::try_from(header.stream_id).unwrap()] 133 | .extend(&request[size_of::()..]); 134 | 135 | VirtIOSndPcmStatus { 136 | status: CommandCode::SOk.into(), 137 | latency_bytes: 0, 138 | } 139 | .as_bytes() 140 | .to_owned() 141 | } 142 | 143 | fn handle_control_request(&self, request: &[u8]) -> Vec { 144 | { 145 | let header = VirtIOSndHdr::read_from_prefix(&request) 146 | .expect("Control request too short") 147 | .0; 148 | let mut response = Vec::new(); 149 | const R_JACK_INFO: u32 = CommandCode::RJackInfo as u32; 150 | const R_PCM_INFO: u32 = CommandCode::RPcmInfo as u32; 151 | const R_CHMAP_INFO: u32 = CommandCode::RChmapInfo as u32; 152 | const R_PCM_SET_PARAMS: u32 = CommandCode::RPcmSetParams as u32; 153 | const R_PCM_PREPARE: u32 = CommandCode::RPcmPrepare as u32; 154 | const R_PCM_START: u32 = CommandCode::RPcmStart as u32; 155 | const R_PCM_STOP: u32 = CommandCode::RPcmStop as u32; 156 | const R_PCM_RELEASE: u32 = CommandCode::RPcmRelease as u32; 157 | match header.command_code { 158 | R_JACK_INFO => { 159 | let request = VirtIOSndQueryInfo::read_from_bytes(&request) 160 | .expect("R_JACK_INFO control request wrong length"); 161 | assert_eq!( 162 | request.size, 163 | u32::try_from(size_of::()).unwrap() 164 | ); 165 | response.extend_from_slice( 166 | VirtIOSndHdr { 167 | command_code: CommandCode::SOk.into(), 168 | } 169 | .as_bytes(), 170 | ); 171 | for jack_info in &self.jack_infos[request.start_id as usize 172 | ..request.start_id as usize + request.count as usize] 173 | { 174 | response.extend_from_slice(jack_info.as_bytes()); 175 | } 176 | } 177 | R_PCM_INFO => { 178 | let request = VirtIOSndQueryInfo::read_from_bytes(&request) 179 | .expect("R_PCM_INFO control request wrong length"); 180 | assert_eq!( 181 | request.size, 182 | u32::try_from(size_of::()).unwrap() 183 | ); 184 | response.extend_from_slice( 185 | VirtIOSndHdr { 186 | command_code: CommandCode::SOk.into(), 187 | } 188 | .as_bytes(), 189 | ); 190 | for pcm_info in &self.pcm_infos[request.start_id as usize 191 | ..request.start_id as usize + request.count as usize] 192 | { 193 | response.extend_from_slice(pcm_info.as_bytes()); 194 | } 195 | } 196 | R_CHMAP_INFO => { 197 | let request = VirtIOSndQueryInfo::read_from_bytes(&request) 198 | .expect("R_CHMAP_INFO control request wrong length"); 199 | assert_eq!( 200 | request.size, 201 | u32::try_from(size_of::()).unwrap() 202 | ); 203 | response.extend_from_slice( 204 | VirtIOSndHdr { 205 | command_code: CommandCode::SOk.into(), 206 | } 207 | .as_bytes(), 208 | ); 209 | for chmap_info in &self.chmap_infos[request.start_id as usize 210 | ..request.start_id as usize + request.count as usize] 211 | { 212 | response.extend_from_slice(chmap_info.as_bytes()); 213 | } 214 | } 215 | R_PCM_SET_PARAMS => { 216 | let request = VirtIOSndPcmSetParams::read_from_bytes(&request) 217 | .expect("R_PCM_SET_PARAMS request wrong length"); 218 | let stream_id = request.hdr.stream_id; 219 | self.params.lock().unwrap()[usize::try_from(stream_id).unwrap()] = 220 | Some(request); 221 | response.extend_from_slice( 222 | VirtIOSndHdr { 223 | command_code: CommandCode::SOk.into(), 224 | } 225 | .as_bytes(), 226 | ); 227 | } 228 | R_PCM_PREPARE | R_PCM_START | R_PCM_STOP | R_PCM_RELEASE => { 229 | let _request = 230 | VirtIOSndPcmHdr::read_from_bytes(&request).expect("Request wrong length"); 231 | response.extend_from_slice( 232 | VirtIOSndHdr { 233 | command_code: CommandCode::SOk.into(), 234 | } 235 | .as_bytes(), 236 | ); 237 | } 238 | _ => { 239 | panic!("Unexpected control request, header {:?}", header); 240 | } 241 | } 242 | response 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/embedded_io.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of `embedded-io::Error' trait for `Error`. 2 | 3 | use crate::{device::socket::SocketError, Error}; 4 | use embedded_io::ErrorKind; 5 | 6 | impl embedded_io::Error for Error { 7 | fn kind(&self) -> ErrorKind { 8 | match self { 9 | Error::InvalidParam => ErrorKind::InvalidInput, 10 | Error::DmaError => ErrorKind::OutOfMemory, 11 | Error::Unsupported => ErrorKind::Unsupported, 12 | Error::SocketDeviceError(e) => match e { 13 | &SocketError::ConnectionExists => ErrorKind::AddrInUse, 14 | SocketError::NotConnected => ErrorKind::NotConnected, 15 | SocketError::PeerSocketShutdown => ErrorKind::ConnectionAborted, 16 | SocketError::BufferTooShort => ErrorKind::InvalidInput, 17 | SocketError::OutputBufferTooShort(_) => ErrorKind::InvalidInput, 18 | SocketError::BufferTooLong(_, _) => ErrorKind::InvalidInput, 19 | SocketError::InsufficientBufferSpaceInPeer => ErrorKind::WriteZero, 20 | SocketError::UnknownOperation(_) 21 | | SocketError::InvalidOperation 22 | | SocketError::InvalidNumber 23 | | SocketError::UnexpectedDataInPacket 24 | | SocketError::RecycledWrongBuffer => ErrorKind::Other, 25 | }, 26 | Error::QueueFull 27 | | Error::NotReady 28 | | Error::WrongToken 29 | | Error::AlreadyUsed 30 | | Error::IoError 31 | | Error::ConfigSpaceTooSmall 32 | | Error::ConfigSpaceMissing => ErrorKind::Other, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/hal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod fake; 3 | 4 | use crate::{Error, Result, PAGE_SIZE}; 5 | use core::{marker::PhantomData, ptr::NonNull}; 6 | 7 | /// A physical address as used for virtio. 8 | pub type PhysAddr = usize; 9 | 10 | /// A region of contiguous physical memory used for DMA. 11 | #[derive(Debug)] 12 | pub struct Dma { 13 | paddr: usize, 14 | vaddr: NonNull, 15 | pages: usize, 16 | _hal: PhantomData, 17 | } 18 | 19 | // SAFETY: DMA memory can be accessed from any thread. 20 | unsafe impl Send for Dma {} 21 | 22 | // SAFETY: `&Dma` only allows pointers and physical addresses to be returned. Any actual access to 23 | // the memory requires unsafe code, which is responsible for avoiding data races. 24 | unsafe impl Sync for Dma {} 25 | 26 | impl Dma { 27 | /// Allocates the given number of pages of physically contiguous memory to be used for DMA in 28 | /// the given direction. 29 | /// 30 | /// The pages will be zeroed. 31 | pub fn new(pages: usize, direction: BufferDirection) -> Result { 32 | let (paddr, vaddr) = H::dma_alloc(pages, direction); 33 | if paddr == 0 { 34 | return Err(Error::DmaError); 35 | } 36 | Ok(Self { 37 | paddr, 38 | vaddr, 39 | pages, 40 | _hal: PhantomData, 41 | }) 42 | } 43 | 44 | /// Returns the physical address of the start of the DMA region, as seen by devices. 45 | pub fn paddr(&self) -> usize { 46 | self.paddr 47 | } 48 | 49 | /// Returns a pointer to the given offset within the DMA region. 50 | pub fn vaddr(&self, offset: usize) -> NonNull { 51 | assert!(offset < self.pages * PAGE_SIZE); 52 | NonNull::new((self.vaddr.as_ptr() as usize + offset) as _).unwrap() 53 | } 54 | 55 | /// Returns a pointer to the entire DMA region as a slice. 56 | pub fn raw_slice(&self) -> NonNull<[u8]> { 57 | let raw_slice = 58 | core::ptr::slice_from_raw_parts_mut(self.vaddr(0).as_ptr(), self.pages * PAGE_SIZE); 59 | NonNull::new(raw_slice).unwrap() 60 | } 61 | } 62 | 63 | impl Drop for Dma { 64 | fn drop(&mut self) { 65 | // SAFETY: The memory was previously allocated by `dma_alloc` in `Dma::new`, 66 | // not yet deallocated, and we are passing the values from then. 67 | let err = unsafe { H::dma_dealloc(self.paddr, self.vaddr, self.pages) }; 68 | assert_eq!(err, 0, "failed to deallocate DMA"); 69 | } 70 | } 71 | 72 | /// The interface which a particular hardware implementation must implement. 73 | /// 74 | /// # Safety 75 | /// 76 | /// Implementations of this trait must follow the "implementation safety" requirements documented 77 | /// for each method. Callers must follow the safety requirements documented for the unsafe methods. 78 | pub unsafe trait Hal { 79 | /// Allocates and zeroes the given number of contiguous physical pages of DMA memory for VirtIO 80 | /// use. 81 | /// 82 | /// Returns both the physical address which the device can use to access the memory, and a 83 | /// pointer to the start of it which the driver can use to access it. 84 | /// 85 | /// # Implementation safety 86 | /// 87 | /// Implementations of this method must ensure that the `NonNull` returned is a 88 | /// [_valid_](https://doc.rust-lang.org/std/ptr/index.html#safety) pointer, aligned to 89 | /// [`PAGE_SIZE`], and won't alias any other allocations or references in the program until it 90 | /// is deallocated by `dma_dealloc`. The pages must be zeroed. 91 | fn dma_alloc(pages: usize, direction: BufferDirection) -> (PhysAddr, NonNull); 92 | 93 | /// Deallocates the given contiguous physical DMA memory pages. 94 | /// 95 | /// # Safety 96 | /// 97 | /// The memory must have been allocated by `dma_alloc` on the same `Hal` implementation, and not 98 | /// yet deallocated. `pages` must be the same number passed to `dma_alloc` originally, and both 99 | /// `paddr` and `vaddr` must be the values returned by `dma_alloc`. 100 | unsafe fn dma_dealloc(paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32; 101 | 102 | /// Converts a physical address used for MMIO to a virtual address which the driver can access. 103 | /// 104 | /// This is only used for MMIO addresses within BARs read from the device, for the PCI 105 | /// transport. It may check that the address range up to the given size is within the region 106 | /// expected for MMIO. 107 | /// 108 | /// # Implementation safety 109 | /// 110 | /// Implementations of this method must ensure that the `NonNull` returned is a 111 | /// [_valid_](https://doc.rust-lang.org/std/ptr/index.html#safety) pointer, and won't alias any 112 | /// other allocations or references in the program. 113 | /// 114 | /// # Safety 115 | /// 116 | /// The `paddr` and `size` must describe a valid MMIO region. The implementation may validate it 117 | /// in some way (and panic if it is invalid) but is not guaranteed to. 118 | unsafe fn mmio_phys_to_virt(paddr: PhysAddr, size: usize) -> NonNull; 119 | 120 | /// Shares the given memory range with the device, and returns the physical address that the 121 | /// device can use to access it. 122 | /// 123 | /// This may involve mapping the buffer into an IOMMU, giving the host permission to access the 124 | /// memory, or copying it to a special region where it can be accessed. 125 | /// 126 | /// # Safety 127 | /// 128 | /// The buffer must be a valid pointer to a non-empty memory range which will not be accessed by 129 | /// any other thread for the duration of this method call. 130 | unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> PhysAddr; 131 | 132 | /// Unshares the given memory range from the device and (if necessary) copies it back to the 133 | /// original buffer. 134 | /// 135 | /// # Safety 136 | /// 137 | /// The buffer must be a valid pointer to a non-empty memory range which will not be accessed by 138 | /// any other thread for the duration of this method call. The `paddr` must be the value 139 | /// previously returned by the corresponding `share` call. 140 | unsafe fn unshare(paddr: PhysAddr, buffer: NonNull<[u8]>, direction: BufferDirection); 141 | } 142 | 143 | /// The direction in which a buffer is passed. 144 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 145 | pub enum BufferDirection { 146 | /// The buffer may be read or written by the driver, but only read by the device. 147 | DriverToDevice, 148 | /// The buffer may be read or written by the device, but only read by the driver. 149 | DeviceToDriver, 150 | /// The buffer may be read or written by both the device and the driver. 151 | Both, 152 | } 153 | -------------------------------------------------------------------------------- /src/hal/fake.rs: -------------------------------------------------------------------------------- 1 | //! Fake HAL implementation for tests. 2 | 3 | #![deny(unsafe_op_in_unsafe_fn)] 4 | 5 | use crate::{BufferDirection, Hal, PhysAddr, PAGE_SIZE}; 6 | use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error}; 7 | use core::{ 8 | alloc::Layout, 9 | ptr::{self, NonNull}, 10 | }; 11 | use zerocopy::FromZeros; 12 | 13 | #[derive(Debug)] 14 | pub struct FakeHal; 15 | 16 | /// Fake HAL implementation for use in unit tests. 17 | unsafe impl Hal for FakeHal { 18 | fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { 19 | assert_ne!(pages, 0); 20 | let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap(); 21 | // Safe because the size and alignment of the layout are non-zero. 22 | let ptr = unsafe { alloc_zeroed(layout) }; 23 | if let Some(ptr) = NonNull::new(ptr) { 24 | (ptr.as_ptr() as PhysAddr, ptr) 25 | } else { 26 | handle_alloc_error(layout); 27 | } 28 | } 29 | 30 | unsafe fn dma_dealloc(_paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { 31 | assert_ne!(pages, 0); 32 | let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap(); 33 | // Safe because the layout is the same as was used when the memory was allocated by 34 | // `dma_alloc` above. 35 | unsafe { 36 | dealloc(vaddr.as_ptr(), layout); 37 | } 38 | 0 39 | } 40 | 41 | unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { 42 | NonNull::new(paddr as _).unwrap() 43 | } 44 | 45 | unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> PhysAddr { 46 | assert_ne!(buffer.len(), 0); 47 | // To ensure that the driver is handling and unsharing buffers properly, allocate a new 48 | // buffer and copy to it if appropriate. 49 | let mut shared_buffer = <[u8]>::new_box_zeroed_with_elems(buffer.len()).unwrap(); 50 | if let BufferDirection::DriverToDevice | BufferDirection::Both = direction { 51 | unsafe { 52 | buffer 53 | .as_ptr() 54 | .cast::() 55 | .copy_to(shared_buffer.as_mut_ptr(), buffer.len()); 56 | } 57 | } 58 | let vaddr = Box::into_raw(shared_buffer) as *mut u8 as usize; 59 | // Nothing to do, as the host already has access to all memory. 60 | virt_to_phys(vaddr) 61 | } 62 | 63 | unsafe fn unshare(paddr: PhysAddr, buffer: NonNull<[u8]>, direction: BufferDirection) { 64 | assert_ne!(buffer.len(), 0); 65 | assert_ne!(paddr, 0); 66 | let vaddr = phys_to_virt(paddr); 67 | let shared_buffer = unsafe { 68 | Box::from_raw(ptr::slice_from_raw_parts_mut( 69 | vaddr as *mut u8, 70 | buffer.len(), 71 | )) 72 | }; 73 | if let BufferDirection::DeviceToDriver | BufferDirection::Both = direction { 74 | unsafe { 75 | buffer 76 | .as_ptr() 77 | .cast::() 78 | .copy_from(shared_buffer.as_ptr(), buffer.len()); 79 | } 80 | } 81 | } 82 | } 83 | 84 | fn virt_to_phys(vaddr: usize) -> PhysAddr { 85 | vaddr 86 | } 87 | 88 | fn phys_to_virt(paddr: PhysAddr) -> usize { 89 | paddr 90 | } 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! VirtIO guest drivers. 2 | //! 3 | //! These drivers can be used by bare-metal code (such as a bootloader or OS kernel) running in a VM 4 | //! to interact with VirtIO devices provided by the VMM (such as QEMU or crosvm). 5 | //! 6 | //! # Usage 7 | //! 8 | //! You must first implement the [`Hal`] trait, to allocate DMA regions and translate between 9 | //! physical addresses (as seen by devices) and virtual addresses (as seen by your program). You can 10 | //! then construct the appropriate transport for the VirtIO device, e.g. for an MMIO device (perhaps 11 | //! discovered from the device tree): 12 | //! 13 | //! ``` 14 | //! use core::ptr::NonNull; 15 | //! use virtio_drivers::transport::mmio::{MmioTransport, VirtIOHeader}; 16 | //! 17 | //! # fn example(mmio_device_address: usize, mmio_size: usize) { 18 | //! let header = NonNull::new(mmio_device_address as *mut VirtIOHeader).unwrap(); 19 | //! let transport = unsafe { MmioTransport::new(header, mmio_size) }.unwrap(); 20 | //! # } 21 | //! ``` 22 | //! 23 | //! You can then check what kind of VirtIO device it is and construct the appropriate driver: 24 | //! 25 | //! ``` 26 | //! # use virtio_drivers::Hal; 27 | //! # #[cfg(feature = "alloc")] 28 | //! use virtio_drivers::{ 29 | //! device::console::VirtIOConsole, 30 | //! transport::{mmio::MmioTransport, DeviceType, Transport}, 31 | //! }; 32 | 33 | //! 34 | //! # #[cfg(feature = "alloc")] 35 | //! # fn example(transport: MmioTransport) { 36 | //! if transport.device_type() == DeviceType::Console { 37 | //! let mut console = VirtIOConsole::::new(transport).unwrap(); 38 | //! // Send a byte to the console. 39 | //! console.send(b'H').unwrap(); 40 | //! } 41 | //! # } 42 | //! ``` 43 | 44 | #![cfg_attr(not(test), no_std)] 45 | #![deny(unused_must_use, missing_docs, clippy::undocumented_unsafe_blocks)] 46 | #![allow(clippy::identity_op)] 47 | #![allow(dead_code)] 48 | 49 | #[cfg(any(feature = "alloc", test))] 50 | extern crate alloc; 51 | 52 | mod config; 53 | pub mod device; 54 | #[cfg(feature = "embedded-io")] 55 | mod embedded_io; 56 | mod hal; 57 | mod queue; 58 | pub mod transport; 59 | 60 | use device::socket::SocketError; 61 | use thiserror::Error; 62 | 63 | pub use self::hal::{BufferDirection, Hal, PhysAddr}; 64 | pub use safe_mmio::UniqueMmioPointer; 65 | 66 | /// The page size in bytes supported by the library (4 KiB). 67 | pub const PAGE_SIZE: usize = 0x1000; 68 | 69 | /// The type returned by driver methods. 70 | pub type Result = core::result::Result; 71 | 72 | /// The error type of VirtIO drivers. 73 | #[derive(Copy, Clone, Debug, Eq, Error, PartialEq)] 74 | pub enum Error { 75 | /// There are not enough descriptors available in the virtqueue, try again later. 76 | #[error("Virtqueue is full")] 77 | QueueFull, 78 | /// The device is not ready. 79 | #[error("Device not ready")] 80 | NotReady, 81 | /// The device used a different descriptor chain to the one we were expecting. 82 | #[error("Device used a different descriptor chain to the one we were expecting")] 83 | WrongToken, 84 | /// The queue is already in use. 85 | #[error("Virtqueue is already in use")] 86 | AlreadyUsed, 87 | /// Invalid parameter. 88 | #[error("Invalid parameter")] 89 | InvalidParam, 90 | /// Failed to allocate DMA memory. 91 | #[error("Failed to allocate DMA memory")] 92 | DmaError, 93 | /// I/O error 94 | #[error("I/O error")] 95 | IoError, 96 | /// The request was not supported by the device. 97 | #[error("Request not supported by device")] 98 | Unsupported, 99 | /// The config space advertised by the device is smaller than the driver expected. 100 | #[error("Config space advertised by the device is smaller than expected")] 101 | ConfigSpaceTooSmall, 102 | /// The device doesn't have any config space, but the driver expects some. 103 | #[error("The device doesn't have any config space, but the driver expects some")] 104 | ConfigSpaceMissing, 105 | /// Error from the socket device. 106 | #[error("Error from the socket device: {0}")] 107 | SocketDeviceError(#[from] SocketError), 108 | } 109 | 110 | #[cfg(feature = "alloc")] 111 | impl From for Error { 112 | fn from(_value: alloc::string::FromUtf8Error) -> Self { 113 | Self::IoError 114 | } 115 | } 116 | 117 | /// Align `size` up to a page. 118 | fn align_up(size: usize) -> usize { 119 | (size + PAGE_SIZE) & !(PAGE_SIZE - 1) 120 | } 121 | 122 | /// The number of pages required to store `size` bytes, rounded up to a whole number of pages. 123 | fn pages(size: usize) -> usize { 124 | size.div_ceil(PAGE_SIZE) 125 | } 126 | -------------------------------------------------------------------------------- /src/queue/owning.rs: -------------------------------------------------------------------------------- 1 | use super::VirtQueue; 2 | use crate::{transport::Transport, Error, Hal, Result}; 3 | use alloc::boxed::Box; 4 | use core::convert::TryInto; 5 | use core::ptr::{null_mut, NonNull}; 6 | use zerocopy::FromZeros; 7 | 8 | /// A wrapper around [`Queue`] that owns all the buffers that are passed to the queue. 9 | #[derive(Debug)] 10 | pub struct OwningQueue { 11 | queue: VirtQueue, 12 | buffers: [NonNull<[u8; BUFFER_SIZE]>; SIZE], 13 | } 14 | 15 | impl OwningQueue { 16 | /// Constructs a new `OwningQueue` wrapping around the given `VirtQueue`. 17 | /// 18 | /// This will allocate `SIZE` buffers of `BUFFER_SIZE` bytes each and add them to the queue. 19 | /// 20 | /// The caller is responsible for notifying the device if `should_notify` returns true. 21 | pub fn new(mut queue: VirtQueue) -> Result { 22 | let mut buffers = [null_mut(); SIZE]; 23 | for (i, queue_buffer) in buffers.iter_mut().enumerate() { 24 | let mut buffer: Box<[u8; BUFFER_SIZE]> = FromZeros::new_box_zeroed().unwrap(); 25 | // SAFETY: The buffer lives as long as the queue, as specified in the function safety 26 | // requirement, and we don't access it until it is popped. 27 | let token = unsafe { queue.add(&[], &mut [buffer.as_mut_slice()]) }?; 28 | assert_eq!(i, token.into()); 29 | *queue_buffer = Box::into_raw(buffer); 30 | } 31 | let buffers = buffers.map(|ptr| NonNull::new(ptr).unwrap()); 32 | 33 | Ok(Self { queue, buffers }) 34 | } 35 | 36 | /// Returns whether the driver should notify the device after adding a new buffer to the 37 | /// virtqueue. 38 | /// 39 | /// This will be false if the device has supressed notifications. 40 | pub fn should_notify(&self) -> bool { 41 | self.queue.should_notify() 42 | } 43 | 44 | /// Tells the device whether to send used buffer notifications. 45 | pub fn set_dev_notify(&mut self, enable: bool) { 46 | self.queue.set_dev_notify(enable); 47 | } 48 | 49 | /// Adds the buffer at the given index in `buffers` back to the queue. 50 | /// 51 | /// Automatically notifies the device if required. 52 | /// 53 | /// # Safety 54 | /// 55 | /// The buffer must not currently be in the RX queue, and no other references to it must exist 56 | /// between when this method is called and when it is popped from the queue. 57 | unsafe fn add_buffer_to_queue(&mut self, index: u16, transport: &mut impl Transport) -> Result { 58 | // SAFETY: The buffer lives as long as the queue, and the caller guarantees that it's not 59 | // currently in the queue or referred to anywhere else until it is popped. 60 | unsafe { 61 | let buffer = self 62 | .buffers 63 | .get_mut(usize::from(index)) 64 | .ok_or(Error::WrongToken)? 65 | .as_mut(); 66 | let new_token = self.queue.add(&[], &mut [buffer])?; 67 | // If the RX buffer somehow gets assigned a different token, then our safety assumptions 68 | // are broken and we can't safely continue to do anything with the device. 69 | assert_eq!(new_token, index); 70 | } 71 | 72 | if self.queue.should_notify() { 73 | transport.notify(self.queue.queue_idx); 74 | } 75 | 76 | Ok(()) 77 | } 78 | 79 | fn pop(&mut self) -> Result> { 80 | let Some(token) = self.queue.peek_used() else { 81 | return Ok(None); 82 | }; 83 | 84 | // SAFETY: The device has told us it has finished using the buffer, and there are no other 85 | // references to it. 86 | let buffer = unsafe { self.buffers[usize::from(token)].as_mut() }; 87 | // SAFETY: We maintain a consistent mapping of tokens to buffers, so we pass the same buffer 88 | // to `pop_used` as we previously passed to `add` for the token. Once we add the buffer back 89 | // to the RX queue then we don't access it again until next time it is popped. 90 | let len = unsafe { self.queue.pop_used(token, &[], &mut [buffer])? } 91 | .try_into() 92 | .unwrap(); 93 | 94 | Ok(Some((&buffer[0..len], token))) 95 | } 96 | 97 | /// Checks whether there are any buffers which the device has marked as used so the driver 98 | /// should now process. If so, passes the first one to the `handle` function and then adds it 99 | /// back to the queue. 100 | /// 101 | /// Returns an error if there is an error accessing the queue or `handler` returns an error. 102 | /// Returns `Ok(None)` if there are no pending buffers to handle, or if `handler` returns 103 | /// `Ok(None)`. 104 | /// 105 | /// If `handler` panics then the buffer will not be added back to the queue, so this should be 106 | /// avoided. 107 | pub fn poll( 108 | &mut self, 109 | transport: &mut impl Transport, 110 | handler: impl FnOnce(&[u8]) -> Result>, 111 | ) -> Result> { 112 | let Some((buffer, token)) = self.pop()? else { 113 | return Ok(None); 114 | }; 115 | 116 | let result = handler(buffer); 117 | 118 | // SAFETY: The buffer was just popped from the queue so it's not in it, and there won't be 119 | // any other references until next time it's popped. 120 | unsafe { 121 | self.add_buffer_to_queue(token, transport)?; 122 | } 123 | 124 | result 125 | } 126 | } 127 | 128 | // SAFETY: The `buffers` can be accessed from any thread. 129 | unsafe impl Send 130 | for OwningQueue 131 | where 132 | VirtQueue: Send, 133 | { 134 | } 135 | 136 | // SAFETY: An `&OwningQueue` only allows calling `should_notify`. 137 | unsafe impl Sync 138 | for OwningQueue 139 | where 140 | VirtQueue: Sync, 141 | { 142 | } 143 | 144 | impl Drop 145 | for OwningQueue 146 | { 147 | fn drop(&mut self) { 148 | for buffer in self.buffers { 149 | // SAFETY: We obtained the buffer pointer from `Box::into_raw`, and it won't be used 150 | // anywhere else after the queue is destroyed. 151 | unsafe { drop(Box::from_raw(buffer.as_ptr())) }; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/transport/fake.rs: -------------------------------------------------------------------------------- 1 | //! A fake implementation of `Transport` for unit tests. 2 | 3 | use super::{DeviceStatus, DeviceType, Transport}; 4 | use crate::{ 5 | queue::{fake_read_write_queue, Descriptor}, 6 | Error, PhysAddr, 7 | }; 8 | use alloc::{sync::Arc, vec::Vec}; 9 | use core::{ 10 | fmt::{self, Debug, Formatter}, 11 | sync::atomic::{AtomicBool, Ordering}, 12 | time::Duration, 13 | }; 14 | use std::{sync::Mutex, thread}; 15 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 16 | 17 | /// A fake implementation of [`Transport`] for unit tests. 18 | #[derive(Debug)] 19 | pub struct FakeTransport { 20 | /// The type of device which the transport should claim to be for. 21 | pub device_type: DeviceType, 22 | /// The maximum queue size supported by the transport. 23 | pub max_queue_size: u32, 24 | /// The device features which should be reported by the transport. 25 | pub device_features: u64, 26 | /// The mutable state of the transport. 27 | pub state: Arc>>, 28 | } 29 | 30 | impl Transport for FakeTransport { 31 | fn device_type(&self) -> DeviceType { 32 | self.device_type 33 | } 34 | 35 | fn read_device_features(&mut self) -> u64 { 36 | self.device_features 37 | } 38 | 39 | fn write_driver_features(&mut self, driver_features: u64) { 40 | self.state.lock().unwrap().driver_features = driver_features; 41 | } 42 | 43 | fn max_queue_size(&mut self, _queue: u16) -> u32 { 44 | self.max_queue_size 45 | } 46 | 47 | fn notify(&mut self, queue: u16) { 48 | self.state.lock().unwrap().queues[queue as usize] 49 | .notified 50 | .store(true, Ordering::SeqCst); 51 | } 52 | 53 | fn get_status(&self) -> DeviceStatus { 54 | self.state.lock().unwrap().status 55 | } 56 | 57 | fn set_status(&mut self, status: DeviceStatus) { 58 | self.state.lock().unwrap().status = status; 59 | } 60 | 61 | fn set_guest_page_size(&mut self, guest_page_size: u32) { 62 | self.state.lock().unwrap().guest_page_size = guest_page_size; 63 | } 64 | 65 | fn requires_legacy_layout(&self) -> bool { 66 | false 67 | } 68 | 69 | fn queue_set( 70 | &mut self, 71 | queue: u16, 72 | size: u32, 73 | descriptors: PhysAddr, 74 | driver_area: PhysAddr, 75 | device_area: PhysAddr, 76 | ) { 77 | let mut state = self.state.lock().unwrap(); 78 | state.queues[queue as usize].size = size; 79 | state.queues[queue as usize].descriptors = descriptors; 80 | state.queues[queue as usize].driver_area = driver_area; 81 | state.queues[queue as usize].device_area = device_area; 82 | } 83 | 84 | fn queue_unset(&mut self, queue: u16) { 85 | let mut state = self.state.lock().unwrap(); 86 | state.queues[queue as usize].size = 0; 87 | state.queues[queue as usize].descriptors = 0; 88 | state.queues[queue as usize].driver_area = 0; 89 | state.queues[queue as usize].device_area = 0; 90 | } 91 | 92 | fn queue_used(&mut self, queue: u16) -> bool { 93 | self.state.lock().unwrap().queues[queue as usize].descriptors != 0 94 | } 95 | 96 | fn ack_interrupt(&mut self) -> bool { 97 | let mut state = self.state.lock().unwrap(); 98 | let pending = state.interrupt_pending; 99 | if pending { 100 | state.interrupt_pending = false; 101 | } 102 | pending 103 | } 104 | 105 | fn read_config_generation(&self) -> u32 { 106 | self.state.lock().unwrap().config_generation 107 | } 108 | 109 | fn read_config_space(&self, offset: usize) -> Result { 110 | assert!(align_of::() <= 4, 111 | "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", 112 | align_of::()); 113 | assert!(offset % align_of::() == 0); 114 | 115 | if size_of::() < offset + size_of::() { 116 | Err(Error::ConfigSpaceTooSmall) 117 | } else { 118 | let state = self.state.lock().unwrap(); 119 | let bytes = &state.config_space.as_bytes()[offset..offset + size_of::()]; 120 | Ok(T::read_from_bytes(bytes).unwrap()) 121 | } 122 | } 123 | 124 | fn write_config_space( 125 | &mut self, 126 | offset: usize, 127 | value: T, 128 | ) -> Result<(), Error> { 129 | assert!(align_of::() <= 4, 130 | "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", 131 | align_of::()); 132 | assert!(offset % align_of::() == 0); 133 | 134 | if size_of::() < offset + size_of::() { 135 | Err(Error::ConfigSpaceTooSmall) 136 | } else { 137 | let mut state = self.state.lock().unwrap(); 138 | let bytes = &mut state.config_space.as_mut_bytes()[offset..offset + size_of::()]; 139 | value.write_to(bytes).unwrap(); 140 | Ok(()) 141 | } 142 | } 143 | } 144 | 145 | /// The mutable state of a fake transport. 146 | pub struct State { 147 | /// The status of the fake device. 148 | pub status: DeviceStatus, 149 | /// The features which the driver says it supports. 150 | pub driver_features: u64, 151 | /// The guest page size set by the driver. 152 | pub guest_page_size: u32, 153 | /// Whether the transport has an interrupt pending. 154 | pub interrupt_pending: bool, 155 | /// The state of the transport's queues. 156 | pub queues: Vec, 157 | /// The config generation which the transport should report. 158 | pub config_generation: u32, 159 | /// The state of the transport's VirtIO configuration space. 160 | pub config_space: C, 161 | } 162 | 163 | impl Debug for State { 164 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 165 | f.debug_struct("State") 166 | .field("status", &self.status) 167 | .field("driver_features", &self.driver_features) 168 | .field("guest_page_size", &self.guest_page_size) 169 | .field("interrupt_pending", &self.interrupt_pending) 170 | .field("queues", &self.queues) 171 | .field("config_generation", &self.config_generation) 172 | .field("config_space", &"...") 173 | .finish() 174 | } 175 | } 176 | 177 | impl State { 178 | /// Creates a state for a fake transport, with the given queues and VirtIO configuration space. 179 | pub const fn new(queues: Vec, config_space: C) -> Self { 180 | Self { 181 | status: DeviceStatus::empty(), 182 | driver_features: 0, 183 | guest_page_size: 0, 184 | interrupt_pending: false, 185 | queues, 186 | config_generation: 0, 187 | config_space, 188 | } 189 | } 190 | 191 | /// Simulates the device writing to the given queue. 192 | /// 193 | /// The fake device always uses descriptors in order. 194 | pub fn write_to_queue(&mut self, queue_index: u16, data: &[u8]) { 195 | let queue = &self.queues[queue_index as usize]; 196 | assert_ne!(queue.descriptors, 0); 197 | assert!(fake_read_write_queue( 198 | queue.descriptors as *const [Descriptor; QUEUE_SIZE], 199 | queue.driver_area as *const u8, 200 | queue.device_area as *mut u8, 201 | |input| { 202 | assert_eq!(input, Vec::new()); 203 | data.to_owned() 204 | }, 205 | )); 206 | } 207 | 208 | /// Simulates the device reading from the given queue. 209 | /// 210 | /// Data is read into the `data` buffer passed in. Returns the number of bytes actually read. 211 | /// 212 | /// The fake device always uses descriptors in order. 213 | pub fn read_from_queue(&mut self, queue_index: u16) -> Vec { 214 | let queue = &self.queues[queue_index as usize]; 215 | assert_ne!(queue.descriptors, 0); 216 | 217 | let mut ret = None; 218 | 219 | // Read data from the queue but don't write any response. 220 | assert!(fake_read_write_queue( 221 | queue.descriptors as *const [Descriptor; QUEUE_SIZE], 222 | queue.driver_area as *const u8, 223 | queue.device_area as *mut u8, 224 | |input| { 225 | ret = Some(input); 226 | Vec::new() 227 | }, 228 | )); 229 | 230 | ret.unwrap() 231 | } 232 | 233 | /// Simulates the device reading data from the given queue and then writing a response back. 234 | /// 235 | /// The fake device always uses descriptors in order. 236 | /// 237 | /// Returns true if a descriptor chain was available and processed, or false if no descriptors were 238 | /// available. 239 | pub fn read_write_queue( 240 | &mut self, 241 | queue_index: u16, 242 | handler: impl FnOnce(Vec) -> Vec, 243 | ) -> bool { 244 | let queue = &self.queues[queue_index as usize]; 245 | assert_ne!(queue.descriptors, 0); 246 | fake_read_write_queue( 247 | queue.descriptors as *const [Descriptor; QUEUE_SIZE], 248 | queue.driver_area as *const u8, 249 | queue.device_area as *mut u8, 250 | handler, 251 | ) 252 | } 253 | 254 | /// Waits until the given queue is notified. 255 | pub fn wait_until_queue_notified(state: &Mutex, queue_index: u16) { 256 | while !Self::poll_queue_notified(state, queue_index) { 257 | thread::sleep(Duration::from_millis(10)); 258 | } 259 | } 260 | 261 | /// Checks if the given queue has been notified. 262 | /// 263 | /// If it has, returns true and resets the status so this will return false until it is notified 264 | /// again. 265 | pub fn poll_queue_notified(state: &Mutex, queue_index: u16) -> bool { 266 | state.lock().unwrap().queues[usize::from(queue_index)] 267 | .notified 268 | .swap(false, Ordering::SeqCst) 269 | } 270 | } 271 | 272 | /// The status of a fake virtqueue. 273 | #[derive(Debug, Default)] 274 | pub struct QueueStatus { 275 | /// The size of the fake virtqueue. 276 | pub size: u32, 277 | /// The physical address set for the queue's descriptors. 278 | pub descriptors: PhysAddr, 279 | /// The physical address set for the queue's driver area. 280 | pub driver_area: PhysAddr, 281 | /// The physical address set for the queue's device area. 282 | pub device_area: PhysAddr, 283 | /// Whether the queue has been notified by the driver since last we checked. 284 | pub notified: AtomicBool, 285 | } 286 | -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! VirtIO transports. 2 | 3 | #[cfg(test)] 4 | pub mod fake; 5 | pub mod mmio; 6 | pub mod pci; 7 | mod some; 8 | #[cfg(target_arch = "x86_64")] 9 | pub mod x86_64; 10 | 11 | use crate::{PhysAddr, Result, PAGE_SIZE}; 12 | use bitflags::{bitflags, Flags}; 13 | use core::{ 14 | fmt::{self, Debug, Formatter}, 15 | ops::BitAnd, 16 | }; 17 | use log::debug; 18 | pub use some::SomeTransport; 19 | use thiserror::Error; 20 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 21 | 22 | /// A VirtIO transport layer. 23 | pub trait Transport { 24 | /// Gets the device type. 25 | fn device_type(&self) -> DeviceType; 26 | 27 | /// Reads device features. 28 | fn read_device_features(&mut self) -> u64; 29 | 30 | /// Writes device features. 31 | fn write_driver_features(&mut self, driver_features: u64); 32 | 33 | /// Gets the max size of the given queue. 34 | fn max_queue_size(&mut self, queue: u16) -> u32; 35 | 36 | /// Notifies the given queue on the device. 37 | fn notify(&mut self, queue: u16); 38 | 39 | /// Gets the device status. 40 | fn get_status(&self) -> DeviceStatus; 41 | 42 | /// Sets the device status. 43 | fn set_status(&mut self, status: DeviceStatus); 44 | 45 | /// Sets the guest page size. 46 | fn set_guest_page_size(&mut self, guest_page_size: u32); 47 | 48 | /// Returns whether the transport requires queues to use the legacy layout. 49 | /// 50 | /// Ref: 2.6.2 Legacy Interfaces: A Note on Virtqueue Layout 51 | fn requires_legacy_layout(&self) -> bool; 52 | 53 | /// Sets up the given queue. 54 | fn queue_set( 55 | &mut self, 56 | queue: u16, 57 | size: u32, 58 | descriptors: PhysAddr, 59 | driver_area: PhysAddr, 60 | device_area: PhysAddr, 61 | ); 62 | 63 | /// Disables and resets the given queue. 64 | fn queue_unset(&mut self, queue: u16); 65 | 66 | /// Returns whether the queue is in use, i.e. has a nonzero PFN or is marked as ready. 67 | fn queue_used(&mut self, queue: u16) -> bool; 68 | 69 | /// Acknowledges an interrupt. 70 | /// 71 | /// Returns true on success. 72 | fn ack_interrupt(&mut self) -> bool; 73 | 74 | /// Begins initializing the device. 75 | /// 76 | /// Ref: virtio 3.1.1 Device Initialization 77 | /// 78 | /// Returns the negotiated set of features. 79 | fn begin_init + BitAnd + Debug>( 80 | &mut self, 81 | supported_features: F, 82 | ) -> F { 83 | self.set_status(DeviceStatus::empty()); 84 | self.set_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER); 85 | 86 | let device_features = F::from_bits_truncate(self.read_device_features()); 87 | debug!("Device features: {:?}", device_features); 88 | let negotiated_features = device_features & supported_features; 89 | self.write_driver_features(negotiated_features.bits()); 90 | 91 | self.set_status( 92 | DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK, 93 | ); 94 | 95 | self.set_guest_page_size(PAGE_SIZE as u32); 96 | 97 | negotiated_features 98 | } 99 | 100 | /// Finishes initializing the device. 101 | fn finish_init(&mut self) { 102 | self.set_status( 103 | DeviceStatus::ACKNOWLEDGE 104 | | DeviceStatus::DRIVER 105 | | DeviceStatus::FEATURES_OK 106 | | DeviceStatus::DRIVER_OK, 107 | ); 108 | } 109 | 110 | /// Reads the configuration space generation. 111 | fn read_config_generation(&self) -> u32; 112 | 113 | /// Reads a value from the device config space. 114 | fn read_config_space(&self, offset: usize) -> Result; 115 | 116 | /// Writes a value to the device config space. 117 | fn write_config_space( 118 | &mut self, 119 | offset: usize, 120 | value: T, 121 | ) -> Result<()>; 122 | 123 | /// Safely reads multiple fields from config space by ensuring that the config generation is the 124 | /// same before and after all reads, and retrying if not. 125 | fn read_consistent(&self, f: impl Fn() -> Result) -> Result { 126 | loop { 127 | let before = self.read_config_generation(); 128 | let result = f(); 129 | let after = self.read_config_generation(); 130 | if before == after { 131 | break result; 132 | } 133 | } 134 | } 135 | } 136 | 137 | /// The device status field. Writing 0 into this field resets the device. 138 | #[derive(Copy, Clone, Default, Eq, FromBytes, Immutable, IntoBytes, PartialEq)] 139 | pub struct DeviceStatus(u32); 140 | 141 | impl Debug for DeviceStatus { 142 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 143 | write!(f, "DeviceStatus(")?; 144 | bitflags::parser::to_writer(self, &mut *f)?; 145 | write!(f, ")")?; 146 | Ok(()) 147 | } 148 | } 149 | 150 | bitflags! { 151 | impl DeviceStatus: u32 { 152 | /// Indicates that the guest OS has found the device and recognized it 153 | /// as a valid virtio device. 154 | const ACKNOWLEDGE = 1; 155 | 156 | /// Indicates that the guest OS knows how to drive the device. 157 | const DRIVER = 2; 158 | 159 | /// Indicates that something went wrong in the guest, and it has given 160 | /// up on the device. This could be an internal error, or the driver 161 | /// didn’t like the device for some reason, or even a fatal error 162 | /// during device operation. 163 | const FAILED = 128; 164 | 165 | /// Indicates that the driver has acknowledged all the features it 166 | /// understands, and feature negotiation is complete. 167 | const FEATURES_OK = 8; 168 | 169 | /// Indicates that the driver is set up and ready to drive the device. 170 | const DRIVER_OK = 4; 171 | 172 | /// Indicates that the device has experienced an error from which it 173 | /// can’t recover. 174 | const DEVICE_NEEDS_RESET = 64; 175 | } 176 | } 177 | 178 | /// Types of virtio devices. 179 | #[repr(u8)] 180 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 181 | #[allow(missing_docs)] 182 | pub enum DeviceType { 183 | Network = 1, 184 | Block = 2, 185 | Console = 3, 186 | EntropySource = 4, 187 | MemoryBallooning = 5, 188 | IoMemory = 6, 189 | Rpmsg = 7, 190 | ScsiHost = 8, 191 | _9P = 9, 192 | Mac80211 = 10, 193 | RprocSerial = 11, 194 | VirtioCAIF = 12, 195 | MemoryBalloon = 13, 196 | GPU = 16, 197 | Timer = 17, 198 | Input = 18, 199 | Socket = 19, 200 | Crypto = 20, 201 | SignalDistributionModule = 21, 202 | Pstore = 22, 203 | IOMMU = 23, 204 | Memory = 24, 205 | Sound = 25, 206 | } 207 | 208 | /// Errors converting a number to a virtio device type. 209 | #[derive(Copy, Clone, Debug, Eq, Error, PartialEq)] 210 | pub enum DeviceTypeError { 211 | /// Invalid or unknown virtio device type. 212 | #[error("Invalid or unknown virtio device type {0}")] 213 | InvalidDeviceType(u32), 214 | } 215 | 216 | impl TryFrom for DeviceType { 217 | type Error = DeviceTypeError; 218 | 219 | fn try_from(virtio_device_id: u32) -> core::result::Result { 220 | match virtio_device_id { 221 | 1 => Ok(DeviceType::Network), 222 | 2 => Ok(DeviceType::Block), 223 | 3 => Ok(DeviceType::Console), 224 | 4 => Ok(DeviceType::EntropySource), 225 | 5 => Ok(DeviceType::MemoryBalloon), 226 | 6 => Ok(DeviceType::IoMemory), 227 | 7 => Ok(DeviceType::Rpmsg), 228 | 8 => Ok(DeviceType::ScsiHost), 229 | 9 => Ok(DeviceType::_9P), 230 | 10 => Ok(DeviceType::Mac80211), 231 | 11 => Ok(DeviceType::RprocSerial), 232 | 12 => Ok(DeviceType::VirtioCAIF), 233 | 13 => Ok(DeviceType::MemoryBalloon), 234 | 16 => Ok(DeviceType::GPU), 235 | 17 => Ok(DeviceType::Timer), 236 | 18 => Ok(DeviceType::Input), 237 | 19 => Ok(DeviceType::Socket), 238 | 20 => Ok(DeviceType::Crypto), 239 | 21 => Ok(DeviceType::SignalDistributionModule), 240 | 22 => Ok(DeviceType::Pstore), 241 | 23 => Ok(DeviceType::IOMMU), 242 | 24 => Ok(DeviceType::Memory), 243 | 25 => Ok(DeviceType::Sound), 244 | _ => Err(DeviceTypeError::InvalidDeviceType(virtio_device_id)), 245 | } 246 | } 247 | } 248 | 249 | impl TryFrom for DeviceType { 250 | type Error = DeviceTypeError; 251 | 252 | fn try_from(virtio_device_id: u16) -> core::result::Result { 253 | u32::from(virtio_device_id).try_into() 254 | } 255 | } 256 | 257 | impl TryFrom for DeviceType { 258 | type Error = DeviceTypeError; 259 | 260 | fn try_from(virtio_device_id: u8) -> core::result::Result { 261 | u32::from(virtio_device_id).try_into() 262 | } 263 | } 264 | 265 | #[cfg(test)] 266 | mod tests { 267 | use super::*; 268 | 269 | #[test] 270 | fn debug_device_status() { 271 | let status = DeviceStatus::from_bits_retain(0x23); 272 | assert_eq!( 273 | format!("{:?}", status), 274 | "DeviceStatus(ACKNOWLEDGE | DRIVER | 0x20)" 275 | ); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/transport/some.rs: -------------------------------------------------------------------------------- 1 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 2 | 3 | use super::{mmio::MmioTransport, pci::PciTransport, DeviceStatus, DeviceType, Transport}; 4 | use crate::{PhysAddr, Result}; 5 | 6 | /// A wrapper for an arbitrary VirtIO transport, either MMIO or PCI. 7 | #[derive(Debug)] 8 | pub enum SomeTransport<'a> { 9 | /// An MMIO transport. 10 | Mmio(MmioTransport<'a>), 11 | /// A PCI transport. 12 | Pci(PciTransport), 13 | /// An x86-64 pKVM PCI transport. 14 | #[cfg(target_arch = "x86_64")] 15 | HypPci(super::x86_64::HypPciTransport), 16 | } 17 | 18 | impl<'a> From> for SomeTransport<'a> { 19 | fn from(mmio: MmioTransport<'a>) -> Self { 20 | Self::Mmio(mmio) 21 | } 22 | } 23 | 24 | impl From for SomeTransport<'_> { 25 | fn from(pci: PciTransport) -> Self { 26 | Self::Pci(pci) 27 | } 28 | } 29 | 30 | impl Transport for SomeTransport<'_> { 31 | fn device_type(&self) -> DeviceType { 32 | match self { 33 | Self::Mmio(mmio) => mmio.device_type(), 34 | Self::Pci(pci) => pci.device_type(), 35 | #[cfg(target_arch = "x86_64")] 36 | Self::HypPci(pci) => pci.device_type(), 37 | } 38 | } 39 | 40 | fn read_device_features(&mut self) -> u64 { 41 | match self { 42 | Self::Mmio(mmio) => mmio.read_device_features(), 43 | Self::Pci(pci) => pci.read_device_features(), 44 | #[cfg(target_arch = "x86_64")] 45 | Self::HypPci(pci) => pci.read_device_features(), 46 | } 47 | } 48 | 49 | fn write_driver_features(&mut self, driver_features: u64) { 50 | match self { 51 | Self::Mmio(mmio) => mmio.write_driver_features(driver_features), 52 | Self::Pci(pci) => pci.write_driver_features(driver_features), 53 | #[cfg(target_arch = "x86_64")] 54 | Self::HypPci(pci) => pci.write_driver_features(driver_features), 55 | } 56 | } 57 | 58 | fn max_queue_size(&mut self, queue: u16) -> u32 { 59 | match self { 60 | Self::Mmio(mmio) => mmio.max_queue_size(queue), 61 | Self::Pci(pci) => pci.max_queue_size(queue), 62 | #[cfg(target_arch = "x86_64")] 63 | Self::HypPci(pci) => pci.max_queue_size(queue), 64 | } 65 | } 66 | 67 | fn notify(&mut self, queue: u16) { 68 | match self { 69 | Self::Mmio(mmio) => mmio.notify(queue), 70 | Self::Pci(pci) => pci.notify(queue), 71 | #[cfg(target_arch = "x86_64")] 72 | Self::HypPci(pci) => pci.notify(queue), 73 | } 74 | } 75 | 76 | fn get_status(&self) -> DeviceStatus { 77 | match self { 78 | Self::Mmio(mmio) => mmio.get_status(), 79 | Self::Pci(pci) => pci.get_status(), 80 | #[cfg(target_arch = "x86_64")] 81 | Self::HypPci(pci) => pci.get_status(), 82 | } 83 | } 84 | 85 | fn set_status(&mut self, status: DeviceStatus) { 86 | match self { 87 | Self::Mmio(mmio) => mmio.set_status(status), 88 | Self::Pci(pci) => pci.set_status(status), 89 | #[cfg(target_arch = "x86_64")] 90 | Self::HypPci(pci) => pci.set_status(status), 91 | } 92 | } 93 | 94 | fn set_guest_page_size(&mut self, guest_page_size: u32) { 95 | match self { 96 | Self::Mmio(mmio) => mmio.set_guest_page_size(guest_page_size), 97 | Self::Pci(pci) => pci.set_guest_page_size(guest_page_size), 98 | #[cfg(target_arch = "x86_64")] 99 | Self::HypPci(pci) => pci.set_guest_page_size(guest_page_size), 100 | } 101 | } 102 | 103 | fn requires_legacy_layout(&self) -> bool { 104 | match self { 105 | Self::Mmio(mmio) => mmio.requires_legacy_layout(), 106 | Self::Pci(pci) => pci.requires_legacy_layout(), 107 | #[cfg(target_arch = "x86_64")] 108 | Self::HypPci(pci) => pci.requires_legacy_layout(), 109 | } 110 | } 111 | 112 | fn queue_set( 113 | &mut self, 114 | queue: u16, 115 | size: u32, 116 | descriptors: PhysAddr, 117 | driver_area: PhysAddr, 118 | device_area: PhysAddr, 119 | ) { 120 | match self { 121 | Self::Mmio(mmio) => mmio.queue_set(queue, size, descriptors, driver_area, device_area), 122 | Self::Pci(pci) => pci.queue_set(queue, size, descriptors, driver_area, device_area), 123 | #[cfg(target_arch = "x86_64")] 124 | Self::HypPci(pci) => pci.queue_set(queue, size, descriptors, driver_area, device_area), 125 | } 126 | } 127 | 128 | fn queue_unset(&mut self, queue: u16) { 129 | match self { 130 | Self::Mmio(mmio) => mmio.queue_unset(queue), 131 | Self::Pci(pci) => pci.queue_unset(queue), 132 | #[cfg(target_arch = "x86_64")] 133 | Self::HypPci(pci) => pci.queue_unset(queue), 134 | } 135 | } 136 | 137 | fn queue_used(&mut self, queue: u16) -> bool { 138 | match self { 139 | Self::Mmio(mmio) => mmio.queue_used(queue), 140 | Self::Pci(pci) => pci.queue_used(queue), 141 | #[cfg(target_arch = "x86_64")] 142 | Self::HypPci(pci) => pci.queue_used(queue), 143 | } 144 | } 145 | 146 | fn ack_interrupt(&mut self) -> bool { 147 | match self { 148 | Self::Mmio(mmio) => mmio.ack_interrupt(), 149 | Self::Pci(pci) => pci.ack_interrupt(), 150 | #[cfg(target_arch = "x86_64")] 151 | Self::HypPci(pci) => pci.ack_interrupt(), 152 | } 153 | } 154 | 155 | fn read_config_generation(&self) -> u32 { 156 | match self { 157 | Self::Mmio(mmio) => mmio.read_config_generation(), 158 | Self::Pci(pci) => pci.read_config_generation(), 159 | #[cfg(target_arch = "x86_64")] 160 | Self::HypPci(pci) => pci.read_config_generation(), 161 | } 162 | } 163 | 164 | fn read_config_space(&self, offset: usize) -> Result { 165 | match self { 166 | Self::Mmio(mmio) => mmio.read_config_space(offset), 167 | Self::Pci(pci) => pci.read_config_space(offset), 168 | #[cfg(target_arch = "x86_64")] 169 | Self::HypPci(pci) => pci.read_config_space(offset), 170 | } 171 | } 172 | 173 | fn write_config_space( 174 | &mut self, 175 | offset: usize, 176 | value: T, 177 | ) -> Result<()> { 178 | match self { 179 | Self::Mmio(mmio) => mmio.write_config_space(offset, value), 180 | Self::Pci(pci) => pci.write_config_space(offset, value), 181 | #[cfg(target_arch = "x86_64")] 182 | Self::HypPci(pci) => pci.write_config_space(offset, value), 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/transport/x86_64.rs: -------------------------------------------------------------------------------- 1 | //! x86-64 specific transports. 2 | 3 | mod cam; 4 | mod hypercalls; 5 | 6 | use super::{ 7 | pci::{ 8 | bus::{ConfigurationAccess, DeviceFunction, PciRoot, PCI_CAP_ID_VNDR}, 9 | device_type, CommonCfg, VirtioCapabilityInfo, VirtioPciError, CAP_BAR_OFFSET, 10 | CAP_BAR_OFFSET_OFFSET, CAP_LENGTH_OFFSET, CAP_NOTIFY_OFF_MULTIPLIER_OFFSET, 11 | VIRTIO_PCI_CAP_COMMON_CFG, VIRTIO_PCI_CAP_DEVICE_CFG, VIRTIO_PCI_CAP_ISR_CFG, 12 | VIRTIO_PCI_CAP_NOTIFY_CFG, VIRTIO_VENDOR_ID, 13 | }, 14 | DeviceStatus, DeviceType, Transport, 15 | }; 16 | use crate::{hal::PhysAddr, Error}; 17 | pub use cam::HypCam; 18 | use hypercalls::HypIoRegion; 19 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 20 | 21 | macro_rules! configread { 22 | ($common_cfg:expr, $field:ident) => { 23 | $common_cfg.read(core::mem::offset_of!(CommonCfg, $field)) 24 | }; 25 | } 26 | 27 | macro_rules! configwrite { 28 | ($common_cfg:expr, $field:ident, $value:expr) => { 29 | $common_cfg.write(core::mem::offset_of!(CommonCfg, $field), $value) 30 | }; 31 | } 32 | 33 | /// PCI transport for VirtIO using hypercalls implemented by the x86-64 pKVM hypervisor for IO BARs. 34 | #[derive(Debug)] 35 | pub struct HypPciTransport { 36 | device_type: DeviceType, 37 | /// The bus, device and function identifier for the VirtIO device. 38 | device_function: DeviceFunction, 39 | /// The common configuration structure within some BAR. 40 | common_cfg: HypIoRegion, 41 | /// The start of the queue notification region within some BAR. 42 | notify_region: HypIoRegion, 43 | notify_off_multiplier: u32, 44 | /// The ISR status register within some BAR. 45 | isr_status: HypIoRegion, 46 | /// The VirtIO device-specific configuration within some BAR. 47 | config_space: Option, 48 | } 49 | 50 | impl HypPciTransport { 51 | /// Constructs a new x86-64 pKVM PCI VirtIO transport for the given device function on the given 52 | /// PCI root controller. 53 | pub fn new( 54 | root: &mut PciRoot, 55 | device_function: DeviceFunction, 56 | ) -> Result { 57 | let device_vendor = root.configuration_access.read_word(device_function, 0); 58 | let device_id = (device_vendor >> 16) as u16; 59 | let vendor_id = device_vendor as u16; 60 | if vendor_id != VIRTIO_VENDOR_ID { 61 | return Err(VirtioPciError::InvalidVendorId(vendor_id)); 62 | } 63 | let device_type = 64 | device_type(device_id).ok_or(VirtioPciError::InvalidDeviceId(device_id))?; 65 | 66 | // Find the PCI capabilities we need. 67 | let mut common_cfg = None; 68 | let mut notify_cfg = None; 69 | let mut notify_off_multiplier = 0; 70 | let mut isr_cfg = None; 71 | let mut device_cfg = None; 72 | for capability in root.capabilities(device_function) { 73 | if capability.id != PCI_CAP_ID_VNDR { 74 | continue; 75 | } 76 | let cap_len = capability.private_header as u8; 77 | let cfg_type = (capability.private_header >> 8) as u8; 78 | if cap_len < 16 { 79 | continue; 80 | } 81 | let struct_info = VirtioCapabilityInfo { 82 | bar: root 83 | .configuration_access 84 | .read_word(device_function, capability.offset + CAP_BAR_OFFSET) 85 | as u8, 86 | offset: root 87 | .configuration_access 88 | .read_word(device_function, capability.offset + CAP_BAR_OFFSET_OFFSET), 89 | length: root 90 | .configuration_access 91 | .read_word(device_function, capability.offset + CAP_LENGTH_OFFSET), 92 | }; 93 | 94 | match cfg_type { 95 | VIRTIO_PCI_CAP_COMMON_CFG if common_cfg.is_none() => { 96 | common_cfg = Some(struct_info); 97 | } 98 | VIRTIO_PCI_CAP_NOTIFY_CFG if cap_len >= 20 && notify_cfg.is_none() => { 99 | notify_cfg = Some(struct_info); 100 | notify_off_multiplier = root.configuration_access.read_word( 101 | device_function, 102 | capability.offset + CAP_NOTIFY_OFF_MULTIPLIER_OFFSET, 103 | ); 104 | } 105 | VIRTIO_PCI_CAP_ISR_CFG if isr_cfg.is_none() => { 106 | isr_cfg = Some(struct_info); 107 | } 108 | VIRTIO_PCI_CAP_DEVICE_CFG if device_cfg.is_none() => { 109 | device_cfg = Some(struct_info); 110 | } 111 | _ => {} 112 | } 113 | } 114 | 115 | let common_cfg = get_bar_region::( 116 | root, 117 | device_function, 118 | &common_cfg.ok_or(VirtioPciError::MissingCommonConfig)?, 119 | )?; 120 | 121 | let notify_cfg = notify_cfg.ok_or(VirtioPciError::MissingNotifyConfig)?; 122 | if notify_off_multiplier % 2 != 0 { 123 | return Err(VirtioPciError::InvalidNotifyOffMultiplier( 124 | notify_off_multiplier, 125 | )); 126 | } 127 | let notify_region = get_bar_region::(root, device_function, ¬ify_cfg)?; 128 | 129 | let isr_status = get_bar_region::( 130 | root, 131 | device_function, 132 | &isr_cfg.ok_or(VirtioPciError::MissingIsrConfig)?, 133 | )?; 134 | 135 | let config_space = if let Some(device_cfg) = device_cfg { 136 | Some(get_bar_region::( 137 | root, 138 | device_function, 139 | &device_cfg, 140 | )?) 141 | } else { 142 | None 143 | }; 144 | 145 | Ok(Self { 146 | device_type, 147 | device_function, 148 | common_cfg, 149 | notify_region, 150 | notify_off_multiplier, 151 | isr_status, 152 | config_space, 153 | }) 154 | } 155 | } 156 | 157 | impl Transport for HypPciTransport { 158 | fn device_type(&self) -> DeviceType { 159 | self.device_type 160 | } 161 | 162 | fn read_device_features(&mut self) -> u64 { 163 | configwrite!(self.common_cfg, device_feature_select, 0u32); 164 | let device_features_low: u32 = configread!(self.common_cfg, device_feature); 165 | configwrite!(self.common_cfg, device_feature_select, 1u32); 166 | let device_features_high: u32 = configread!(self.common_cfg, device_feature); 167 | ((device_features_high as u64) << 32) | (device_features_low as u64) 168 | } 169 | 170 | fn write_driver_features(&mut self, driver_features: u64) { 171 | configwrite!(self.common_cfg, driver_feature_select, 0u32); 172 | configwrite!(self.common_cfg, driver_feature, driver_features as u32); 173 | configwrite!(self.common_cfg, driver_feature_select, 1u32); 174 | configwrite!( 175 | self.common_cfg, 176 | driver_feature, 177 | (driver_features >> 32) as u32 178 | ); 179 | } 180 | 181 | fn max_queue_size(&mut self, queue: u16) -> u32 { 182 | configwrite!(self.common_cfg, queue_select, queue); 183 | let queue_size: u16 = configread!(self.common_cfg, queue_size); 184 | queue_size.into() 185 | } 186 | 187 | fn notify(&mut self, queue: u16) { 188 | configwrite!(self.common_cfg, queue_select, queue); 189 | // TODO: Consider caching this somewhere (per queue). 190 | let queue_notify_off: u16 = configread!(self.common_cfg, queue_notify_off); 191 | 192 | let offset_bytes = usize::from(queue_notify_off) * self.notify_off_multiplier as usize; 193 | self.notify_region.write(offset_bytes, queue); 194 | } 195 | 196 | fn get_status(&self) -> DeviceStatus { 197 | let status: u8 = configread!(self.common_cfg, device_status); 198 | DeviceStatus::from_bits_truncate(status.into()) 199 | } 200 | 201 | fn set_status(&mut self, status: DeviceStatus) { 202 | configwrite!(self.common_cfg, device_status, status.bits() as u8); 203 | } 204 | 205 | fn set_guest_page_size(&mut self, _guest_page_size: u32) { 206 | // No-op, the PCI transport doesn't care. 207 | } 208 | 209 | fn requires_legacy_layout(&self) -> bool { 210 | false 211 | } 212 | 213 | fn queue_set( 214 | &mut self, 215 | queue: u16, 216 | size: u32, 217 | descriptors: PhysAddr, 218 | driver_area: PhysAddr, 219 | device_area: PhysAddr, 220 | ) { 221 | configwrite!(self.common_cfg, queue_select, queue); 222 | configwrite!(self.common_cfg, queue_size, size as u16); 223 | configwrite!(self.common_cfg, queue_desc, descriptors as u64); 224 | configwrite!(self.common_cfg, queue_driver, driver_area as u64); 225 | configwrite!(self.common_cfg, queue_device, device_area as u64); 226 | configwrite!(self.common_cfg, queue_enable, 1u16); 227 | } 228 | 229 | fn queue_unset(&mut self, _queue: u16) { 230 | // The VirtIO spec doesn't allow queues to be unset once they have been set up for the PCI 231 | // transport, so this is a no-op. 232 | } 233 | 234 | fn queue_used(&mut self, queue: u16) -> bool { 235 | configwrite!(self.common_cfg, queue_select, queue); 236 | let queue_enable: u16 = configread!(self.common_cfg, queue_enable); 237 | queue_enable == 1 238 | } 239 | 240 | fn ack_interrupt(&mut self) -> bool { 241 | // Safe because the common config pointer is valid and we checked in get_bar_region that it 242 | // was aligned. 243 | // Reading the ISR status resets it to 0 and causes the device to de-assert the interrupt. 244 | let isr_status: u8 = self.isr_status.read(0); 245 | // TODO: Distinguish between queue interrupt and device configuration interrupt. 246 | isr_status & 0x3 != 0 247 | } 248 | 249 | fn read_config_generation(&self) -> u32 { 250 | configread!(self.common_cfg, config_generation) 251 | } 252 | 253 | fn read_config_space(&self, offset: usize) -> Result { 254 | assert!(align_of::() <= 4, 255 | "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", 256 | align_of::()); 257 | assert_eq!(offset % align_of::(), 0); 258 | 259 | let config_space = self.config_space.ok_or(Error::ConfigSpaceMissing)?; 260 | if config_space.size < offset + size_of::() { 261 | Err(Error::ConfigSpaceTooSmall) 262 | } else { 263 | Ok(config_space.read(offset)) 264 | } 265 | } 266 | 267 | fn write_config_space( 268 | &mut self, 269 | offset: usize, 270 | value: T, 271 | ) -> Result<(), Error> { 272 | assert!(align_of::() <= 4, 273 | "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", 274 | align_of::()); 275 | assert_eq!(offset % align_of::(), 0); 276 | 277 | let config_space = self.config_space.ok_or(Error::ConfigSpaceMissing)?; 278 | if config_space.size < offset + size_of::() { 279 | Err(Error::ConfigSpaceTooSmall) 280 | } else { 281 | config_space.write(offset, value); 282 | Ok(()) 283 | } 284 | } 285 | } 286 | 287 | fn get_bar_region( 288 | root: &mut PciRoot, 289 | device_function: DeviceFunction, 290 | struct_info: &VirtioCapabilityInfo, 291 | ) -> Result { 292 | let bar_info = root 293 | .bar_info(device_function, struct_info.bar)? 294 | .ok_or(VirtioPciError::BarNotAllocated(struct_info.bar))?; 295 | let (bar_address, bar_size) = bar_info 296 | .memory_address_size() 297 | .ok_or(VirtioPciError::UnexpectedIoBar)?; 298 | if bar_address == 0 { 299 | return Err(VirtioPciError::BarNotAllocated(struct_info.bar)); 300 | } 301 | if u64::from(struct_info.offset + struct_info.length) > bar_size 302 | || size_of::() > struct_info.length as usize 303 | { 304 | return Err(VirtioPciError::BarOffsetOutOfRange); 305 | } 306 | let paddr = bar_address as PhysAddr + struct_info.offset as PhysAddr; 307 | if paddr % align_of::() != 0 { 308 | return Err(VirtioPciError::Misaligned { 309 | address: paddr, 310 | alignment: align_of::(), 311 | }); 312 | } 313 | Ok(HypIoRegion { 314 | paddr, 315 | size: struct_info.length as usize, 316 | }) 317 | } 318 | -------------------------------------------------------------------------------- /src/transport/x86_64/cam.rs: -------------------------------------------------------------------------------- 1 | use super::hypercalls::{cpuid_signature, hyp_io_read, hyp_io_write}; 2 | use crate::transport::pci::bus::{Cam, ConfigurationAccess, DeviceFunction}; 3 | 4 | const PKVM_SIGNATURE: &[u8] = b"PKVM"; 5 | 6 | /// A PCI configuration access mechanism using hypercalls implemented by the x86-64 pKVM hypervisor. 7 | pub struct HypCam { 8 | /// The physical base address of the PCI root complex. 9 | phys_base: usize, 10 | cam: Cam, 11 | } 12 | 13 | impl HypCam { 14 | /// Creates a new `HypCam` for the PCI root complex at the given physical base address. 15 | pub fn new(phys_base: usize, cam: Cam) -> Self { 16 | Self { phys_base, cam } 17 | } 18 | 19 | /// Returns whether we are running under pKVM by checking the CPU ID signature. 20 | pub fn is_pkvm() -> bool { 21 | cpuid_signature() == PKVM_SIGNATURE 22 | } 23 | } 24 | 25 | impl ConfigurationAccess for HypCam { 26 | fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 { 27 | let address = self.cam.cam_offset(device_function, register_offset); 28 | hyp_io_read(self.phys_base + (address as usize), 4) as u32 29 | } 30 | 31 | fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) { 32 | let address = self.cam.cam_offset(device_function, register_offset); 33 | hyp_io_write(self.phys_base + (address as usize), 4, data.into()); 34 | } 35 | 36 | unsafe fn unsafe_clone(&self) -> Self { 37 | Self { 38 | phys_base: self.phys_base, 39 | cam: self.cam, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/transport/x86_64/hypercalls.rs: -------------------------------------------------------------------------------- 1 | //! Hypercalls for x86-64 pKVM. 2 | 3 | use core::arch::asm; 4 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 5 | 6 | /// This CPUID returns the signature and should be used to determine if VM is running under pKVM, 7 | /// KVM or not. See the Linux header `arch/x86/include/uapi/asm/kvm_para.h`. 8 | const KVM_CPUID_SIGNATURE: u32 = 0x40000000; 9 | 10 | // See `include/uapi/linux/kvm_para.h`. (These hypercalls numbers can change depending on the 11 | // upstream progress.) 12 | const KVM_HC_PKVM_OP: u32 = 20; 13 | const PKVM_GHC_IOREAD: u32 = KVM_HC_PKVM_OP + 3; 14 | const PKVM_GHC_IOWRITE: u32 = KVM_HC_PKVM_OP + 4; 15 | 16 | /// The maximum number of bytes that can be read or written by a single IO hypercall. 17 | const HYP_IO_MAX: usize = 8; 18 | 19 | /// Gets the signature CPU ID. 20 | pub fn cpuid_signature() -> [u8; 4] { 21 | let signature: u32; 22 | 23 | // SAFETY: Assembly call. The argument for cpuid is passed via rax and in case of 24 | // KVM_CPUID_SIGNATURE returned via rbx, rcx and rdx. Ideally using a named argument in 25 | // inline asm for rbx would be more straightforward, but when "rbx" is directly used 26 | // LLVM complains that it is used internally. 27 | // 28 | // Therefore use r8 instead and push rbx to the stack before making cpuid call, store 29 | // rbx content to r8 as use it as inline asm output and pop the rbx. 30 | unsafe { 31 | asm!( 32 | "push rbx", 33 | "cpuid", 34 | "mov r8, rbx", 35 | "pop rbx", 36 | in("eax") KVM_CPUID_SIGNATURE, 37 | out("r8") signature, 38 | out("rcx") _, 39 | out("rdx") _, 40 | ); 41 | }; 42 | signature.to_le_bytes() 43 | } 44 | 45 | /// Asks the hypervisor to perform an IO read at the given physical address. 46 | pub fn hyp_io_read(address: usize, size: usize) -> u64 { 47 | let data; 48 | 49 | // SAFETY: Assembly call. Arguments for vmcall are passed via rax, rbx, rcx and rdx. Ideally 50 | // using a named argument in the inline asm for rbx would be more straightforward, but when 51 | // "rbx" is used directly LLVM complains that it is used internally. 52 | // 53 | // Therefore use r8 temporary, push rbx to the stack, perform proper call and pop rbx again. 54 | unsafe { 55 | asm!( 56 | "push rbx", 57 | "mov rbx, r8", 58 | "vmcall", 59 | "pop rbx", 60 | inout("rax") u64::from(PKVM_GHC_IOREAD) => data, 61 | in("r8") address, 62 | in("rcx") size, 63 | ); 64 | } 65 | data 66 | } 67 | 68 | /// Asks the hypervisor to perform an IO write at the given physical address. 69 | pub fn hyp_io_write(address: usize, size: usize, data: u64) { 70 | // SAFETY: Assembly call. Arguments for vmcall are passed via rax, rbx, rcx and rdx. Ideally 71 | // using a named argument in the inline asm for rbx would be more straightforward but when 72 | // "rbx" is used directly used LLVM complains that it is used internally. 73 | // 74 | // Therefore use r8 temporary, push rbx to the stack, perform proper call and pop rbx again. 75 | unsafe { 76 | asm!( 77 | "push rbx", 78 | "mov rbx, r8", 79 | "vmcall", 80 | "pop rbx", 81 | in("rax") PKVM_GHC_IOWRITE, 82 | in("r8") address, 83 | in("rcx") size, 84 | in("rdx") data, 85 | ); 86 | } 87 | } 88 | 89 | /// A region of physical address space which may be accessed by IO read and/or write hypercalls. 90 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 91 | pub struct HypIoRegion { 92 | /// The physical address of the start of the IO region. 93 | pub paddr: usize, 94 | /// The size of the IO region in bytes. 95 | pub size: usize, 96 | } 97 | 98 | impl HypIoRegion { 99 | pub fn read(self, offset: usize) -> T { 100 | assert!(offset + size_of::() <= self.size); 101 | assert!(size_of::() <= HYP_IO_MAX); 102 | 103 | let data = hyp_io_read(self.paddr + offset, size_of::()); 104 | T::read_from_prefix(data.as_bytes()).unwrap().0 105 | } 106 | 107 | pub fn write(self, offset: usize, value: T) { 108 | assert!(offset + size_of::() <= self.size); 109 | assert!(size_of::() <= HYP_IO_MAX); 110 | 111 | let mut data = 0; 112 | data.as_mut_bytes()[..size_of::()].copy_from_slice(value.as_bytes()); 113 | hyp_io_write(self.paddr + offset, size_of::(), data); 114 | } 115 | } 116 | --------------------------------------------------------------------------------