From 65d32bcb0af4b27863ffb92d7fc9500bc646ae83 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Sun, 22 Dec 2024 16:46:47 +0800 Subject: [PATCH] Introduce RISC-V MicroVM Signed-off-by: Ruoqing He --- Cargo.lock | 62 +- Cargo.toml | 22 +- acpi/src/acpi_table.rs | 6 + address_space/Cargo.toml | 1 + address_space/src/address_space.rs | 15 +- address_space/src/host_mmap.rs | 3 +- block_backend/src/file.rs | 28 +- block_backend/src/lib.rs | 63 + block_backend/src/qcow2/mod.rs | 83 +- block_backend/src/qcow2/refcount.rs | 58 +- block_backend/src/qcow2/table.rs | 15 +- block_backend/src/raw.rs | 9 +- boot_loader/Cargo.toml | 2 +- boot_loader/src/lib.rs | 9 + boot_loader/src/riscv64/mod.rs | 202 +++ build.rs | 3 + chardev_backend/src/chardev.rs | 254 +++- cpu/Cargo.toml | 2 +- cpu/src/lib.rs | 17 +- cpu/src/riscv64/mod.rs | 205 +++ cpu/src/x86_64/mod.rs | 2 +- devices/Cargo.toml | 8 +- devices/src/acpi/cpu_controller.rs | 16 +- devices/src/acpi/ged.rs | 24 +- devices/src/acpi/power.rs | 13 +- devices/src/camera_backend/demo.rs | 94 +- devices/src/camera_backend/mod.rs | 3 - devices/src/camera_backend/ohcam.rs | 81 +- .../src/interrupt_controller/aarch64/gicv3.rs | 77 +- devices/src/interrupt_controller/mod.rs | 9 +- .../src/interrupt_controller/riscv64/aia.rs | 172 +++ .../src/interrupt_controller/riscv64/mod.rs | 94 ++ devices/src/legacy/fwcfg.rs | 82 +- devices/src/legacy/mod.rs | 2 +- devices/src/legacy/pflash.rs | 15 +- devices/src/legacy/pl011.rs | 36 +- devices/src/legacy/pl031.rs | 14 +- devices/src/legacy/ramfb.rs | 37 +- devices/src/legacy/rtc.rs | 14 +- devices/src/legacy/serial.rs | 51 +- devices/src/lib.rs | 8 +- devices/src/misc/mod.rs | 2 +- devices/src/misc/pvpanic.rs | 55 +- devices/src/misc/scream/mod.rs | 16 +- devices/src/misc/scream/ohaudio.rs | 130 +- devices/src/pci/bus.rs | 19 +- devices/src/pci/config.rs | 2 +- devices/src/pci/demo_device/mod.rs | 63 +- devices/src/pci/host.rs | 27 +- devices/src/pci/intx.rs | 8 +- devices/src/pci/mod.rs | 83 +- devices/src/pci/msix.rs | 9 - devices/src/pci/root_port.rs | 62 +- devices/src/scsi/bus.rs | 20 +- devices/src/scsi/disk.rs | 143 +- devices/src/smbios/smbios_table.rs | 4 +- devices/src/sysbus/mod.rs | 239 +-- devices/src/usb/camera.rs | 23 +- devices/src/usb/config.rs | 24 +- devices/src/usb/descriptor.rs | 22 +- devices/src/usb/keyboard.rs | 17 +- devices/src/usb/mod.rs | 44 +- devices/src/usb/storage.rs | 68 +- devices/src/usb/tablet.rs | 10 +- devices/src/usb/uas.rs | 1289 +++++++++++++++++ devices/src/usb/usbhost/host_usblib.rs | 19 + devices/src/usb/usbhost/mod.rs | 58 +- devices/src/usb/usbhost/ohusb.rs | 82 ++ devices/src/usb/xhci/xhci_controller.rs | 62 +- devices/src/usb/xhci/xhci_pci.rs | 9 +- docs/config_guidebook.md | 9 +- docs/stratovirt-img.md | 10 + hypervisor/Cargo.toml | 2 +- hypervisor/src/kvm/aarch64/mod.rs | 6 +- hypervisor/src/kvm/interrupt.rs | 8 +- hypervisor/src/kvm/mod.rs | 196 ++- hypervisor/src/kvm/riscv64/aia.rs | 150 ++ hypervisor/src/kvm/riscv64/config_regs.rs | 37 + hypervisor/src/kvm/riscv64/core_regs.rs | 171 +++ hypervisor/src/kvm/riscv64/cpu_caps.rs | 38 + hypervisor/src/kvm/riscv64/mod.rs | 404 ++++++ hypervisor/src/kvm/riscv64/timer_regs.rs | 50 + hypervisor/src/kvm/x86_64/mod.rs | 8 +- hypervisor/src/lib.rs | 10 + .../src/test/aarch64/mod.rs | 19 +- hypervisor/src/test/listener.rs | 40 + hypervisor/src/test/mod.rs | 417 ++++++ image/src/img.rs | 101 +- image/src/main.rs | 3 +- machine/src/aarch64/micro.rs | 50 +- machine/src/aarch64/mod.rs | 2 +- machine/src/aarch64/standard.rs | 90 +- machine/src/error.rs | 4 +- machine/src/lib.rs | 736 ++++++---- machine/src/micro_common/mod.rs | 266 ++-- machine/src/micro_common/syscall.rs | 9 +- machine/src/riscv64/fdt.rs | 217 +++ machine/src/riscv64/micro.rs | 278 ++++ machine/src/riscv64/mod.rs | 14 + machine/src/standard_common/mod.rs | 27 +- machine/src/standard_common/syscall.rs | 1 + machine/src/x86_64/micro.rs | 19 +- machine/src/x86_64/mod.rs | 3 +- machine/src/x86_64/standard.rs | 49 +- machine_manager/src/cmdline.rs | 120 +- machine_manager/src/config/camera.rs | 9 +- machine_manager/src/config/chardev.rs | 765 ++++------ machine_manager/src/config/demo_dev.rs | 97 -- machine_manager/src/config/devices.rs | 45 +- machine_manager/src/config/display.rs | 115 +- machine_manager/src/config/drive.rs | 822 +++-------- machine_manager/src/config/fs.rs | 115 -- machine_manager/src/config/gpu.rs | 176 --- machine_manager/src/config/iothread.rs | 28 +- machine_manager/src/config/machine_config.rs | 718 ++++----- machine_manager/src/config/mod.rs | 586 ++++---- machine_manager/src/config/network.rs | 665 +++------ machine_manager/src/config/numa.rs | 228 ++- machine_manager/src/config/pci.rs | 138 +- machine_manager/src/config/pvpanic_pci.rs | 66 - machine_manager/src/config/rng.rs | 241 +-- machine_manager/src/config/sasl_auth.rs | 37 +- machine_manager/src/config/scsi.rs | 279 ---- machine_manager/src/config/smbios.rs | 251 ++-- machine_manager/src/config/tls_creds.rs | 58 +- machine_manager/src/config/usb.rs | 99 -- machine_manager/src/config/vfio.rs | 134 -- machine_manager/src/config/vnc.rs | 62 +- machine_manager/src/event_loop.rs | 47 +- machine_manager/src/machine.rs | 35 +- machine_manager/src/qmp/qmp_channel.rs | 7 +- machine_manager/src/qmp/qmp_schema.rs | 11 + machine_manager/src/signal_handler.rs | 2 +- machine_manager/src/temp_cleaner.rs | 9 +- migration/src/manager.rs | 20 + migration/src/protocol.rs | 4 + migration/src/snapshot.rs | 23 + src/main.rs | 59 +- tests/mod_test/src/libtest.rs | 8 +- tests/mod_test/tests/pvpanic_test.rs | 2 +- tests/mod_test/tests/usb_camera_test.rs | 17 +- trace/src/lib.rs | 2 +- trace/src/trace_scope.rs | 3 + trace/trace_generator/src/lib.rs | 7 +- trace/trace_info/acpi.toml | 23 + trace/trace_info/camera.toml | 12 + trace/trace_info/device_legacy.toml | 24 + trace/trace_info/memory.toml | 41 + trace/trace_info/misc.toml | 60 + trace/trace_info/ui.toml | 60 + trace/trace_info/usb.toml | 104 +- trace/trace_info/virtio.toml | 24 +- ui/src/input.rs | 28 + ui/src/ohui_srv/channel.rs | 143 +- ui/src/ohui_srv/mod.rs | 64 +- ui/src/ohui_srv/msg.rs | 6 + ui/src/ohui_srv/msg_handle.rs | 179 ++- ui/src/vnc/client_io.rs | 8 +- ui/src/vnc/mod.rs | 2 +- ui/src/vnc/server_io.rs | 2 +- util/Cargo.toml | 3 +- util/src/aio/mod.rs | 15 +- util/src/aio/raw.rs | 16 +- util/src/device_tree.rs | 6 + util/src/leak_bucket.rs | 4 +- util/src/lib.rs | 2 +- util/src/loop_context.rs | 74 +- util/src/ohos_binding/audio/mod.rs | 5 + util/src/ohos_binding/camera.rs | 6 +- util/src/ohos_binding/hwf_adapter/mod.rs | 23 + util/src/ohos_binding/hwf_adapter/usb.rs | 45 + util/src/ohos_binding/mod.rs | 2 + util/src/ohos_binding/usb.rs | 50 + util/src/seccomp.rs | 20 + util/src/socket.rs | 9 + util/src/test_helper.rs | 34 +- util/src/unix.rs | 86 +- vendor/memoffset-0.7.1/.cargo-checksum.json | 1 - vendor/memoffset-0.7.1/Cargo.toml | 36 - vendor/memoffset-0.7.1/LICENSE | 19 - vendor/memoffset-0.7.1/README.md | 65 - vendor/memoffset-0.7.1/build.rs | 22 - vendor/memoffset-0.7.1/src/lib.rs | 92 -- vendor/memoffset-0.7.1/src/offset_of.rs | 356 ----- vendor/memoffset-0.7.1/src/raw_field.rs | 226 --- vendor/memoffset-0.7.1/src/span_of.rs | 263 ---- vfio/Cargo.toml | 3 +- vfio/src/lib.rs | 8 +- vfio/src/vfio_pci.rs | 62 +- virtio/src/device/balloon.rs | 9 +- virtio/src/device/block.rs | 256 +++- virtio/src/device/gpu.rs | 99 +- virtio/src/device/net.rs | 247 +++- virtio/src/device/rng.rs | 150 +- virtio/src/device/scsi_cntlr.rs | 96 +- virtio/src/device/serial.rs | 121 +- virtio/src/lib.rs | 43 +- virtio/src/queue/mod.rs | 3 +- virtio/src/transport/virtio_mmio.rs | 116 +- virtio/src/transport/virtio_pci.rs | 290 ++-- virtio/src/vhost/kernel/mod.rs | 2 +- virtio/src/vhost/kernel/net.rs | 91 +- virtio/src/vhost/kernel/vsock.rs | 55 +- virtio/src/vhost/user/block.rs | 78 +- virtio/src/vhost/user/client.rs | 34 +- virtio/src/vhost/user/fs.rs | 77 +- virtio/src/vhost/user/mod.rs | 2 +- virtio/src/vhost/user/net.rs | 32 +- 208 files changed, 10607 insertions(+), 7601 deletions(-) create mode 100644 boot_loader/src/riscv64/mod.rs create mode 100644 cpu/src/riscv64/mod.rs create mode 100644 devices/src/interrupt_controller/riscv64/aia.rs create mode 100644 devices/src/interrupt_controller/riscv64/mod.rs create mode 100644 devices/src/usb/uas.rs create mode 100644 devices/src/usb/usbhost/ohusb.rs create mode 100644 hypervisor/src/kvm/riscv64/aia.rs create mode 100644 hypervisor/src/kvm/riscv64/config_regs.rs create mode 100644 hypervisor/src/kvm/riscv64/core_regs.rs create mode 100644 hypervisor/src/kvm/riscv64/cpu_caps.rs create mode 100644 hypervisor/src/kvm/riscv64/mod.rs create mode 100644 hypervisor/src/kvm/riscv64/timer_regs.rs rename machine_manager/src/config/ramfb.rs => hypervisor/src/test/aarch64/mod.rs (55%) create mode 100644 hypervisor/src/test/listener.rs create mode 100644 hypervisor/src/test/mod.rs create mode 100644 machine/src/riscv64/fdt.rs create mode 100644 machine/src/riscv64/micro.rs create mode 100644 machine/src/riscv64/mod.rs delete mode 100644 machine_manager/src/config/demo_dev.rs delete mode 100644 machine_manager/src/config/fs.rs delete mode 100644 machine_manager/src/config/gpu.rs delete mode 100644 machine_manager/src/config/pvpanic_pci.rs delete mode 100644 machine_manager/src/config/scsi.rs delete mode 100644 machine_manager/src/config/usb.rs delete mode 100644 machine_manager/src/config/vfio.rs create mode 100644 trace/trace_info/acpi.toml create mode 100644 trace/trace_info/memory.toml create mode 100644 util/src/ohos_binding/hwf_adapter/usb.rs create mode 100644 util/src/ohos_binding/usb.rs delete mode 100644 vendor/memoffset-0.7.1/.cargo-checksum.json delete mode 100644 vendor/memoffset-0.7.1/Cargo.toml delete mode 100644 vendor/memoffset-0.7.1/LICENSE delete mode 100644 vendor/memoffset-0.7.1/README.md delete mode 100644 vendor/memoffset-0.7.1/build.rs delete mode 100644 vendor/memoffset-0.7.1/src/lib.rs delete mode 100644 vendor/memoffset-0.7.1/src/offset_of.rs delete mode 100644 vendor/memoffset-0.7.1/src/raw_field.rs delete mode 100644 vendor/memoffset-0.7.1/src/span_of.rs diff --git a/Cargo.lock b/Cargo.lock index 99a1e19..3dc781f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,7 @@ dependencies = [ "nix 0.26.2", "once_cell", "thiserror", + "trace", "util", "vmm-sys-util", ] @@ -370,11 +371,11 @@ dependencies = [ "anyhow", "block_backend", "byteorder", - "cairo-rs", "chardev_backend", "clap", "cpu", "drm-fourcc", + "kvm-bindings", "libc", "libpulse-binding", "libpulse-simple-binding", @@ -388,6 +389,8 @@ dependencies = [ "rusb", "serde", "serde_json", + "strum", + "strum_macros", "thiserror", "trace", "ui", @@ -426,7 +429,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" dependencies = [ - "memoffset 0.8.0", + "memoffset", "rustc_version", ] @@ -1003,15 +1006,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.8.0" @@ -1054,26 +1048,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "mod_test" -version = "2.4.0" -dependencies = [ - "acpi", - "anyhow", - "byteorder", - "devices", - "hex", - "libc", - "machine", - "machine_manager", - "rand", - "serde", - "serde_json", - "util", - "virtio", - "vmm-sys-util", -] - [[package]] name = "nix" version = "0.24.3" @@ -1094,8 +1068,6 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.7.1", - "pin-utils", "static_assertions", ] @@ -1180,17 +1152,6 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" -[[package]] -name = "ozone" -version = "2.4.0" -dependencies = [ - "anyhow", - "libc", - "nix 0.26.2", - "thiserror", - "util", -] - [[package]] name = "pango" version = "0.17.4" @@ -1590,18 +1551,6 @@ dependencies = [ "util", ] -[[package]] -name = "stratovirt-img" -version = "2.4.0" -dependencies = [ - "anyhow", - "block_backend", - "libc", - "log", - "machine_manager", - "util", -] - [[package]] name = "strsim" version = "0.10.0" @@ -1861,6 +1810,7 @@ dependencies = [ "address_space", "anyhow", "byteorder", + "clap", "devices", "hypervisor", "kvm-bindings", diff --git a/Cargo.toml b/Cargo.toml index a82f9b7..6a74e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,31 +17,10 @@ trace = { path = "trace" } [workspace] members = [ - "ozone", - "image", - "tests/mod_test", ] [features] default = [] -boot_time = ["machine/boot_time"] -scream_alsa = ["machine/scream_alsa"] -scream_pulseaudio = ["machine/scream_pulseaudio"] -scream_ohaudio = ["machine/scream_ohaudio"] -pvpanic = ["machine/pvpanic"] -demo_device = ["machine/demo_device"] -usb_host = ["machine/usb_host"] -usb_camera_v4l2 = ["machine/usb_camera_v4l2"] -usb_camera_oh = ["machine/usb_camera_oh"] -gtk = ["machine/gtk"] -vnc = ["machine/vnc"] -vnc_auth = ["machine/vnc_auth"] -ohui_srv = ["machine/ohui_srv"] -ramfb = ["machine/ramfb"] -virtio_gpu = ["machine/virtio_gpu"] -trace_to_logger = ["trace/trace_to_logger"] -trace_to_ftrace = ["trace/trace_to_ftrace"] -trace_to_hitrace = ["trace/trace_to_hitrace"] [package.metadata.rpm.cargo] buildflags = ["--release"] @@ -55,3 +34,4 @@ panic = "abort" [profile.release] panic = "abort" lto = true +debug = false diff --git a/acpi/src/acpi_table.rs b/acpi/src/acpi_table.rs index 1577fd5..72c205c 100644 --- a/acpi/src/acpi_table.rs +++ b/acpi/src/acpi_table.rs @@ -646,3 +646,9 @@ pub mod madt_subtable { } } } + +/// This module describes ACPI MADT's sub-tables on riscv64 platform. +#[cfg(target_arch = "riscv64")] +pub mod madt_subtable { + // TODO +} diff --git a/address_space/Cargo.toml b/address_space/Cargo.toml index b9e7b1d..c77820d 100644 --- a/address_space/Cargo.toml +++ b/address_space/Cargo.toml @@ -19,3 +19,4 @@ machine_manager = { path = "../machine_manager" } migration = { path = "../migration" } migration_derive = { path = "../migration/migration_derive" } util = { path = "../util" } +trace = { path = "../trace" } diff --git a/address_space/src/address_space.rs b/address_space/src/address_space.rs index ae18dd8..fd419d7 100644 --- a/address_space/src/address_space.rs +++ b/address_space/src/address_space.rs @@ -503,6 +503,7 @@ impl AddressSpace { /// * `count` - Memory needed length pub fn get_address_map( &self, + cache: &Option, addr: GuestAddress, count: u64, res: &mut Vec, @@ -512,7 +513,7 @@ impl AddressSpace { loop { let io_vec = self - .addr_cache_init(start) + .get_host_address_from_cache(start, cache) .map(|(hva, fr_len)| Iovec { iov_base: hva, iov_len: std::cmp::min(len, fr_len), @@ -610,6 +611,7 @@ impl AddressSpace { /// /// Return Error if the `addr` is not mapped. pub fn read(&self, dst: &mut dyn std::io::Write, addr: GuestAddress, count: u64) -> Result<()> { + trace::trace_scope_start!(address_space_read, args = (&addr, count)); let view = self.flat_view.load(); view.read(dst, addr, count)?; @@ -628,6 +630,7 @@ impl AddressSpace { /// /// Return Error if the `addr` is not mapped. pub fn write(&self, src: &mut dyn std::io::Read, addr: GuestAddress, count: u64) -> Result<()> { + trace::trace_scope_start!(address_space_write, args = (&addr, count)); let view = self.flat_view.load(); if !*self.hyp_ioevtfd_enabled.get_or_init(|| false) { @@ -649,6 +652,7 @@ impl AddressSpace { src.read_to_end(&mut buf).unwrap(); if buf.len() <= 8 { + buf.resize(8, 0); let data = u64::from_bytes(buf.as_slice()).unwrap(); if *data == evtfd.data { if let Err(e) = evtfd.fd.write(1) { @@ -691,6 +695,10 @@ impl AddressSpace { /// # Note /// To use this method, it is necessary to implement `ByteCode` trait for your object. pub fn write_object_direct(&self, data: &T, host_addr: u64) -> Result<()> { + trace::trace_scope_start!( + address_space_write_direct, + args = (host_addr, std::mem::size_of::()) + ); // Mark vmm dirty page manually if live migration is active. MigrationManager::mark_dirty_log(host_addr, data.as_bytes().len() as u64); @@ -730,6 +738,10 @@ impl AddressSpace { /// # Note /// To use this method, it is necessary to implement `ByteCode` trait for your object. pub fn read_object_direct(&self, host_addr: u64) -> Result { + trace::trace_scope_start!( + address_space_read_direct, + args = (host_addr, std::mem::size_of::()) + ); let mut obj = T::default(); let mut dst = obj.as_mut_bytes(); // SAFETY: host_addr is managed by address_space, it has been verified for legality. @@ -744,6 +756,7 @@ impl AddressSpace { /// Update the topology of memory. pub fn update_topology(&self) -> Result<()> { + trace::trace_scope_start!(address_update_topology); let old_fv = self.flat_view.load(); let addr_range = AddressRange::new(GuestAddress(0), self.root.size()); diff --git a/address_space/src/host_mmap.rs b/address_space/src/host_mmap.rs index ed2ce2e..ca30977 100644 --- a/address_space/src/host_mmap.rs +++ b/address_space/src/host_mmap.rs @@ -205,6 +205,7 @@ fn touch_pages(start: u64, page_size: u64, nr_pages: u64) { /// * `size` - Size of memory. /// * `nr_vcpus` - Number of vcpus. fn mem_prealloc(host_addr: u64, size: u64, nr_vcpus: u8) { + trace::trace_scope_start!(pre_alloc, args = (size)); let page_size = host_page_size(); let threads = max_nr_threads(nr_vcpus); let nr_pages = (size + page_size - 1) / page_size; @@ -294,7 +295,7 @@ pub fn create_default_mem(mem_config: &MachineMemConfig, thread_num: u8) -> Resu pub fn create_backend_mem(mem_config: &MemZoneConfig, thread_num: u8) -> Result { let mut f_back: Option = None; - if mem_config.memfd { + if mem_config.memfd() { let anon_fd = memfd_create( &CString::new("stratovirt_anon_mem")?, MemFdCreateFlag::empty(), diff --git a/block_backend/src/file.rs b/block_backend/src/file.rs index 56fac1e..45ea154 100644 --- a/block_backend/src/file.rs +++ b/block_backend/src/file.rs @@ -14,7 +14,10 @@ use std::{ cell::RefCell, fs::File, io::{Seek, SeekFrom}, - os::unix::prelude::{AsRawFd, RawFd}, + os::{ + linux::fs::MetadataExt, + unix::prelude::{AsRawFd, RawFd}, + }, rc::Rc, sync::{ atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering}, @@ -26,7 +29,7 @@ use anyhow::{Context, Result}; use log::error; use vmm_sys_util::epoll::EventSet; -use crate::{BlockIoErrorCallback, BlockProperty}; +use crate::{qcow2::DEFAULT_SECTOR_SIZE, BlockIoErrorCallback, BlockProperty}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use util::{ aio::{Aio, AioCb, AioEngine, Iovec, OpCode}, @@ -102,7 +105,7 @@ impl FileDriver { completecb: T, ) -> Result<()> { if req_list.is_empty() { - return self.complete_request(opcode, &Vec::new(), 0, 0, completecb); + return self.complete_request(opcode, 0, completecb); } let single_req = req_list.len() == 1; let cnt = Arc::new(AtomicU32::new(req_list.len() as u32)); @@ -127,16 +130,10 @@ impl FileDriver { self.process_request(OpCode::Preadv, req_list, completecb) } - fn complete_request( - &mut self, - opcode: OpCode, - iovec: &[Iovec], - offset: usize, - nbytes: u64, - completecb: T, - ) -> Result<()> { - let aiocb = self.package_aiocb(opcode, iovec.to_vec(), offset, nbytes, completecb); - (self.aio.borrow_mut().complete_func)(&aiocb, nbytes as i64) + pub fn complete_request(&mut self, opcode: OpCode, res: i64, completecb: T) -> Result<()> { + let iovec: Vec = Vec::new(); + let aiocb = self.package_aiocb(opcode, iovec.to_vec(), 0, 0, completecb); + (self.aio.borrow_mut().complete_func)(&aiocb, res) } pub fn write_vectored(&mut self, req_list: Vec, completecb: T) -> Result<()> { @@ -194,6 +191,11 @@ impl FileDriver { unregister_event_helper(self.block_prop.iothread.as_ref(), &mut self.delete_evts) } + pub fn actual_size(&mut self) -> Result { + let meta_data = self.file.metadata()?; + Ok(meta_data.st_blocks() * DEFAULT_SECTOR_SIZE) + } + pub fn disk_size(&mut self) -> Result { let disk_size = self .file diff --git a/block_backend/src/lib.rs b/block_backend/src/lib.rs index cbe37df..b6f4251 100644 --- a/block_backend/src/lib.rs +++ b/block_backend/src/lib.rs @@ -15,6 +15,7 @@ pub mod qcow2; pub mod raw; use std::{ + fmt, fs::File, sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, @@ -151,6 +152,66 @@ impl CreateOptions { } } +// Transform size into string with storage units. +fn size_to_string(size: f64) -> Result { + let units = vec!["", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; + + // Switch to higher power if the integer part is >= 1000, + // For example: 1000 * 2^30 bytes + // It's better to output 0.978 TiB, rather than 1000 GiB. + let n = (size / 1000.0 * 1024.0).log2() as u64; + let idx = n / 10; + if idx >= units.len() as u64 { + bail!("Input value {} is too large", size); + } + let div = 1_u64 << (idx * 10); + + // Keep three significant digits and do not output any extra zeros, + // For example: 512 * 2^20 bytes + // It's better to output 512 MiB, rather than 512.000 MiB. + let num_str = format!("{:.3}", size / div as f64); + let num_str = num_str.trim_end_matches('0').trim_end_matches('.'); + + let res = format!("{} {}", num_str, units[idx as usize]); + Ok(res) +} + +#[derive(Default)] +pub struct ImageInfo { + pub path: String, + pub format: String, + pub actual_size: u64, + pub virtual_size: u64, + pub cluster_size: Option, + pub snap_lists: Option, +} + +impl fmt::Display for ImageInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!( + f, + "image: {}\n\ + file format: {}\n\ + virtual size: {} ({} bytes)\n\ + disk size: {}", + self.path, + self.format, + size_to_string(self.virtual_size as f64).unwrap_or_else(|e| format!("{:?}", e)), + self.virtual_size, + size_to_string(self.actual_size as f64).unwrap_or_else(|e| format!("{:?}", e)) + )?; + + if let Some(cluster_size) = self.cluster_size { + writeln!(f, "cluster_size: {}", cluster_size)?; + } + + if let Some(snap_lists) = &self.snap_lists { + write!(f, "Snapshot list:\n{}", snap_lists)?; + } + Ok(()) + } +} + #[derive(Default, Clone, Copy)] pub struct DiskFragments { pub allocated_clusters: u64, @@ -291,6 +352,8 @@ impl Default for BlockProperty { pub trait BlockDriverOps: Send { fn create_image(&mut self, options: &CreateOptions) -> Result; + fn query_image(&mut self, image_info: &mut ImageInfo) -> Result<()>; + fn check_image(&mut self, res: &mut CheckResult, quite: bool, fix: u64) -> Result<()>; fn disk_size(&mut self) -> Result; diff --git a/block_backend/src/qcow2/mod.rs b/block_backend/src/qcow2/mod.rs index 2dd1e4a..1b5a3df 100644 --- a/block_backend/src/qcow2/mod.rs +++ b/block_backend/src/qcow2/mod.rs @@ -50,7 +50,7 @@ use crate::{ table::{Qcow2ClusterType, Qcow2Table}, }, BlockDriverOps, BlockIoErrorCallback, BlockProperty, BlockStatus, CheckResult, CreateOptions, - SECTOR_SIZE, + ImageInfo, SECTOR_SIZE, }; use machine_manager::event_loop::EventLoop; use machine_manager::qmp::qmp_schema::SnapshotInfo; @@ -77,7 +77,7 @@ pub const QCOW2_OFLAG_ZERO: u64 = 1 << 0; const QCOW2_OFFSET_COMPRESSED: u64 = 1 << 62; pub const QCOW2_OFFSET_COPIED: u64 = 1 << 63; const MAX_L1_SIZE: u64 = 32 * (1 << 20); -const DEFAULT_SECTOR_SIZE: u64 = 512; +pub(crate) const DEFAULT_SECTOR_SIZE: u64 = 512; pub(crate) const QCOW2_MAX_L1_SIZE: u64 = 1 << 25; // The default flush interval is 30s. @@ -341,6 +341,13 @@ impl Qcow2Driver { .sync_aio .borrow_mut() .read_ctrl_cluster(self.header.refcount_table_offset, sz)?; + for block_offset in &self.refcount.refcount_table { + if *block_offset == 0 { + continue; + } + let rfb_offset = block_offset & REFCOUNT_TABLE_OFFSET_MASK; + self.refcount.refcount_table_map.insert(rfb_offset, 1); + } Ok(()) } @@ -435,7 +442,7 @@ impl Qcow2Driver { l2_entry &= !QCOW2_OFLAG_ZERO; let mut cluster_addr = l2_entry & L2_TABLE_OFFSET_MASK; if cluster_addr == 0 { - let new_addr = self.alloc_cluster(1, true)?; + let new_addr = self.alloc_cluster(1, false)?; l2_entry = new_addr | QCOW2_OFFSET_COPIED; cluster_addr = new_addr & L2_TABLE_OFFSET_MASK; } else if l2_entry & QCOW2_OFFSET_COPIED == 0 { @@ -865,6 +872,11 @@ impl Qcow2Driver { self.table.l1_table_offset = new_l1_table_offset; self.table.l1_size = snap.l1_size; self.table.l1_table = snap_l1_table; + self.table.l1_table_map.clear(); + for l1_entry in self.table.l1_table.iter() { + let addr = l1_entry & L1_TABLE_OFFSET_MASK; + self.table.l1_table_map.insert(addr, 1); + } self.qcow2_update_snapshot_refcount(old_l1_table_offset, old_l1_size as usize, -1)?; @@ -1291,13 +1303,14 @@ impl Qcow2Driver { return 0; } - if check & METADATA_OVERLAP_CHECK_MAINHEADER != 0 && offset < self.header.cluster_size() { + let cluster_size = self.header.cluster_size(); + if check & METADATA_OVERLAP_CHECK_MAINHEADER != 0 && offset < cluster_size { return METADATA_OVERLAP_CHECK_MAINHEADER as i64; } let size = round_up( self.refcount.offset_into_cluster(offset) + size, - self.header.cluster_size(), + cluster_size, ) .unwrap() as usize; let offset = self.refcount.start_of_cluster(offset) as usize; @@ -1321,15 +1334,10 @@ impl Qcow2Driver { } if check & METADATA_OVERLAP_CHECK_ACTIVEL2 != 0 { - for l1_entry in &self.table.l1_table { - if ranges_overlap( - offset, - size, - (l1_entry & L1_TABLE_OFFSET_MASK) as usize, - self.header.cluster_size() as usize, - ) - .unwrap() - { + let num = size as u64 / cluster_size; + for i in 0..num { + let addr = offset as u64 + i * cluster_size; + if self.table.l1_table_map.contains_key(&addr) { return METADATA_OVERLAP_CHECK_ACTIVEL2 as i64; } } @@ -1340,7 +1348,7 @@ impl Qcow2Driver { offset, size, self.header.refcount_table_offset as usize, - self.header.refcount_table_clusters as usize * self.header.cluster_size() as usize, + self.header.refcount_table_clusters as usize * cluster_size as usize, ) .unwrap() { @@ -1348,15 +1356,10 @@ impl Qcow2Driver { } if check & METADATA_OVERLAP_CHECK_REFCOUNTBLOCK != 0 { - for block_offset in &self.refcount.refcount_table { - if ranges_overlap( - offset, - size, - (block_offset & REFCOUNT_TABLE_OFFSET_MASK) as usize, - self.header.cluster_size() as usize, - ) - .unwrap() - { + let num = size as u64 / cluster_size; + for i in 0..num { + let addr = offset as u64 + i * cluster_size; + if self.refcount.refcount_table_map.contains_key(&addr) { return METADATA_OVERLAP_CHECK_REFCOUNTBLOCK as i64; } } @@ -1640,6 +1643,18 @@ impl BlockDriverOps for Qcow2Driver { Ok(image_info) } + fn query_image(&mut self, info: &mut ImageInfo) -> Result<()> { + info.format = "qcow2".to_string(); + info.virtual_size = self.disk_size()?; + info.actual_size = self.driver.actual_size()?; + info.cluster_size = Some(self.header.cluster_size()); + + if !self.snapshot.snapshots.is_empty() { + info.snap_lists = Some(self.qcow2_list_snapshots()); + } + Ok(()) + } + fn check_image(&mut self, res: &mut CheckResult, quite: bool, fix: u64) -> Result<()> { let cluster_size = self.header.cluster_size(); let refcount_order = self.header.refcount_order; @@ -1672,8 +1687,8 @@ impl BlockDriverOps for Qcow2Driver { let mut copied = 0; while copied < nbytes { let pos = offset as u64 + copied; - match self.host_offset_for_read(pos, nbytes - copied)? { - HostRange::DataAddress(host_offset, cnt) => { + match self.host_offset_for_read(pos, nbytes - copied) { + Ok(HostRange::DataAddress(host_offset, cnt)) => { let (begin, end) = iovecs_split(left, cnt); left = end; req_list.push(CombineRequest { @@ -1683,12 +1698,16 @@ impl BlockDriverOps for Qcow2Driver { }); copied += cnt; } - HostRange::DataNotInit(cnt) => { + Ok(HostRange::DataNotInit(cnt)) => { let (begin, end) = iovecs_split(left, cnt); left = end; iovec_write_zero(&begin); copied += cnt; } + Err(e) => { + error!("Failed to read vectored: {:?}", e); + return self.driver.complete_request(OpCode::Preadv, -1, completecb); + } } } @@ -1706,7 +1725,15 @@ impl BlockDriverOps for Qcow2Driver { while copied < nbytes { let pos = offset as u64 + copied; let count = self.cluster_aligned_bytes(pos, nbytes - copied); - let host_offset = self.host_offset_for_write(pos, count)?; + let host_offset = match self.host_offset_for_write(pos, count) { + Ok(host_offset) => host_offset, + Err(e) => { + error!("Failed to write vectored: {:?}", e); + return self + .driver + .complete_request(OpCode::Pwritev, -1, completecb); + } + }; if let Some(end) = req_list.last_mut() { if end.offset + end.nbytes == host_offset { end.nbytes += count; diff --git a/block_backend/src/qcow2/refcount.rs b/block_backend/src/qcow2/refcount.rs index 1a3dfdd..b58e371 100644 --- a/block_backend/src/qcow2/refcount.rs +++ b/block_backend/src/qcow2/refcount.rs @@ -10,7 +10,7 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use anyhow::{bail, Context, Result}; use log::{error, info}; @@ -32,6 +32,9 @@ use util::{ // The max refcount table size default is 4 clusters; const MAX_REFTABLE_NUM: u64 = 4; +// Default refcount table map length, which can describe 512GiB data for 64Kib cluster. +const REFCOUNT_TABLE_MAP_LEN: usize = 256; + #[derive(Eq, PartialEq, Clone)] pub enum Qcow2DiscardType { Never, @@ -64,6 +67,7 @@ impl DiscardTask { #[derive(Clone)] pub struct RefCount { pub refcount_table: Vec, + pub refcount_table_map: HashMap, sync_aio: Rc>, pub(crate) refcount_blk_cache: Qcow2Cache, pub discard_list: Vec, @@ -87,6 +91,7 @@ impl RefCount { pub fn new(sync_aio: Rc>) -> Self { RefCount { refcount_table: Vec::new(), + refcount_table_map: HashMap::with_capacity(REFCOUNT_TABLE_MAP_LEN), sync_aio, refcount_blk_cache: Qcow2Cache::default(), discard_list: Vec::new(), @@ -217,9 +222,11 @@ impl RefCount { new_table.resize(new_table_size as usize, 0); let start_offset = start_idx * self.cluster_size; let mut table_offset = start_offset; + let mut added_rb = Vec::new(); for i in 0..new_block_clusters { if new_table[i as usize] == 0 { new_table[i as usize] = table_offset; + added_rb.push(table_offset & REFCOUNT_TABLE_OFFSET_MASK); table_offset += self.cluster_size; } } @@ -247,6 +254,9 @@ impl RefCount { let old_table_offset = self.refcount_table_offset; let old_table_clusters = self.refcount_table_clusters; self.refcount_table = new_table; + for rb_offset in added_rb.iter() { + self.refcount_table_map.insert(*rb_offset, 1); + } self.refcount_table_offset = header.refcount_table_offset; self.refcount_table_clusters = header.refcount_table_clusters; self.refcount_table_size = new_table_size; @@ -316,7 +326,7 @@ impl RefCount { bail!("Failed to update refcount, offset is not aligned to cluster"); } let first_cluster = bytes_to_clusters(offset, self.cluster_size).unwrap(); - let mut rc_vec = Vec::new(); + let mut rc_vec: Vec<(u64, u64, usize)> = Vec::with_capacity(clusters as usize); let mut i = 0; while i < clusters { let rt_idx = (first_cluster + i) >> self.refcount_blk_bits; @@ -381,6 +391,22 @@ impl RefCount { self.refcount_blk_cache.flush(self.sync_aio.clone()) } + fn get_refcount_block_cache(&mut self, rt_idx: u64) -> Result>> { + let entry = self.refcount_blk_cache.get(rt_idx); + let cache_entry = if let Some(entry) = entry { + entry.clone() + } else { + self.load_refcount_block(rt_idx).with_context(|| { + format!("Failed to get refcount block cache, index is {}", rt_idx) + })?; + self.refcount_blk_cache + .get(rt_idx) + .with_context(|| format!("Not found refcount block cache, index is {}", rt_idx))? + .clone() + }; + Ok(cache_entry) + } + fn set_refcount( &mut self, rt_idx: u64, @@ -391,18 +417,10 @@ impl RefCount { ) -> Result<()> { let is_add = added > 0; let added_value = added.unsigned_abs() as u16; - if !self.refcount_blk_cache.contains_keys(rt_idx) { - self.load_refcount_block(rt_idx).with_context(|| { - format!("Failed to get refcount block cache, index is {}", rt_idx) - })?; - } let cache_entry = self - .refcount_blk_cache - .get(rt_idx) - .with_context(|| format!("Not found refcount block cache, index is {}", rt_idx))? - .clone(); - - let mut rb_vec = Vec::new(); + .get_refcount_block_cache(rt_idx) + .with_context(|| "Get refcount block cache failed")?; + let mut rb_vec: Vec = Vec::with_capacity(clusters); let mut borrowed_entry = cache_entry.borrow_mut(); let is_dirty = borrowed_entry.dirty_info.is_dirty; for i in 0..clusters { @@ -471,17 +489,9 @@ impl RefCount { ); } - if !self.refcount_blk_cache.contains_keys(rt_idx) { - self.load_refcount_block(rt_idx).with_context(|| { - format!("Failed to get refcount block cache, index is {}", rt_idx) - })?; - } let cache_entry = self - .refcount_blk_cache - .get(rt_idx) - .with_context(|| format!("Not found refcount block cache, index is {}", rt_idx))? - .clone(); - + .get_refcount_block_cache(rt_idx) + .with_context(|| "Get refcount block cache failed")?; let rb_idx = self.cluster_in_rc_block(cluster) as usize; let rc_value = cache_entry.borrow_mut().get_entry_map(rb_idx).unwrap(); @@ -542,6 +552,8 @@ impl RefCount { // Update refcount table. self.refcount_table[rt_idx as usize] = alloc_offset; + let rb_offset = alloc_offset & REFCOUNT_TABLE_OFFSET_MASK; + self.refcount_table_map.insert(rb_offset, 1); let rc_block = vec![0_u8; self.cluster_size as usize]; let cache_entry = Rc::new(RefCell::new(CacheTable::new( alloc_offset, diff --git a/block_backend/src/qcow2/table.rs b/block_backend/src/qcow2/table.rs index 5886bec..6554f3f 100644 --- a/block_backend/src/qcow2/table.rs +++ b/block_backend/src/qcow2/table.rs @@ -10,7 +10,7 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use anyhow::{Context, Result}; use log::info; @@ -28,6 +28,9 @@ use crate::{ use machine_manager::config::MAX_L2_CACHE_SIZE; use util::num_ops::div_round_up; +// Default l1 table map length, which can describe 512GiB data for 64KiB cluster. +const L1_TABLE_MAP_LEN: usize = 1024; + #[derive(PartialEq, Eq, Debug)] pub enum Qcow2ClusterType { /// Cluster is unallocated. @@ -81,6 +84,7 @@ pub struct Qcow2Table { cluster_bits: u64, cluster_size: u64, pub l1_table: Vec, + pub l1_table_map: HashMap, pub l1_table_offset: u64, pub l1_size: u32, pub l2_table_cache: Qcow2Cache, @@ -96,6 +100,7 @@ impl Qcow2Table { cluster_bits: 0, cluster_size: 0, l1_table: Vec::new(), + l1_table_map: HashMap::with_capacity(L1_TABLE_MAP_LEN), l1_table_offset: 0, l1_size: 0, l2_table_cache: Qcow2Cache::default(), @@ -143,6 +148,10 @@ impl Qcow2Table { .sync_aio .borrow_mut() .read_ctrl_cluster(self.l1_table_offset, self.l1_size as u64)?; + for l1_entry in &self.l1_table { + let l1_entry_addr = l1_entry & L1_TABLE_OFFSET_MASK; + self.l1_table_map.insert(l1_entry_addr, 1); + } Ok(()) } @@ -185,7 +194,11 @@ impl Qcow2Table { } pub fn update_l1_table(&mut self, l1_index: usize, l2_address: u64) { + let old_addr = self.l1_table[l1_index] & L1_TABLE_OFFSET_MASK; + let new_addr = l2_address & L1_TABLE_OFFSET_MASK; self.l1_table[l1_index] = l2_address; + self.l1_table_map.remove(&old_addr); + self.l1_table_map.insert(new_addr, 1); } pub fn update_l2_table( diff --git a/block_backend/src/raw.rs b/block_backend/src/raw.rs index c051b3f..d8622d5 100644 --- a/block_backend/src/raw.rs +++ b/block_backend/src/raw.rs @@ -25,7 +25,7 @@ use crate::{ file::{CombineRequest, FileDriver}, qcow2::is_aligned, BlockDriverOps, BlockIoErrorCallback, BlockProperty, BlockStatus, CheckResult, CreateOptions, - SECTOR_SIZE, + ImageInfo, SECTOR_SIZE, }; use util::{ aio::{get_iov_size, raw_write, Aio, Iovec}, @@ -92,6 +92,13 @@ impl BlockDriverOps for RawDriver { Ok(image_info) } + fn query_image(&mut self, info: &mut ImageInfo) -> Result<()> { + info.format = "raw".to_string(); + info.virtual_size = self.disk_size()?; + info.actual_size = self.driver.actual_size()?; + Ok(()) + } + fn check_image(&mut self, _res: &mut CheckResult, _quite: bool, _fix: u64) -> Result<()> { bail!("This image format does not support checks"); } diff --git a/boot_loader/Cargo.toml b/boot_loader/Cargo.toml index cbd2287..8570b82 100644 --- a/boot_loader/Cargo.toml +++ b/boot_loader/Cargo.toml @@ -8,7 +8,7 @@ license = "Mulan PSL v2" [dependencies] thiserror = "1.0" anyhow = "1.0" -kvm-bindings = { version = "0.10.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } log = "0.4" address_space = { path = "../address_space" } devices = { path = "../devices" } diff --git a/boot_loader/src/lib.rs b/boot_loader/src/lib.rs index 46955e8..9aa2888 100644 --- a/boot_loader/src/lib.rs +++ b/boot_loader/src/lib.rs @@ -25,6 +25,7 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` //! //! ## Examples //! @@ -87,6 +88,8 @@ #[cfg(target_arch = "aarch64")] mod aarch64; pub mod error; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; #[cfg(target_arch = "x86_64")] mod x86_64; @@ -98,6 +101,12 @@ pub use aarch64::AArch64BootLoader as BootLoader; pub use aarch64::AArch64BootLoaderConfig as BootLoaderConfig; pub use error::BootLoaderError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::load_linux; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVBootLoader as BootLoader; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVBootLoaderConfig as BootLoaderConfig; #[cfg(target_arch = "x86_64")] pub use x86_64::load_linux; #[cfg(target_arch = "x86_64")] diff --git a/boot_loader/src/riscv64/mod.rs b/boot_loader/src/riscv64/mod.rs new file mode 100644 index 0000000..773ff26 --- /dev/null +++ b/boot_loader/src/riscv64/mod.rs @@ -0,0 +1,202 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use crate::error::BootLoaderError; +use address_space::{AddressSpace, GuestAddress}; +use anyhow::{anyhow, Context, Result}; +use devices::legacy::{error::LegacyError as FwcfgErrorKind, FwCfgEntryType, FwCfgOps}; +use log::info; +use util::byte_code::ByteCode; + +const RISCV64_KERNEL_OFFSET: u64 = 0x20_0000; +const SZ_4M: u64 = 0x00400000; + +/// Boot loader config used for riscv. +#[derive(Default, Debug)] +pub struct RISCVBootLoaderConfig { + /// Path of kernel image. + pub kernel: Option, + /// Path of initrd image. + pub initrd: Option, + /// Start address of guest memory. + pub mem_start: u64, +} + +/// The start address for `kernel image`, `initrd image` and `dtb` in guest memory. +pub struct RISCVBootLoader { + /// PC register on riscv platform. + pub boot_pc: u64, + /// Start address for `initrd image` in guest memory. + pub initrd_start: u64, + /// Initrd file size, 0 means no initrd file. + pub initrd_size: u64, + /// Start address for `dtb` in guest memory. + pub dtb_start: u64, +} + +fn load_kernel( + fwcfg: Option<&Arc>>, + kernel_start: u64, + kernel_path: &Path, + sys_mem: &Arc, +) -> Result { + let mut kernel_image = + File::open(kernel_path).with_context(|| anyhow!(BootLoaderError::BootLoaderOpenKernel))?; + let kernel_size = kernel_image.metadata().unwrap().len(); + let kernel_end = kernel_start + kernel_size; + + if let Some(fw_cfg) = fwcfg { + let mut kernel_data = Vec::new(); + kernel_image.read_to_end(&mut kernel_data)?; + let mut lock_dev = fw_cfg.lock().unwrap(); + lock_dev + .add_data_entry( + FwCfgEntryType::KernelSize, + (kernel_size as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("KernelSize".to_string())))?; + lock_dev + .add_data_entry(FwCfgEntryType::KernelData, kernel_data) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("KernelData".to_string())))?; + } else { + if sys_mem + .memory_end_address() + .raw_value() + .checked_sub(kernel_end) + .is_none() + { + return Err(anyhow!(BootLoaderError::KernelOverflow( + kernel_start, + kernel_size + ))); + } + sys_mem + .write(&mut kernel_image, GuestAddress(kernel_start), kernel_size) + .with_context(|| "Fail to write kernel to guest memory")?; + } + Ok(kernel_end) +} + +fn load_initrd( + fwcfg: Option<&Arc>>, + initrd_path: &Path, + sys_mem: &Arc, + kernel_end: u64, +) -> Result<(u64, u64)> { + let mut initrd_image = + File::open(initrd_path).with_context(|| anyhow!(BootLoaderError::BootLoaderOpenInitrd))?; + let initrd_size = initrd_image.metadata().unwrap().len(); + + let initrd_start = if let Some(addr) = sys_mem + .memory_end_address() + .raw_value() + .checked_sub(initrd_size) + .filter(|addr| addr >= &kernel_end) + { + addr + } else { + return Err(anyhow!(BootLoaderError::InitrdOverflow( + kernel_end, + initrd_size + ))); + }; + + if let Some(fw_cfg) = fwcfg { + let mut initrd_data = Vec::new(); + initrd_image.read_to_end(&mut initrd_data)?; + let mut lock_dev = fw_cfg.lock().unwrap(); + lock_dev + .add_data_entry( + FwCfgEntryType::InitrdAddr, + (initrd_start as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdAddr".to_string())))?; + lock_dev + .add_data_entry( + FwCfgEntryType::InitrdSize, + (initrd_size as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdSize".to_string())))?; + lock_dev + .add_data_entry(FwCfgEntryType::InitrdData, initrd_data) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdData".to_string())))?; + } else { + sys_mem + .write(&mut initrd_image, GuestAddress(initrd_start), initrd_size) + .with_context(|| "Fail to write initrd to guest memory")?; + } + + Ok((initrd_start, initrd_size)) +} + +/// Load linux kernel and other boot source to Guest Memory. +/// +/// # Steps +/// +/// 1. Prepare for linux kernel boot env, return guest memory layout. +/// 2. According guest memory layout, load linux kernel to guest memory. +/// 3. According guest memory layout, load initrd image to guest memory. +/// +/// # Arguments +/// +/// * `config` - boot source config, contains kernel, initrd. +/// * `sys_mem` - guest memory. +/// +/// # Errors +/// +/// Load kernel, initrd to guest memory failed. Boot source is broken or +/// guest memory is abnormal. +pub fn load_linux( + config: &RISCVBootLoaderConfig, + sys_mem: &Arc, + fwcfg: Option<&Arc>>, +) -> Result { + // The memory layout is as follow: + // 1. kernel address: memory start + RISCV64_KERNEL_OFFSET + // 2. dtb address: kernel end + SZ_4M + // 3. initrd address: memory end - inird_size + let kernel_start = config.mem_start + RISCV64_KERNEL_OFFSET; + let boot_pc = if fwcfg.is_some() { 0 } else { kernel_start }; + + let kernel_end = load_kernel( + fwcfg, + kernel_start, + config.kernel.as_ref().unwrap(), + sys_mem, + ) + .with_context(|| "Fail to load kernel")?; + + let dtb_addr = kernel_end + SZ_4M; + + let mut initrd_start = 0_u64; + let mut initrd_size = 0_u64; + if config.initrd.is_some() { + let initrd_tuple = load_initrd(fwcfg, config.initrd.as_ref().unwrap(), sys_mem, kernel_end) + .with_context(|| "Fail to load initrd")?; + initrd_start = initrd_tuple.0; + initrd_size = initrd_tuple.1; + } else { + info!("No initrd image file."); + } + + Ok(RISCVBootLoader { + boot_pc, + initrd_start, + initrd_size, + dtb_start: dtb_addr, + }) +} diff --git a/build.rs b/build.rs index 96f77ff..13b5a89 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,9 @@ fn ohos_env_configure() { println!("cargo:rustc-link-arg=--verbose"); println!("cargo:rustc-link-arg=--sysroot={}/sysroot", ohos_sdk_path); println!("cargo:rustc-link-arg=-lpixman_static"); + if cfg!(feature = "usb_host") { + println!("cargo:rustc-link-arg=-lusb-1.0"); + } println!( "cargo:rustc-link-search={}/sysroot/usr/lib/aarch64-linux-ohos", ohos_sdk_path diff --git a/chardev_backend/src/chardev.rs b/chardev_backend/src/chardev.rs index 7a07a78..bd2b37d 100644 --- a/chardev_backend/src/chardev.rs +++ b/chardev_backend/src/chardev.rs @@ -10,8 +10,9 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +use std::collections::VecDeque; use std::fs::{read_link, File, OpenOptions}; -use std::io::{Stdin, Stdout}; +use std::io::{ErrorKind, Stdin, Stdout}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::PathBuf; use std::rc::Rc; @@ -24,11 +25,12 @@ use nix::fcntl::{fcntl, FcntlArg, OFlag}; use nix::pty::openpty; use nix::sys::termios::{cfmakeraw, tcgetattr, tcsetattr, SetArg, Termios}; use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::EventFd; use machine_manager::event_loop::EventLoop; use machine_manager::machine::{PathInfo, PTY_PATH}; use machine_manager::{ - config::{ChardevConfig, ChardevType}, + config::{ChardevConfig, ChardevType, SocketType}, temp_cleaner::TempCleaner, }; use util::file::clear_file; @@ -39,6 +41,8 @@ use util::set_termi_raw_mode; use util::socket::{SocketListener, SocketStream}; use util::unix::limit_permission; +const BUF_QUEUE_SIZE: usize = 128; + /// Provide the trait that helps handle the input data. pub trait InputReceiver: Send { /// Handle the input data and trigger interrupt if necessary. @@ -85,13 +89,17 @@ pub struct Chardev { /// Scheduled DPC to unpause input stream. /// Unpause must be done inside event-loop unpause_timer: Option, + /// output listener to notify when output stream fd can be written + output_listener_fd: Option>, + /// output buffer queue + outbuf: VecDeque>, } impl Chardev { pub fn new(chardev_cfg: ChardevConfig) -> Self { Chardev { - id: chardev_cfg.id, - backend: chardev_cfg.backend, + id: chardev_cfg.id(), + backend: chardev_cfg.classtype, listener: None, input: None, output: None, @@ -100,17 +108,19 @@ impl Chardev { dev: None, wait_port: false, unpause_timer: None, + output_listener_fd: None, + outbuf: VecDeque::with_capacity(BUF_QUEUE_SIZE), } } pub fn realize(&mut self) -> Result<()> { match &self.backend { - ChardevType::Stdio => { + ChardevType::Stdio { .. } => { set_termi_raw_mode().with_context(|| "Failed to set terminal to raw mode")?; self.input = Some(Arc::new(Mutex::new(std::io::stdin()))); self.output = Some(Arc::new(Mutex::new(std::io::stdout()))); } - ChardevType::Pty => { + ChardevType::Pty { .. } => { let (master, path) = set_pty_raw_mode().with_context(|| "Failed to set pty to raw mode")?; info!("Pty path is: {:?}", path); @@ -125,58 +135,43 @@ impl Chardev { self.input = Some(master_arc.clone()); self.output = Some(master_arc); } - ChardevType::UnixSocket { - path, - server, - nowait, - } => { + ChardevType::Socket { server, nowait, .. } => { if !*server || !*nowait { bail!( "Argument \'server\' and \'nowait\' are both required for chardev \'{}\'", &self.id ); } - - clear_file(path.clone())?; - let listener = SocketListener::bind_by_uds(path).with_context(|| { - format!( - "Failed to bind socket for chardev \'{}\', path: {}", - &self.id, path - ) - })?; - self.listener = Some(listener); - - // add file to temporary pool, so it could be cleaned when vm exit. - TempCleaner::add_path(path.clone()); - limit_permission(path).with_context(|| { - format!( - "Failed to change file permission for chardev \'{}\', path: {}", - &self.id, path - ) - })?; - } - ChardevType::TcpSocket { - host, - port, - server, - nowait, - } => { - if !*server || !*nowait { - bail!( - "Argument \'server\' and \'nowait\' are both required for chardev \'{}\'", - &self.id - ); + let socket_type = self.backend.socket_type()?; + if let SocketType::Tcp { host, port } = socket_type { + let listener = SocketListener::bind_by_tcp(&host, port).with_context(|| { + format!( + "Failed to bind socket for chardev \'{}\', address: {}:{}", + &self.id, host, port + ) + })?; + self.listener = Some(listener); + } else if let SocketType::Unix { path } = socket_type { + clear_file(path.clone())?; + let listener = SocketListener::bind_by_uds(&path).with_context(|| { + format!( + "Failed to bind socket for chardev \'{}\', path: {}", + &self.id, path + ) + })?; + self.listener = Some(listener); + + // add file to temporary pool, so it could be cleaned when vm exit. + TempCleaner::add_path(path.clone()); + limit_permission(&path).with_context(|| { + format!( + "Failed to change file permission for chardev \'{}\', path: {}", + &self.id, path + ) + })?; } - - let listener = SocketListener::bind_by_tcp(host, *port).with_context(|| { - format!( - "Failed to bind socket for chardev \'{}\', address: {}:{}", - &self.id, host, port - ) - })?; - self.listener = Some(listener); } - ChardevType::File(path) => { + ChardevType::File { path, .. } => { let file = Arc::new(Mutex::new( OpenOptions::new() .read(true) @@ -237,7 +232,7 @@ impl Chardev { let unpause_fn = Box::new(move || { let res = EventLoop::update_event( vec![EventNotifier::new( - NotifierOperation::Modify, + NotifierOperation::AddEvents, input_fd, None, EventSet::IN | EventSet::HANG_UP, @@ -261,6 +256,107 @@ impl Chardev { self.unpause_timer = None; } } + + fn clear_outbuf(&mut self) { + self.outbuf.clear(); + } + + pub fn outbuf_is_full(&self) -> bool { + self.outbuf.len() == self.outbuf.capacity() + } + + pub fn fill_outbuf(&mut self, buf: Vec, listener_fd: Option>) -> Result<()> { + match self.backend { + ChardevType::File { .. } | ChardevType::Pty { .. } | ChardevType::Stdio { .. } => { + if self.output.is_none() { + bail!("chardev has no output"); + } + return write_buffer_sync(self.output.as_ref().unwrap().clone(), buf); + } + ChardevType::Socket { .. } => (), + } + + if self.output.is_none() { + return Ok(()); + } + + if self.outbuf_is_full() { + bail!("Failed to append buffer because output buffer queue is full"); + } + self.outbuf.push_back(buf); + self.output_listener_fd = listener_fd; + + let event_notifier = EventNotifier::new( + NotifierOperation::AddEvents, + self.stream_fd.unwrap(), + None, + EventSet::OUT, + Vec::new(), + ); + EventLoop::update_event(vec![event_notifier], None)?; + Ok(()) + } + + fn consume_outbuf(&mut self) -> Result<()> { + let output = self.output.as_ref().unwrap(); + while !self.outbuf.is_empty() { + if write_buffer_async(output.clone(), self.outbuf.front_mut().unwrap())? { + break; + } + self.outbuf.pop_front(); + } + Ok(()) + } +} + +fn write_buffer_sync(writer: Arc>, buf: Vec) -> Result<()> { + let len = buf.len(); + let mut written = 0; + let mut locked_writer = writer.lock().unwrap(); + + while written < len { + match locked_writer.write(&buf[written..len]) { + Ok(n) => written += n, + Err(e) => bail!("chardev failed to write file with error {:?}", e), + } + } + locked_writer + .flush() + .with_context(|| "chardev failed to flush")?; + Ok(()) +} + +// If write is blocked, return true. Otherwise return false. +fn write_buffer_async( + writer: Arc>, + buf: &mut Vec, +) -> Result { + let len = buf.len(); + let mut locked_writer = writer.lock().unwrap(); + let mut written = 0; + + while written < len { + match locked_writer.write(&buf[written..len]) { + Ok(0) => break, + Ok(n) => written += n, + Err(e) => { + let err_type = e.kind(); + if err_type != ErrorKind::WouldBlock && err_type != ErrorKind::Interrupted { + bail!("chardev failed to write data with error {:?}", e); + } + break; + } + } + } + locked_writer + .flush() + .with_context(|| "chardev failed to flush")?; + + if written == len { + return Ok(false); + } + buf.drain(0..written); + Ok(true) } fn set_pty_raw_mode() -> Result<(i32, PathBuf)> { @@ -453,10 +549,10 @@ fn get_socket_notifier(chardev: Arc>) -> Option { locked_receiver.set_paused(); return Some(vec![EventNotifier::new( - NotifierOperation::Modify, + NotifierOperation::DeleteEvents, stream_fd, None, - EventSet::HANG_UP, + EventSet::IN, vec![], )]); } @@ -486,12 +582,53 @@ fn get_socket_notifier(chardev: Arc>) -> Option { None }); + let handling_chardev = cloned_chardev.clone(); + let output_handler = Rc::new(move |event, fd| { + if event & EventSet::OUT != EventSet::OUT { + return None; + } + + let mut locked_cdev = handling_chardev.lock().unwrap(); + if let Err(e) = locked_cdev.consume_outbuf() { + error!("Failed to consume outbuf with error {:?}", e); + locked_cdev.clear_outbuf(); + return Some(vec![EventNotifier::new( + NotifierOperation::DeleteEvents, + fd, + None, + EventSet::OUT, + Vec::new(), + )]); + } + + if locked_cdev.output_listener_fd.is_some() { + let fd = locked_cdev.output_listener_fd.as_ref().unwrap(); + if let Err(e) = fd.write(1) { + error!("Failed to write eventfd with error {:?}", e); + return None; + } + locked_cdev.output_listener_fd = None; + } + + if locked_cdev.outbuf.is_empty() { + Some(vec![EventNotifier::new( + NotifierOperation::DeleteEvents, + fd, + None, + EventSet::OUT, + Vec::new(), + )]) + } else { + None + } + }); + Some(vec![EventNotifier::new( NotifierOperation::AddShared, stream_fd, Some(listener_fd), EventSet::IN | EventSet::HANG_UP, - vec![input_handler], + vec![input_handler, output_handler], )]) }); @@ -510,11 +647,10 @@ impl EventNotifierHelper for Chardev { let notifier = { let backend = chardev.lock().unwrap().backend.clone(); match backend { - ChardevType::Stdio => get_terminal_notifier(chardev), - ChardevType::Pty => get_terminal_notifier(chardev), - ChardevType::UnixSocket { .. } => get_socket_notifier(chardev), - ChardevType::TcpSocket { .. } => get_socket_notifier(chardev), - ChardevType::File(_) => None, + ChardevType::Stdio { .. } => get_terminal_notifier(chardev), + ChardevType::Pty { .. } => get_terminal_notifier(chardev), + ChardevType::Socket { .. } => get_socket_notifier(chardev), + ChardevType::File { .. } => None, } }; notifier.map_or(Vec::new(), |value| vec![value]) diff --git a/cpu/Cargo.toml b/cpu/Cargo.toml index 1be3d7b..44265b6 100644 --- a/cpu/Cargo.toml +++ b/cpu/Cargo.toml @@ -9,7 +9,7 @@ description = "CPU emulation" [dependencies] thiserror = "1.0" anyhow = "1.0" -kvm-bindings = { version = "0.10.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } nix = { version = "0.26.2", default-features = false, features = ["fs", "feature"] } log = "0.4" libc = "0.2" diff --git a/cpu/src/lib.rs b/cpu/src/lib.rs index 873cb49..3da91b1 100644 --- a/cpu/src/lib.rs +++ b/cpu/src/lib.rs @@ -26,12 +26,15 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` pub mod error; #[allow(clippy::upper_case_acronyms)] #[cfg(target_arch = "aarch64")] mod aarch64; +#[cfg(target_arch = "riscv64")] +mod riscv64; #[cfg(target_arch = "x86_64")] mod x86_64; @@ -52,6 +55,14 @@ pub use aarch64::PMU_INTR; #[cfg(target_arch = "aarch64")] pub use aarch64::PPI_BASE; pub use error::CpuError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUBootConfig as CPUBootConfig; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUState as ArchCPU; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUTopology as CPUTopology; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVRegsIndex as RegsIndex; #[cfg(target_arch = "x86_64")] pub use x86_64::X86CPUBootConfig as CPUBootConfig; #[cfg(target_arch = "x86_64")] @@ -118,7 +129,7 @@ pub trait CPUInterface { /// Realize `CPU` structure, set registers value for `CPU`. fn realize( &self, - boot: &Option, + boot: &CPUBootConfig, topology: &CPUTopology, #[cfg(target_arch = "aarch64")] features: &CPUFeatures, ) -> Result<()>; @@ -160,7 +171,7 @@ pub trait CPUHypervisorOps: Send + Sync { fn set_boot_config( &self, arch_cpu: Arc>, - boot_config: &Option, + boot_config: &CPUBootConfig, #[cfg(target_arch = "aarch64")] vcpu_config: &CPUFeatures, ) -> Result<()>; @@ -310,7 +321,7 @@ impl CPU { impl CPUInterface for CPU { fn realize( &self, - boot: &Option, + boot: &CPUBootConfig, topology: &CPUTopology, #[cfg(target_arch = "aarch64")] config: &CPUFeatures, ) -> Result<()> { diff --git a/cpu/src/riscv64/mod.rs b/cpu/src/riscv64/mod.rs new file mode 100644 index 0000000..5fd97a3 --- /dev/null +++ b/cpu/src/riscv64/mod.rs @@ -0,0 +1,205 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::sync::{Arc, Mutex}; + +use anyhow::{Context, Result}; +use kvm_bindings::{ + kvm_mp_state as MpState, + // AIA CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_aia_csr as AiaCsrs, + // CONFIG registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_config as ConfigRegs, + // CORE registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_core as CoreRegs, + // General CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_csr as Csrs, + // TIMER registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_timer as TimerRegs, + KVM_MP_STATE_RUNNABLE as MP_STATE_RUNNABLE, +}; + +use crate::CPU; +use migration::{ + DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, MigrationManager, StateTransfer, +}; +use migration_derive::{ByteCode, Desc}; +use util::byte_code::ByteCode; + +/// RISCV CPU booting configure information +#[derive(Default, Copy, Clone, Debug)] +pub struct RISCVCPUBootConfig { + pub fdt_addr: u64, + pub boot_pc: u64, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RISCVRegsIndex { + MpState, + ConfigRegs, + CoreRegs, + AiaCsrs, + Csrs, + TimerRegs, +} + +#[derive(Default, Copy, Clone, Debug)] +pub struct RISCVCPUTopology {} + +impl RISCVCPUTopology { + pub fn new() -> Self { + RISCVCPUTopology::default() + } + + pub fn set_topology(self, _topology: (u8, u8, u8)) -> Self { + self + } +} + +/// riscv64 CPU architect information +#[repr(C)] +#[derive(Copy, Clone, Desc, ByteCode)] +#[desc_version(compat_version = "0.1.0")] +pub struct RISCVCPUState { + /// The vcpu id, `0` means primary CPU. + pub apic_id: u32, + /// Vcpu mpstate register. + pub mp_state: MpState, + /// Vcpu core registers. + pub core_regs: CoreRegs, + /// AIA CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub aia_csrs: AiaCsrs, + /// General CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub csrs: Csrs, + /// CONFIG registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub config_regs: ConfigRegs, + /// TIMER registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub timer_regs: TimerRegs, + /// XLEN to determine RV64 or RV32 + pub xlen: u64, +} + +impl RISCVCPUState { + /// Allocates a new `RISCVCPUState`. + /// + /// # Arguments + /// + /// * `vcpu_id` - ID of this `CPU`. + pub fn new(vcpu_id: u32) -> Self { + let mp_state = MpState { + mp_state: MP_STATE_RUNNABLE, + }; + + RISCVCPUState { + apic_id: vcpu_id, + mp_state, + xlen: 64, + ..Default::default() + } + } + + pub fn set(&mut self, cpu_state: &Arc>) { + let locked_cpu_state = cpu_state.lock().unwrap(); + self.apic_id = locked_cpu_state.apic_id; + self.mp_state = locked_cpu_state.mp_state; + self.core_regs = locked_cpu_state.core_regs; + self.aia_csrs = locked_cpu_state.aia_csrs; + self.csrs = locked_cpu_state.csrs; + self.config_regs = locked_cpu_state.config_regs; + self.timer_regs = locked_cpu_state.timer_regs; + self.xlen = locked_cpu_state.xlen; + } + + /// Set cpu topology + /// + /// # Arguments + /// + /// * `topology` - RISCV CPU Topology + pub fn set_cpu_topology(&mut self, _topology: &RISCVCPUTopology) -> Result<()> { + Ok(()) + } + + /// Get config_regs value. + pub fn config_regs(&self) -> ConfigRegs { + self.config_regs + } + + /// Get core_regs value. + pub fn core_regs(&self) -> CoreRegs { + self.core_regs + } + + /// Get timer_regs value. + pub fn timer_regs(&self) -> TimerRegs { + self.timer_regs + } + + /// Get aia csrs. + pub fn aia_csrs(&self) -> AiaCsrs { + self.aia_csrs + } + + /// Get csrs. + pub fn csrs(&self) -> Csrs { + self.csrs + } + + /// Set core registers before boot + /// See https://elixir.bootlin.com/linux/v6.6/source/Documentation/riscv/boot.rst + pub fn set_core_reg(&mut self, boot_config: &RISCVCPUBootConfig) { + // Set core regs. + self.core_regs.regs.a0 = self.apic_id as u64; + self.core_regs.regs.a1 = boot_config.fdt_addr; + self.core_regs.regs.pc = boot_config.boot_pc; + } + + /// Get regs_len. + pub fn get_xlen(&self) -> u64 { + self.xlen + } +} + +impl StateTransfer for CPU { + fn get_state_vec(&self) -> Result> { + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::CoreRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::MpState)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::TimerRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::ConfigRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::AiaCsrs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::Csrs)?; + + Ok(self.arch_cpu.lock().unwrap().as_bytes().to_vec()) + } + + fn set_state(&self, state: &[u8]) -> Result<()> { + let cpu_state = *RISCVCPUState::from_bytes(state) + .with_context(|| MigrationError::FromBytesError("CPU"))?; + + let mut cpu_state_locked = self.arch_cpu.lock().unwrap(); + *cpu_state_locked = cpu_state; + drop(cpu_state_locked); + + Ok(()) + } + + fn get_device_alias(&self) -> u64 { + MigrationManager::get_desc_alias(&RISCVCPUState::descriptor().name).unwrap_or(!0) + } +} + +impl MigrationHook for CPU {} diff --git a/cpu/src/x86_64/mod.rs b/cpu/src/x86_64/mod.rs index acb6fb2..0a8ad16 100644 --- a/cpu/src/x86_64/mod.rs +++ b/cpu/src/x86_64/mod.rs @@ -75,7 +75,7 @@ pub enum X86RegsIndex { /// X86 CPU booting configure information #[allow(clippy::upper_case_acronyms)] -#[derive(Default, Clone, Debug, Copy)] +#[derive(Default, Clone, Debug)] pub struct X86CPUBootConfig { pub prot64_mode: bool, /// Register %rip value diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 2e6574e..01bea56 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -12,6 +12,8 @@ anyhow = "1.0" libc = "0.2" log = "0.4" serde = { version = "1.0", features = ["derive"] } +strum = "0.24.1" +strum_macros = "0.24.3" vmm-sys-util = "0.12.1" byteorder = "1.4.3" drm-fourcc = ">=2.2.0" @@ -34,9 +36,9 @@ psimple = { version = "2.27", package = "libpulse-simple-binding", optional = tr alsa = { version = "0.7.0", optional = true } rusb = { version = "0.9", optional = true } libusb1-sys = { version = "0.6.4", optional = true } -cairo-rs = { version = "0.17.10", optional = true } trace = { path = "../trace" } clap = { version = "=4.1.4", default-features = false, features = ["std", "derive"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } [features] default = [] @@ -46,8 +48,8 @@ scream_pulseaudio = ["scream", "dep:pulse", "dep:psimple", "machine_manager/scre scream_ohaudio = ["scream", "machine_manager/scream_ohaudio", "util/scream_ohaudio"] pvpanic = ["machine_manager/pvpanic"] demo_device = ["machine_manager/demo_device", "ui/console", "util/pixman"] -usb_host = ["dep:libusb1-sys", "dep:rusb", "machine_manager/usb_host"] +usb_host = ["dep:libusb1-sys", "dep:rusb", "machine_manager/usb_host", "util/usb_host"] usb_camera = ["machine_manager/usb_camera"] -usb_camera_v4l2 = ["usb_camera", "dep:cairo-rs", "dep:v4l2-sys-mit", "machine_manager/usb_camera_v4l2", "util/usb_camera_v4l2"] +usb_camera_v4l2 = ["usb_camera", "dep:v4l2-sys-mit", "machine_manager/usb_camera_v4l2", "util/usb_camera_v4l2"] usb_camera_oh = ["usb_camera", "machine_manager/usb_camera_oh", "util/usb_camera_oh"] ramfb = ["ui/console", "util/pixman"] diff --git a/devices/src/acpi/cpu_controller.rs b/devices/src/acpi/cpu_controller.rs index 73f2601..4fe256c 100644 --- a/devices/src/acpi/cpu_controller.rs +++ b/devices/src/acpi/cpu_controller.rs @@ -19,7 +19,7 @@ use anyhow::{bail, Context, Result}; use log::{error, info}; use vmm_sys_util::eventfd::EventFd; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps}; use crate::{Device, DeviceBase}; use acpi::{ AcpiError, AcpiLocalApic, AmlAcquire, AmlAddressSpaceType, AmlArg, AmlBuffer, AmlBuilder, @@ -99,11 +99,11 @@ impl CpuController { self.max_cpus = max_cpus; self.cpu_config = Some(cpu_config); self.hotplug_cpu_req = Some(hotplug_cpu_req); - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, region_base, region_size, "CPUController") .with_context(|| AcpiError::Alignment(region_size.try_into().unwrap()))?; let dev = Arc::new(Mutex::new(self)); let ret_dev = dev.clone(); - sysbus.attach_device(&dev, region_base, region_size, "CPUController")?; + sysbus.attach_device(&dev)?; Ok(ret_dev) } @@ -157,8 +157,8 @@ impl CpuController { None } - pub fn get_boot_config(&self) -> CPUBootConfig { - self.cpu_config.as_ref().unwrap().boot_config + pub fn get_boot_config(&self) -> &CPUBootConfig { + &self.cpu_config.as_ref().unwrap().boot_config } pub fn get_hotplug_cpu_info(&self) -> (String, u8) { @@ -329,15 +329,11 @@ impl SysBusDevOps for CpuController { } true } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl AmlBuilder for CpuController { fn aml_bytes(&self) -> Vec { - let res = self.base.res; + let res = self.base.res.clone(); let mut cpu_hotplug_controller = AmlDevice::new("PRES"); cpu_hotplug_controller.append_child(AmlNameDecl::new("_HID", AmlEisaId::new("PNP0A06"))); cpu_hotplug_controller.append_child(AmlNameDecl::new( diff --git a/devices/src/acpi/ged.rs b/devices/src/acpi/ged.rs index f50e1b1..602a999 100644 --- a/devices/src/acpi/ged.rs +++ b/devices/src/acpi/ged.rs @@ -19,7 +19,7 @@ use anyhow::{Context, Result}; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps}; use crate::{Device, DeviceBase}; use acpi::{ AcpiError, AmlActiveLevel, AmlAddressSpaceType, AmlAnd, AmlBuilder, AmlDevice, AmlEdgeLevel, @@ -35,7 +35,7 @@ use address_space::GuestAddress; use machine_manager::event; use machine_manager::event_loop::EventLoop; use machine_manager::qmp::qmp_channel::QmpChannel; -use util::loop_context::{read_fd, EventNotifier, NotifierOperation}; +use util::loop_context::{create_new_eventfd, read_fd, EventNotifier, NotifierOperation}; use util::{loop_context::NotifierCallback, num_ops::write_data_u32}; #[derive(Clone, Copy)] @@ -96,13 +96,13 @@ impl Ged { region_base: u64, region_size: u64, ) -> Result>> { - self.base.interrupt_evt = Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK)?)); - self.set_sys_resource(sysbus, region_base, region_size) + self.base.interrupt_evt = Some(Arc::new(create_new_eventfd()?)); + self.set_sys_resource(sysbus, region_base, region_size, "Ged") .with_context(|| AcpiError::Alignment(region_size as u32))?; self.battery_present = battery_present; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "Ged")?; + sysbus.attach_device(&dev)?; let ged = dev.lock().unwrap(); ged.register_acpi_powerdown_event(ged_event.power_button) @@ -120,8 +120,9 @@ impl Ged { read_fd(power_down_fd); ged_clone .notification_type - .store(AcpiEvent::PowerDown as u32, Ordering::SeqCst); + .fetch_or(AcpiEvent::PowerDown as u32, Ordering::SeqCst); ged_clone.inject_interrupt(); + trace::ged_inject_acpi_event(AcpiEvent::PowerDown as u32); if QmpChannel::is_connected() { event!(Powerdown); } @@ -149,8 +150,9 @@ impl Ged { read_fd(cpu_resize_fd); clone_ged .notification_type - .store(AcpiEvent::CpuResize as u32, Ordering::SeqCst); + .fetch_or(AcpiEvent::CpuResize as u32, Ordering::SeqCst); clone_ged.inject_interrupt(); + trace::ged_inject_acpi_event(AcpiEvent::CpuResize as u32); if QmpChannel::is_connected() { event!(CpuResize); } @@ -174,6 +176,7 @@ impl Ged { self.notification_type .fetch_or(evt as u32, Ordering::SeqCst); self.inject_interrupt(); + trace::ged_inject_acpi_event(evt as u32); } } @@ -203,16 +206,13 @@ impl SysBusDevOps for Ged { let value = self .notification_type .swap(AcpiEvent::Nothing as u32, Ordering::SeqCst); + trace::ged_read(value); write_data_u32(data, value) } fn write(&mut self, _data: &[u8], _base: GuestAddress, _offset: u64) -> bool { true } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl AmlBuilder for Ged { @@ -228,6 +228,8 @@ impl AmlBuilder for Ged { let irq_base = INTERRUPT_PPIS_COUNT + INTERRUPT_SGIS_COUNT; #[cfg(target_arch = "x86_64")] let irq_base = 0; + #[cfg(target_arch = "riscv64")] + let irq_base = 0; res.append_child(AmlExtendedInterrupt::new( AmlResourceUsage::Consumer, AmlEdgeLevel::Edge, diff --git a/devices/src/acpi/power.rs b/devices/src/acpi/power.rs index a51071d..d64486f 100644 --- a/devices/src/acpi/power.rs +++ b/devices/src/acpi/power.rs @@ -18,7 +18,7 @@ use anyhow::{Context, Result}; use log::info; use crate::acpi::ged::{AcpiEvent, Ged}; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps}; use crate::{Device, DeviceBase}; use acpi::{ AcpiError, AmlAddressSpaceType, AmlBuilder, AmlDevice, AmlField, AmlFieldAccessType, @@ -154,6 +154,8 @@ impl PowerDev { // unit: mW self.regs[REG_IDX_BAT_PRATE] = (self.regs[REG_IDX_BAT_PRATE] * self.regs[REG_IDX_BAT_PVOLT]) / 1000; + + trace::power_status_read(&self.regs); Ok(()) } @@ -181,11 +183,11 @@ impl PowerDev { region_base: u64, region_size: u64, ) -> Result<()> { - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, region_base, region_size, "PowerDev") .with_context(|| AcpiError::Alignment(region_size as u32))?; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "PowerDev")?; + sysbus.attach_device(&dev)?; let pdev_available: bool; { @@ -253,16 +255,13 @@ impl SysBusDevOps for PowerDev { return false; } let value = self.regs[reg_idx as usize]; + trace::power_read(reg_idx, value); write_data_u32(data, value) } fn write(&mut self, _data: &[u8], _base: GuestAddress, _offset: u64) -> bool { true } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl AmlBuilder for PowerDev { diff --git a/devices/src/camera_backend/demo.rs b/devices/src/camera_backend/demo.rs index b3ff2e9..19b1d4d 100644 --- a/devices/src/camera_backend/demo.rs +++ b/devices/src/camera_backend/demo.rs @@ -13,13 +13,10 @@ //! Demo backend for vCamera device, that helps for testing. use std::fs::read_to_string; -use std::ops::Deref; use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; -#[cfg(not(target_env = "ohos"))] -use cairo::{Format, ImageSurface}; use log::{debug, error, info}; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; @@ -239,20 +236,13 @@ impl ImageFrame { ImageMode::Random => RgbColor::from(self.frame_idx as u8 % 8), }; debug!("Demo Image color {:?}", color); - let mut surface = ImageSurface::create(Format::Rgb24, width as i32, height as i32)?; - let cr = cairo::Context::new(&surface)?; let (r, g, b) = get_rgb_color(&color); - cr.set_source_rgb(r as f64, g as f64, b as f64); - cr.rectangle(0.0, 0.0, width as f64, height as f64); - cr.fill()?; - cr.paint()?; - drop(cr); - let data = surface.data()?; + let data = init_img(width, height, (r, g, b)); let image = match format { FmtType::Mjpg => build_fake_mjpg(width, height), - FmtType::Yuy2 => convert_to_yuy2(data.deref(), width, height), - FmtType::Rgb565 => data.deref().to_vec(), - FmtType::Nv12 => bail!("demo device does not support NV12 now"), + FmtType::Yuy2 => convert_to_yuy2(&data, width, height), + FmtType::Rgb565 => data, + FmtType::Nv12 => convert_to_nv12(&data, width, height), }; self.frame_idx += 1; if self.frame_idx > FRAME_IDX_LIMIT { @@ -269,7 +259,12 @@ fn read_config(path: &str) -> Result { } fn build_format_list() -> Vec { - vec![build_yuy2_list(), build_mjpg_list(), build_rgb565_list()] + vec![ + build_yuy2_list(), + build_mjpg_list(), + build_rgb565_list(), + build_nv12_list(), + ] } fn build_yuy2_list() -> CameraFormatList { @@ -377,6 +372,33 @@ fn build_rgb565_list() -> CameraFormatList { } } +fn build_nv12_list() -> CameraFormatList { + CameraFormatList { + format: FmtType::Nv12, + fmt_index: 4, + frame: vec![ + CameraFrame { + width: 1280, + height: 720, + interval: INTERVALS_PER_SEC / 10, + index: 1, + }, + CameraFrame { + width: 640, + height: 480, + interval: INTERVALS_PER_SEC / 30, + index: 2, + }, + CameraFrame { + width: 480, + height: 240, + interval: INTERVALS_PER_SEC / 30, + index: 3, + }, + ], + } +} + impl CameraBackend for DemoCameraBackend { fn set_fmt(&mut self, cam_fmt: &CamBasicFmt) -> Result<()> { *self.cur_format.lock().unwrap() = *cam_fmt; @@ -502,6 +524,48 @@ fn clip(x: i32) -> u8 { } } +fn init_img(width: u32, height: u32, color: (u8, u8, u8)) -> Vec { + let len = height * width; + let (r, g, b) = color; + let mut img: Vec = Vec::with_capacity((len * 4) as usize); + for _ in 0..len { + img.push(b); + img.push(g); + img.push(r); + img.push(255); + } + img +} + +fn convert_to_nv12(source: &[u8], width: u32, height: u32) -> Vec { + let pixel = 4; + let len = height * width; + let mut img_nv12: Vec = Vec::with_capacity(len as usize); + for i in 0..len { + let idx = (i * pixel) as usize; + let (b, g, r) = ( + source[idx] as f32, + source[idx + 1] as f32, + source[idx + 2] as f32, + ); + let y = (0.299 * r + 0.587 * g + 0.114 * b) as u8; + img_nv12.push(y); + } + for i in 0..(width * height / 2) { + let idx = (i * 2 * pixel) as usize; + let (b, g, r) = ( + source[idx] as f32, + source[idx + 1] as f32, + source[idx + 2] as f32, + ); + let u = (-0.147 * r - 0.289 * g + 0.436 * b + 128_f32) as u8; + let v = (0.615 * r - 0.515 * g - 0.100 * b + 128_f32) as u8; + img_nv12.push(u); + img_nv12.push(v); + } + img_nv12 +} + fn convert_to_yuy2(source: &[u8], width: u32, height: u32) -> Vec { let pixbytes = 4; let sz = width * height * 2; diff --git a/devices/src/camera_backend/mod.rs b/devices/src/camera_backend/mod.rs index a4e919a..00723e5 100644 --- a/devices/src/camera_backend/mod.rs +++ b/devices/src/camera_backend/mod.rs @@ -14,7 +14,6 @@ //! Backend devices, such as v4l2, usb, or demo device, etc., shall implement trait //! CameraBackend. -#[cfg(not(target_env = "ohos"))] pub mod demo; #[cfg(all(target_env = "ohos", feature = "usb_camera_oh"))] pub mod ohcam; @@ -26,7 +25,6 @@ use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; -#[cfg(not(target_env = "ohos"))] use self::demo::DemoCameraBackend; #[cfg(all(target_env = "ohos", feature = "usb_camera_oh"))] use self::ohcam::OhCameraBackend; @@ -205,7 +203,6 @@ pub fn create_cam_backend( cameradev.id, cameradev.path, )?)), - #[cfg(not(target_env = "ohos"))] CamBackendType::Demo => Arc::new(Mutex::new(DemoCameraBackend::new( config.id, cameradev.path, diff --git a/devices/src/camera_backend/ohcam.rs b/devices/src/camera_backend/ohcam.rs index f27abb1..55b6a80 100755 --- a/devices/src/camera_backend/ohcam.rs +++ b/devices/src/camera_backend/ohcam.rs @@ -20,6 +20,12 @@ use crate::camera_backend::{ CamBasicFmt, CameraBackend, CameraBrokenCallback, CameraFormatList, CameraFrame, CameraNotifyCallback, FmtType, }; +#[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") +))] +use trace::trace_scope::Scope; use util::aio::Iovec; use util::ohos_binding::camera::*; @@ -29,6 +35,9 @@ static OHCAM_CALLBACK: Lazy = Lazy::new(|| RwLock::new(OhCamCallBack::d // In UVC, interval's unit is 100ns. // So, fps * interval / 10_000_000 == 1. const FPS_INTERVAL_TRANS: u32 = 10_000_000; +const RESOLUTION_WHITELIST: [(i32, i32); 2] = [(640, 480), (1280, 720)]; +const FRAME_FORMAT_WHITELIST: [i32; 2] = [CAMERA_FORMAT_YUYV422, CAMERA_FORMAT_NV12]; +const FPS_WHITELIST: [i32; 1] = [30]; #[derive(Default)] struct OhCamCallBack { @@ -76,6 +85,33 @@ impl OhCamCallBack { } } +#[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") +))] +#[derive(Clone, Default)] +struct OhCameraAsyncScope { + next_frame_id: u64, + async_scope: Option, +} + +#[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") +))] +impl OhCameraAsyncScope { + fn start(&mut self) { + self.async_scope = Some(trace::ohcam_next_frame(true, self.next_frame_id)); + self.next_frame_id += 1; + } + + fn stop(&mut self) { + self.async_scope = None; + } +} + #[derive(Clone)] pub struct OhCameraBackend { id: String, @@ -84,6 +120,12 @@ pub struct OhCameraBackend { ctx: OhCamera, fmt_list: Vec, selected_profile: u8, + #[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") + ))] + async_scope: Box, } // SAFETY: Send and Sync is not auto-implemented for raw pointer type. @@ -95,6 +137,8 @@ unsafe impl Sync for OhCameraBackend {} fn cam_fmt_from_oh(t: i32) -> Result { let fmt = match t { CAMERA_FORMAT_YUV420SP => FmtType::Nv12, + CAMERA_FORMAT_NV12 => FmtType::Nv12, + CAMERA_FORMAT_YUYV422 => FmtType::Yuy2, CAMERA_FORMAT_MJPEG => FmtType::Mjpg, _ => bail!("OHCAM: No supported type {}", t), }; @@ -116,6 +160,12 @@ impl OhCameraBackend { ctx, fmt_list: vec![], selected_profile: 0, + #[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") + ))] + async_scope: Box::new(OhCameraAsyncScope::default()), }) } } @@ -158,6 +208,12 @@ impl CameraBackend for OhCameraBackend { fn video_stream_off(&mut self) -> Result<()> { self.ctx.stop_stream(); OHCAM_CALLBACK.write().unwrap().clear_buffer(); + #[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") + ))] + self.async_scope.stop(); Ok(()) } @@ -166,14 +222,13 @@ impl CameraBackend for OhCameraBackend { for idx in 0..self.profile_cnt { match self.ctx.get_profile(self.camidx as i32, idx as i32) { - Ok((fmt, width, height, mut fps)) => { - if (fmt != CAMERA_FORMAT_YUV420SP) && (fmt != CAMERA_FORMAT_MJPEG) { + Ok((fmt, width, height, fps)) => { + if !FRAME_FORMAT_WHITELIST.iter().any(|&x| x == fmt) + || !RESOLUTION_WHITELIST.iter().any(|&x| x == (width, height)) + || !FPS_WHITELIST.iter().any(|&x| x == fps) + { continue; } - // NOTE: windows camera APP doesn't support fps lower than 30, and some OH PC only support 15 fps. - if fps < 30 { - fps = 30; - } let frame = CameraFrame { width: width as u32, @@ -197,6 +252,12 @@ impl CameraBackend for OhCameraBackend { fn reset(&mut self) { OHCAM_CALLBACK.write().unwrap().clear_buffer(); self.ctx.reset_camera(); + #[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") + ))] + self.async_scope.stop(); } fn get_format_by_index(&self, format_index: u8, frame_index: u8) -> Result { @@ -236,6 +297,12 @@ impl CameraBackend for OhCameraBackend { } fn next_frame(&mut self) -> Result<()> { + #[cfg(any( + feature = "trace_to_logger", + feature = "trace_to_ftrace", + all(target_env = "ohos", feature = "trace_to_hitrace") + ))] + self.async_scope.start(); self.ctx.next_frame(); OHCAM_CALLBACK.write().unwrap().clear_buffer(); Ok(()) @@ -251,6 +318,8 @@ impl CameraBackend for OhCameraBackend { bail!("Invalid frame offset {} or len {}", frame_offset, len); } + trace::trace_scope_start!(ohcam_get_frame, args = (frame_offset, len)); + let mut copied = 0; for iov in iovecs { if len == copied { diff --git a/devices/src/interrupt_controller/aarch64/gicv3.rs b/devices/src/interrupt_controller/aarch64/gicv3.rs index b9cb520..60babf0 100644 --- a/devices/src/interrupt_controller/aarch64/gicv3.rs +++ b/devices/src/interrupt_controller/aarch64/gicv3.rs @@ -41,35 +41,58 @@ pub struct GICv3Config { pub trait GICv3Access: Send + Sync { fn init_gic( &self, - nr_irqs: u32, - redist_regions: Vec, - dist_base: u64, - ) -> Result<()>; + _nr_irqs: u32, + _redist_regions: Vec, + _dist_base: u64, + ) -> Result<()> { + Ok(()) + } - /// Returns `gicr_attr` of `vCPU`. - fn vcpu_gicr_attr(&self, cpu: usize) -> u64; + fn vcpu_gicr_attr(&self, _cpu: usize) -> u64 { + 0 + } - fn access_gic_distributor(&self, offset: u64, gicd_value: &mut u32, write: bool) -> Result<()>; + fn access_gic_distributor( + &self, + _offset: u64, + _gicd_value: &mut u32, + _write: bool, + ) -> Result<()> { + Ok(()) + } fn access_gic_redistributor( &self, - offset: u64, - cpu: usize, - gicr_value: &mut u32, - write: bool, - ) -> Result<()>; + _offset: u64, + _cpu: usize, + _gicr_value: &mut u32, + _write: bool, + ) -> Result<()> { + Ok(()) + } fn access_gic_cpu( &self, - offset: u64, - cpu: usize, - gicc_value: &mut u64, - write: bool, - ) -> Result<()>; + _offset: u64, + _cpu: usize, + _gicc_value: &mut u64, + _write: bool, + ) -> Result<()> { + Ok(()) + } - fn access_gic_line_level(&self, offset: u64, gicll_value: &mut u32, write: bool) -> Result<()>; + fn access_gic_line_level( + &self, + _offset: u64, + _gicll_value: &mut u32, + _write: bool, + ) -> Result<()> { + Ok(()) + } - fn pause(&self) -> Result<()>; + fn pause(&self) -> Result<()> { + Ok(()) + } } #[derive(Clone, Copy)] @@ -328,13 +351,21 @@ impl GICDevice for GICv3 { } pub trait GICv3ItsAccess: Send + Sync { - fn init_gic_its(&self, msi_base: u64) -> Result<()>; + fn init_gic_its(&self, _msi_base: u64) -> Result<()> { + Ok(()) + } - fn access_gic_its(&self, attr: u32, its_value: &mut u64, write: bool) -> Result<()>; + fn access_gic_its(&self, _attr: u32, _its_value: &mut u64, _write: bool) -> Result<()> { + Ok(()) + } - fn access_gic_its_tables(&self, save: bool) -> Result<()>; + fn access_gic_its_tables(&self, _save: bool) -> Result<()> { + Ok(()) + } - fn reset(&self) -> Result<()>; + fn reset(&self) -> Result<()> { + Ok(()) + } } pub struct GICv3Its { diff --git a/devices/src/interrupt_controller/mod.rs b/devices/src/interrupt_controller/mod.rs index 4a0453c..07a6328 100644 --- a/devices/src/interrupt_controller/mod.rs +++ b/devices/src/interrupt_controller/mod.rs @@ -18,16 +18,19 @@ //! //! This module offers support for: //! 1. Create hypervisor-based interrupt controller. -//! 2. Manager lifecycle for `GIC`. +//! 2. Manager lifecycle for in-kernel interrupt chips. //! //! ## Platform Support //! //! - `aarch64` +//! - `riscv64` #[allow(clippy::upper_case_acronyms)] #[cfg(target_arch = "aarch64")] mod aarch64; mod error; +#[cfg(target_arch = "riscv64")] +mod riscv64; #[cfg(target_arch = "aarch64")] pub use aarch64::{ @@ -36,6 +39,8 @@ pub use aarch64::{ GICv3ItsState, GICv3State, GicRedistRegion, InterruptController, GIC_IRQ_INTERNAL, GIC_IRQ_MAX, }; pub use error::InterruptError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::{AIAAccess, AIAConfig, AIADevice, InterruptController, AIA}; use std::sync::Arc; @@ -81,6 +86,8 @@ pub trait LineIrqManager: Send + Sync { } pub trait MsiIrqManager: Send + Sync { + fn irqfd_enable(&self) -> bool; + fn allocate_irq(&self, _vector: MsiVector) -> Result { Ok(0) } diff --git a/devices/src/interrupt_controller/riscv64/aia.rs b/devices/src/interrupt_controller/riscv64/aia.rs new file mode 100644 index 0000000..d6cd779 --- /dev/null +++ b/devices/src/interrupt_controller/riscv64/aia.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::sync::{Arc, Mutex}; + +use anyhow::{Context, Result}; +use kvm_bindings::{KVM_DEV_RISCV_APLIC_SIZE, KVM_DEV_RISCV_IMSIC_SIZE}; +use log::{error, info}; + +use super::{AIAConfig, AIADevice}; +use machine_manager::machine::{MachineLifecycle, VmState}; +use util::device_tree::{self, FdtBuilder}; + +/// Access wrapper for AIA. +pub trait AIAAccess: Send + Sync { + fn init_aia(&self, nr_irqs: u32, aia_addr: u64) -> Result<()>; + + fn pause(&self) -> Result<()>; +} + +/// A wrapper around creating and managing a `AIA`. +pub struct AIA { + /// The handler for the AIA device to access the corresponding device in hypervisor. + pub hypervisor_aia: Arc, + /// Number of vCPUs, determines the number of redistributor and CPU interface. + pub(crate) vcpu_count: u32, + /// Maximum irq number. + pub(crate) nr_irqs: u32, + /// IMSIC's start addr. + pub(crate) imsic_start_addr_of: Vec, + /// APLIC's start addr. + pub(crate) aplic_start_addr: u64, + /// Base address in the guest physical address space of AIA + dist_base: u64, + /// AIA distributor region size. + dist_size: u64, + /// Lifecycle state for AIA. + state: Arc>, +} + +impl AIA { + pub fn new(hypervisor_aia: Arc, config: &AIAConfig) -> Result { + use kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE; + // Calculate AIA's IMSIC start address + let mut imsic_start_addr_of = Vec::new(); + for i in 0..config.vcpu_count { + imsic_start_addr_of.push(config.region_range.0 + (i * KVM_DEV_RISCV_IMSIC_SIZE) as u64); + } + let aplic_start_addr = + (config.vcpu_count * KVM_DEV_RISCV_IMSIC_SIZE) as u64 + config.region_range.0; + + Ok(AIA { + hypervisor_aia, + vcpu_count: config.vcpu_count, + nr_irqs: config.max_irq, + imsic_start_addr_of, + aplic_start_addr, + state: Arc::new(Mutex::new(VmState::Created)), + dist_base: config.region_range.0, + dist_size: config.region_range.1, + }) + } +} + +impl MachineLifecycle for AIA { + fn pause(&self) -> bool { + // VM change state will flush REDIST pending tables into guest RAM. + if let Err(e) = self.hypervisor_aia.pause() { + error!( + "Failed to flush REDIST pending tables into guest RAM, error: {:?}", + e + ); + return false; + } + + let mut state = self.state.lock().unwrap(); + *state = VmState::Running; + + true + } + + fn notify_lifecycle(&self, old: VmState, new: VmState) -> bool { + let state = self.state.lock().unwrap(); + if *state != old { + error!("AIA lifecycle error: state check failed."); + return false; + } + drop(state); + + match (old, new) { + (VmState::Running, VmState::Paused) => self.pause(), + _ => true, + } + } +} + +impl AIADevice for AIA { + fn realize(&self) -> Result<()> { + self.hypervisor_aia + .init_aia(self.nr_irqs, self.dist_base) + .with_context(|| "Failed to init AIA")?; + + let mut state = self.state.lock().unwrap(); + *state = VmState::Running; + + Ok(()) + } + + fn generate_fdt(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = format!("imsics@{:x}", self.imsic_start_addr_of[0] as u32); + let imsics_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", device_tree::AIA_IMSIC_PHANDLE)?; + fdt.set_property_u32("riscv,num-ids", 2047)?; + + let mut reg_cells = Vec::new(); + reg_cells.push(0); + reg_cells.push(self.imsic_start_addr_of[0] as u32); + reg_cells.push(0); + reg_cells.push(self.vcpu_count * KVM_DEV_RISCV_IMSIC_SIZE); + info!("imsic regs: {:?}", ®_cells); + fdt.set_property_array_u32("reg", ®_cells)?; + + let mut irq_cells = Vec::new(); + for i in 0..self.vcpu_count { + irq_cells.push(device_tree::INTC_PHANDLE_START + i); + irq_cells.push(9); + } + fdt.set_property_array_u32("interrupts-extended", &irq_cells)?; + + fdt.set_property("msi-controller", &Vec::new())?; + fdt.set_property("interrupt-controller", &Vec::new())?; + fdt.set_property_u32("#interrupt-cells", 0)?; + fdt.set_property_string("compatible", "riscv,imsics")?; + fdt.end_node(imsics_node_dep)?; + + // APLIC + let node = format!("aplic@{:x}", self.aplic_start_addr as u32); + let aplic_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", device_tree::AIA_APLIC_PHANDLE)?; + fdt.set_property_u32("riscv,num-sources", 96)?; + + let mut reg_cells = Vec::new(); + reg_cells.push(0); + reg_cells.push(self.aplic_start_addr as u32); + reg_cells.push(0); + reg_cells.push(KVM_DEV_RISCV_APLIC_SIZE); + info!("aplic regs: {:?}", ®_cells); + fdt.set_property_array_u32("reg", ®_cells)?; + + fdt.set_property_u32("msi-parent", device_tree::AIA_IMSIC_PHANDLE)?; + fdt.set_property("interrupt-controller", &Vec::new())?; + fdt.set_property_u32("#interrupt-cells", 2)?; + fdt.set_property_string("compatible", "riscv,aplic")?; + + fdt.end_node(aplic_node_dep)?; + + Ok(()) + } + + fn reset(&self) -> Result<()> { + Ok(()) + } +} diff --git a/devices/src/interrupt_controller/riscv64/mod.rs b/devices/src/interrupt_controller/riscv64/mod.rs new file mode 100644 index 0000000..d6da2f7 --- /dev/null +++ b/devices/src/interrupt_controller/riscv64/mod.rs @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +mod aia; + +use std::sync::Arc; + +use anyhow::{Context, Result}; + +pub use aia::{AIAAccess, AIA}; +use machine_manager::machine::{MachineLifecycle, VmState}; +use util::device_tree::{self, FdtBuilder}; + +pub struct AIAConfig { + /// Config number of CPUs handled by the device + pub vcpu_count: u32, + /// Config maximum number of irqs handled by the device + pub max_irq: u32, + /// Config AIA address range + pub region_range: (u64, u64), +} + +impl AIAConfig { + pub fn check_sanity(&self) -> Result<()> { + // Yet implemented + Ok(()) + } +} + +/// A wrapper for `AIA` must perform the function. +pub trait AIADevice: MachineLifecycle { + /// Realize function for hypervisor_based `AIA` device. + fn realize(&self) -> Result<()>; + + /// Reset 'AIA' + fn reset(&self) -> Result<()> { + Ok(()) + } + + /// Constructs `fdt` node for `AIA`. + /// + /// # Arguments + /// + /// * `fdt` - Device tree presented by bytes. + fn generate_fdt(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +#[derive(Clone)] +/// A wrapper around creating and using a hypervisor-based interrupt controller. +pub struct InterruptController { + aia: Arc, +} + +impl InterruptController { + /// Constructs a new hypervisor_based `InterruptController`. + pub fn new( + aia: Arc, + ) -> InterruptController { + InterruptController { aia } + } + + pub fn realize(&self) -> Result<()> { + self.aia + .realize() + .with_context(|| "Failed to realize AIA")?; + Ok(()) + } + + /// Reset the InterruptController + pub fn reset(&self) -> Result<()> { + self.aia.reset().with_context(|| "Failed to reset AIA") + } + + /// Change `InterruptController` lifecycle state to `Stopped`. + pub fn stop(&self) { + self.aia.notify_lifecycle(VmState::Running, VmState::Paused); + } +} + +impl device_tree::CompileFDT for InterruptController { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + self.aia.generate_fdt(fdt)?; + Ok(()) + } +} diff --git a/devices/src/legacy/fwcfg.rs b/devices/src/legacy/fwcfg.rs index 2abf436..e8fb9d1 100644 --- a/devices/src/legacy/fwcfg.rs +++ b/devices/src/legacy/fwcfg.rs @@ -19,14 +19,14 @@ use byteorder::{BigEndian, ByteOrder}; use log::{error, warn}; use crate::legacy::error::LegacyError; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::{ AmlBuilder, AmlDevice, AmlInteger, AmlNameDecl, AmlResTemplate, AmlScopeBuilder, AmlString, }; #[cfg(target_arch = "x86_64")] use acpi::{AmlIoDecode, AmlIoResource}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use acpi::{AmlMemory32Fixed, AmlReadAndWrite}; use address_space::{AddressSpace, GuestAddress}; use util::byte_code::ByteCode; @@ -361,11 +361,14 @@ impl FwCfgCommon { /// Select the entry by the key specified fn select_entry(&mut self, key: u16) { + let ret; self.cur_offset = 0; if (key & FW_CFG_ENTRY_MASK) >= self.max_entry() { self.cur_entry = FW_CFG_INVALID; + ret = 0; } else { self.cur_entry = key; + ret = 1; // unwrap() is safe because we have checked the range of `key`. let selected_entry = self.get_entry_mut().unwrap(); @@ -373,6 +376,8 @@ impl FwCfgCommon { cb.select_callback(); } } + + trace::fwcfg_select_entry(key, get_key_name(key as usize), ret); } fn add_entry( @@ -404,11 +409,12 @@ impl FwCfgCommon { warn!("Entry not empty, will override"); } - entry.data = data; + entry.data = data.clone(); entry.select_cb = select_cb; entry.allow_write = allow_write; entry.write_cb = write_cb; + trace::fwcfg_add_entry(key, get_key_name(key as usize), data); Ok(()) } @@ -467,11 +473,8 @@ impl FwCfgCommon { } } - let file = FwCfgFile::new( - data.len() as u32, - FW_CFG_FILE_FIRST + index as u16, - filename, - ); + let data_len = data.len(); + let file = FwCfgFile::new(data_len as u32, FW_CFG_FILE_FIRST + index as u16, filename); self.files.insert(index, file); self.files.iter_mut().skip(index + 1).for_each(|f| { f.select += 1; @@ -489,6 +492,8 @@ impl FwCfgCommon { FW_CFG_FILE_FIRST as usize + index, FwCfgEntry::new(data, select_cb, write_cb, allow_write), ); + + trace::fwcfg_add_file(index, filename, data_len); Ok(()) } @@ -650,6 +655,8 @@ impl FwCfgCommon { self.cur_offset = offset; write_dma_result(&self.mem_space, dma_addr, dma.control)?; + + trace::fwcfg_read_data(0); Ok(()) } @@ -743,6 +750,8 @@ impl FwCfgCommon { value <<= 8 * size as u64; } self.cur_offset = cur_offset; + + trace::fwcfg_read_data(value); Ok(value) } @@ -808,7 +817,7 @@ fn get_io_count(data_len: usize) -> usize { } fn common_read( - #[cfg(target_arch = "aarch64")] fwcfg_arch: &mut FwCfgMem, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fwcfg_arch: &mut FwCfgMem, #[cfg(target_arch = "x86_64")] fwcfg_arch: &mut FwCfgIO, data: &mut [u8], base: GuestAddress, @@ -825,13 +834,13 @@ fn common_read( } /// FwCfg MMIO Device use for AArch64 platform -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub struct FwCfgMem { base: SysBusDevBase, fwcfg: FwCfgCommon, } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl FwCfgMem { pub fn new(sys_mem: Arc) -> Self { FwCfgMem { @@ -847,18 +856,18 @@ impl FwCfgMem { region_size: u64, ) -> Result>> { self.fwcfg.common_realize()?; - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, region_base, region_size, "FwCfgMem") .with_context(|| "Failed to allocate system resource for FwCfg.")?; let dev = Arc::new(Mutex::new(self)); sysbus - .attach_device(&dev, region_base, region_size, "FwCfgMem") + .attach_device(&dev) .with_context(|| "Failed to attach FwCfg device to system bus.")?; Ok(dev) } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn read_bytes( fwcfg_arch: &mut FwCfgMem, data: &mut [u8], @@ -913,14 +922,14 @@ fn read_bytes( true } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl FwCfgOps for FwCfgMem { fn fw_cfg_common(&mut self) -> &mut FwCfgCommon { &mut self.fwcfg } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl Device for FwCfgMem { fn device_base(&self) -> &DeviceBase { &self.base.base @@ -931,7 +940,7 @@ impl Device for FwCfgMem { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl SysBusDevOps for FwCfgMem { fn sysbusdev_base(&self) -> &SysBusDevBase { &self.base @@ -980,19 +989,15 @@ impl SysBusDevOps for FwCfgMem { true } - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } - fn set_sys_resource( &mut self, _sysbus: &mut SysBus, region_base: u64, region_size: u64, + region_name: &str, ) -> Result<()> { - let res = self.get_sys_resource_mut().unwrap(); - res.region_base = region_base; - res.region_size = region_size; + self.sysbusdev_base_mut() + .set_sys(-1, region_base, region_size, region_name); Ok(()) } @@ -1013,30 +1018,19 @@ pub struct FwCfgIO { impl FwCfgIO { pub fn new(sys_mem: Arc) -> Self { FwCfgIO { - base: SysBusDevBase { - base: DeviceBase::default(), - dev_type: SysBusDevType::FwCfg, - res: SysRes { - region_base: FW_CFG_IO_BASE, - region_size: FW_CFG_IO_SIZE, - irq: -1, - }, - ..Default::default() - }, + base: SysBusDevBase::new(SysBusDevType::FwCfg), fwcfg: FwCfgCommon::new(sys_mem), } } pub fn realize(mut self, sysbus: &mut SysBus) -> Result>> { self.fwcfg.common_realize()?; - let region_base = self.base.res.region_base; - let region_size = self.base.res.region_size; - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, FW_CFG_IO_BASE, FW_CFG_IO_SIZE, "FwCfgIO") .with_context(|| "Failed to allocate system resource for FwCfg.")?; let dev = Arc::new(Mutex::new(self)); sysbus - .attach_device(&dev, region_base, region_size, "FwCfgIO") + .attach_device(&dev) .with_context(|| "Failed to attach FwCfg device to system bus.")?; Ok(dev) } @@ -1165,19 +1159,15 @@ impl SysBusDevOps for FwCfgIO { true } - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } - fn set_sys_resource( &mut self, _sysbus: &mut SysBus, region_base: u64, region_size: u64, + region_name: &str, ) -> Result<()> { - let res = self.get_sys_resource_mut().unwrap(); - res.region_base = region_base; - res.region_size = region_size; + self.sysbusdev_base_mut() + .set_sys(-1, region_base, region_size, region_name); Ok(()) } @@ -1258,7 +1248,7 @@ pub trait FwCfgOps: Send + Sync { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl AmlBuilder for FwCfgMem { fn aml_bytes(&self) -> Vec { let mut acpi_dev = AmlDevice::new("FWCF"); diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs index 00632fe..74cc2e4 100644 --- a/devices/src/legacy/mod.rs +++ b/devices/src/legacy/mod.rs @@ -53,5 +53,5 @@ pub use pl011::PL011; #[cfg(target_arch = "aarch64")] pub use pl031::{PL031, RTC_CR, RTC_DR, RTC_IMSC, RTC_LR}; #[cfg(all(feature = "ramfb", target_arch = "aarch64"))] -pub use ramfb::Ramfb; +pub use ramfb::{Ramfb, RamfbConfig}; pub use serial::{Serial, SERIAL_ADDR}; diff --git a/devices/src/legacy/pflash.rs b/devices/src/legacy/pflash.rs index e100392..6c225f6 100644 --- a/devices/src/legacy/pflash.rs +++ b/devices/src/legacy/pflash.rs @@ -18,7 +18,7 @@ use anyhow::{anyhow, bail, Context, Result}; use log::{error, warn}; use super::error::LegacyError; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::AmlBuilder; use address_space::{FileBackend, GuestAddress, HostMemMapping, Region}; @@ -214,7 +214,7 @@ impl PFlash { backend: Option, ) -> Result<()> { let region_size = Self::flash_region_size(region_max_size, &backend, self.read_only)?; - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, region_base, region_size, "PflashRom") .with_context(|| "Failed to allocate system resource for PFlash.")?; let host_mmap = Arc::new(HostMemMapping::new( @@ -893,20 +893,15 @@ impl SysBusDevOps for PFlash { } } - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } - fn set_sys_resource( &mut self, _sysbus: &mut SysBus, region_base: u64, region_size: u64, + region_name: &str, ) -> Result<()> { - let res = self.get_sys_resource_mut().unwrap(); - res.region_base = region_base; - res.region_size = region_size; - res.irq = 0; + self.sysbusdev_base_mut() + .set_sys(0, region_base, region_size, region_name); Ok(()) } diff --git a/devices/src/legacy/pl011.rs b/devices/src/legacy/pl011.rs index 24a34ef..f5fdafa 100644 --- a/devices/src/legacy/pl011.rs +++ b/devices/src/legacy/pl011.rs @@ -13,11 +13,10 @@ use std::sync::{Arc, Mutex}; use anyhow::{Context, Result}; -use log::{debug, error}; -use vmm_sys_util::eventfd::EventFd; +use log::error; use super::error::LegacyError; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::{ AmlActiveLevel, AmlBuilder, AmlDevice, AmlEdgeLevel, AmlExtendedInterrupt, AmlIntShare, @@ -36,7 +35,7 @@ use migration::{ }; use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; -use util::loop_context::EventNotifierHelper; +use util::loop_context::{create_new_eventfd, EventNotifierHelper}; use util::num_ops::read_data_u32; const PL011_FLAG_TXFE: u8 = 0x80; @@ -135,7 +134,7 @@ impl PL011 { Ok(PL011 { base: SysBusDevBase { dev_type: SysBusDevType::PL011, - interrupt_evt: Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK)?)), + interrupt_evt: Some(Arc::new(create_new_eventfd()?)), ..Default::default() }, paused: false, @@ -166,12 +165,12 @@ impl PL011 { .unwrap() .realize() .with_context(|| "Failed to realize chardev")?; - self.set_sys_resource(sysbus, region_base, region_size) + self.set_sys_resource(sysbus, region_base, region_size, "PL011") .with_context(|| "Failed to set system resource for PL011.")?; let dev = Arc::new(Mutex::new(self)); sysbus - .attach_device(&dev, region_base, region_size, "PL011") + .attach_device(&dev) .with_context(|| "Failed to attach PL011 to system bus.")?; bs.lock().unwrap().kernel_cmdline.push(Param { @@ -353,20 +352,10 @@ impl SysBusDevOps for PL011 { match offset >> 2 { 0 => { let ch = value as u8; - - if let Some(output) = &mut self.chardev.lock().unwrap().output { - let mut locked_output = output.lock().unwrap(); - if let Err(e) = locked_output.write_all(&[ch]) { - debug!("Failed to write to pl011 output fd, error is {:?}", e); - } - if let Err(e) = locked_output.flush() { - debug!("Failed to flush pl011, error is {:?}", e); - } - } else { - debug!("Failed to get output fd"); + if let Err(e) = self.chardev.lock().unwrap().fill_outbuf(vec![ch], None) { + error!("Failed to append pl011 data to outbuf of chardev, {:?}", e); return false; } - self.state.int_level |= INT_TX; self.interrupt(); } @@ -425,10 +414,6 @@ impl SysBusDevOps for PL011 { true } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl StateTransfer for PL011 { @@ -486,8 +471,9 @@ mod test { #[test] fn test_receive() { let chardev_cfg = ChardevConfig { - id: "chardev".to_string(), - backend: ChardevType::Stdio, + classtype: ChardevType::Stdio { + id: "chardev".to_string(), + }, }; let mut pl011_dev = PL011::new(SerialConfig { chardev: chardev_cfg, diff --git a/devices/src/legacy/pl031.rs b/devices/src/legacy/pl031.rs index a5790dd..9ad650b 100644 --- a/devices/src/legacy/pl031.rs +++ b/devices/src/legacy/pl031.rs @@ -15,10 +15,9 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result}; use byteorder::{ByteOrder, LittleEndian}; -use vmm_sys_util::eventfd::EventFd; use super::error::LegacyError; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::AmlBuilder; use address_space::GuestAddress; @@ -28,6 +27,7 @@ use migration::{ }; use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; +use util::loop_context::create_new_eventfd; use util::num_ops::write_data_u32; /// Registers for pl031 from ARM PrimeCell Real Time Clock Technical Reference Manual. @@ -100,12 +100,12 @@ impl PL031 { region_base: u64, region_size: u64, ) -> Result<()> { - self.base.interrupt_evt = Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK)?)); - self.set_sys_resource(sysbus, region_base, region_size) + self.base.interrupt_evt = Some(Arc::new(create_new_eventfd()?)); + self.set_sys_resource(sysbus, region_base, region_size, "PL031") .with_context(|| LegacyError::SetSysResErr)?; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "PL031")?; + sysbus.attach_device(&dev)?; MigrationManager::register_device_instance( PL031State::descriptor(), @@ -198,10 +198,6 @@ impl SysBusDevOps for PL031 { true } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl AmlBuilder for PL031 { diff --git a/devices/src/legacy/ramfb.rs b/devices/src/legacy/ramfb.rs index 470fcd7..3945de4 100644 --- a/devices/src/legacy/ramfb.rs +++ b/devices/src/legacy/ramfb.rs @@ -16,6 +16,7 @@ use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use anyhow::{Context, Result}; +use clap::{ArgAction, Parser}; use drm_fourcc::DrmFourcc; use log::error; @@ -24,6 +25,7 @@ use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::AmlBuilder; use address_space::{AddressSpace, GuestAddress}; +use machine_manager::config::valid_id; use machine_manager::event_loop::EventLoop; use ui::console::{ console_init, display_graphic_update, display_replace_surface, ConsoleType, DisplayConsole, @@ -39,6 +41,17 @@ const INSTALL_CHECK_INTERVEL_MS: u64 = 500; const INSTALL_RELEASE_INTERVEL_MS: u64 = 200; const INSTALL_PRESS_INTERVEL_MS: u64 = 100; +#[derive(Parser, Debug, Clone)] +#[command(no_binary_name(true))] +pub struct RamfbConfig { + #[arg(long, value_parser = ["ramfb"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long, default_value = "false", action = ArgAction::Append)] + pub install: bool, +} + #[repr(packed)] struct RamfbCfg { _addr: u64, @@ -239,7 +252,7 @@ impl Ramfb { pub fn realize(self, sysbus: &mut SysBus) -> Result<()> { let dev = Arc::new(Mutex::new(self)); - sysbus.attach_dynamic_device(&dev)?; + sysbus.attach_device(&dev)?; Ok(()) } } @@ -317,3 +330,25 @@ fn set_press_event(install: Arc, data: *const u8) { install.store(false, Ordering::Release); } } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_ramfb_config_cmdline_parser() { + // Test1: install. + let ramfb_cmd1 = "ramfb,id=ramfb0,install=true"; + let ramfb_config = + RamfbConfig::try_parse_from(str_slip_to_clap(ramfb_cmd1, true, false)).unwrap(); + assert_eq!(ramfb_config.id, "ramfb0"); + assert_eq!(ramfb_config.install, true); + + // Test2: Default. + let ramfb_cmd2 = "ramfb,id=ramfb0"; + let ramfb_config = + RamfbConfig::try_parse_from(str_slip_to_clap(ramfb_cmd2, true, false)).unwrap(); + assert_eq!(ramfb_config.install, false); + } +} diff --git a/devices/src/legacy/rtc.rs b/devices/src/legacy/rtc.rs index 8334c2d..1e49808 100644 --- a/devices/src/legacy/rtc.rs +++ b/devices/src/legacy/rtc.rs @@ -15,7 +15,6 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH}; use anyhow::Result; use log::{debug, error, warn}; -use vmm_sys_util::eventfd::EventFd; use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; use crate::{Device, DeviceBase}; @@ -24,6 +23,7 @@ use acpi::{ AmlResTemplate, AmlScopeBuilder, }; use address_space::GuestAddress; +use util::loop_context::create_new_eventfd; use util::time::{mktime64, NANOSECONDS_PER_SECOND}; /// IO port of RTC device to select Register to read/write. @@ -124,9 +124,9 @@ impl RTC { res: SysRes { region_base: RTC_PORT_INDEX, region_size: 8, - irq: -1, + ..Default::default() }, - interrupt_evt: Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK)?)), + interrupt_evt: Some(Arc::new(create_new_eventfd()?)), ..Default::default() }, cmos_data: [0_u8; 128], @@ -269,10 +269,10 @@ impl RTC { pub fn realize(mut self, sysbus: &mut SysBus) -> Result<()> { let region_base = self.base.res.region_base; let region_size = self.base.res.region_size; - self.set_sys_resource(sysbus, region_base, region_size)?; + self.set_sys_resource(sysbus, region_base, region_size, "RTC")?; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "RTC")?; + sysbus.attach_device(&dev)?; Ok(()) } @@ -391,10 +391,6 @@ impl SysBusDevOps for RTC { } } - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } - fn reset(&mut self) -> Result<()> { self.cmos_data.fill(0); self.init_rtc_reg(); diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 7c5b907..8067bc2 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -15,10 +15,9 @@ use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; use log::{debug, error}; -use vmm_sys_util::eventfd::EventFd; use super::error::LegacyError; -use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use crate::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use crate::{Device, DeviceBase}; use acpi::{ AmlActiveLevel, AmlBuilder, AmlDevice, AmlEdgeLevel, AmlEisaId, AmlExtendedInterrupt, @@ -27,6 +26,8 @@ use acpi::{ }; use address_space::GuestAddress; use chardev_backend::chardev::{Chardev, InputReceiver}; +#[cfg(target_arch = "riscv64")] +use machine_manager::config::{BootSource, Param}; use machine_manager::{config::SerialConfig, event_loop::EventLoop}; use migration::{ snapshot::SERIAL_SNAPSHOT_ID, DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, @@ -34,7 +35,7 @@ use migration::{ }; use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; -use util::loop_context::EventNotifierHelper; +use util::loop_context::{create_new_eventfd, EventNotifierHelper}; pub const SERIAL_ADDR: u64 = 0x3f8; @@ -138,24 +139,30 @@ impl Serial { sysbus: &mut SysBus, region_base: u64, region_size: u64, + #[cfg(target_arch = "riscv64")] bs: &Arc>, ) -> Result<()> { self.chardev .lock() .unwrap() .realize() .with_context(|| "Failed to realize chardev")?; - self.base.interrupt_evt = Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK)?)); - self.set_sys_resource(sysbus, region_base, region_size) + self.base.interrupt_evt = Some(Arc::new(create_new_eventfd()?)); + self.set_sys_resource(sysbus, region_base, region_size, "Serial") .with_context(|| LegacyError::SetSysResErr)?; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "Serial")?; + sysbus.attach_device(&dev)?; MigrationManager::register_device_instance( SerialState::descriptor(), dev.clone(), SERIAL_SNAPSHOT_ID, ); + #[cfg(target_arch = "riscv64")] + bs.lock().unwrap().kernel_cmdline.push(Param { + param_type: "earlycon".to_string(), + value: format!("uart,mmio,0x{:08x}", region_base), + }); let locked_dev = dev.lock().unwrap(); locked_dev.chardev.lock().unwrap().set_receiver(&dev); EventLoop::update_event( @@ -297,19 +304,10 @@ impl Serial { self.rbr.push_back(data); self.state.lsr |= UART_LSR_DR; - } else { - let output = self.chardev.lock().unwrap().output.clone(); - if output.is_none() { - self.update_iir(); - bail!("serial: failed to get output fd."); - } - let mut locked_output = output.as_ref().unwrap().lock().unwrap(); - locked_output - .write_all(&[data]) - .with_context(|| "serial: failed to write.")?; - locked_output - .flush() - .with_context(|| "serial: failed to flush.")?; + } else if let Err(e) = + self.chardev.lock().unwrap().fill_outbuf(vec![data], None) + { + bail!("Failed to append data to output buffer of chardev, {:?}", e); } self.update_iir(); @@ -413,13 +411,10 @@ impl SysBusDevOps for Serial { } } + #[cfg(target_arch = "x86_64")] fn get_irq(&self, _sysbus: &mut SysBus) -> Result { Ok(UART_IRQ) } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl AmlBuilder for Serial { @@ -490,8 +485,9 @@ mod test { fn test_methods_of_serial() { // test new method let chardev_cfg = ChardevConfig { - id: "chardev".to_string(), - backend: ChardevType::Stdio, + classtype: ChardevType::Stdio { + id: "chardev".to_string(), + }, }; let mut usart = Serial::new(SerialConfig { chardev: chardev_cfg.clone(), @@ -545,8 +541,9 @@ mod test { #[test] fn test_serial_migration_interface() { let chardev_cfg = ChardevConfig { - id: "chardev".to_string(), - backend: ChardevType::Stdio, + classtype: ChardevType::Stdio { + id: "chardev".to_string(), + }, }; let mut usart = Serial::new(SerialConfig { chardev: chardev_cfg, diff --git a/devices/src/lib.rs b/devices/src/lib.rs index a155ff6..e6d43b3 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -28,6 +28,8 @@ pub mod smbios; pub mod sysbus; pub mod usb; +#[cfg(target_arch = "riscv64")] +pub use interrupt_controller::{AIAAccess, AIAConfig, AIADevice, InterruptController, AIA}; #[cfg(target_arch = "aarch64")] pub use interrupt_controller::{ GICDevice, GICVersion, GICv2, GICv2Access, GICv3, GICv3Access, GICv3ItsAccess, GICv3ItsState, @@ -39,6 +41,10 @@ pub use legacy::error::LegacyError as LegacyErrs; pub use scsi::bus as ScsiBus; pub use scsi::disk as ScsiDisk; +use std::any::Any; + +use util::AsAny; + #[derive(Clone, Default)] pub struct DeviceBase { /// Name of this device @@ -53,7 +59,7 @@ impl DeviceBase { } } -pub trait Device { +pub trait Device: Any + AsAny { fn device_base(&self) -> &DeviceBase; fn device_base_mut(&mut self) -> &mut DeviceBase; diff --git a/devices/src/misc/mod.rs b/devices/src/misc/mod.rs index 36c2d9c..0e0c015 100644 --- a/devices/src/misc/mod.rs +++ b/devices/src/misc/mod.rs @@ -14,7 +14,7 @@ pub mod scream; #[cfg(feature = "scream")] -mod ivshmem; +pub mod ivshmem; #[cfg(feature = "pvpanic")] pub mod pvpanic; diff --git a/devices/src/misc/pvpanic.rs b/devices/src/misc/pvpanic.rs index e2d29dd..38c2ecb 100644 --- a/devices/src/misc/pvpanic.rs +++ b/devices/src/misc/pvpanic.rs @@ -16,7 +16,9 @@ use std::sync::{ }; use anyhow::{bail, Context, Result}; +use clap::Parser; use log::{debug, error, info}; +use serde::{Deserialize, Serialize}; use crate::pci::{ config::{ @@ -29,17 +31,44 @@ use crate::pci::{ }; use crate::{Device, DeviceBase}; use address_space::{GuestAddress, Region, RegionOps}; -use machine_manager::config::{PvpanicDevConfig, PVPANIC_CRASHLOADED, PVPANIC_PANICKED}; +use machine_manager::config::{get_pci_df, valid_id}; const PVPANIC_PCI_REVISION_ID: u8 = 1; const PVPANIC_PCI_VENDOR_ID: u16 = PCI_VENDOR_ID_REDHAT_QUMRANET; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] // param size in Region::init_io_region must greater than 4 const PVPANIC_REG_BAR_SIZE: u64 = 0x4; #[cfg(target_arch = "x86_64")] const PVPANIC_REG_BAR_SIZE: u64 = 0x1; +pub const PVPANIC_PANICKED: u32 = 1 << 0; +pub const PVPANIC_CRASHLOADED: u32 = 1 << 1; + +#[derive(Parser, Debug, Clone, Serialize, Deserialize)] +#[command(no_binary_name(true))] +pub struct PvpanicDevConfig { + #[arg(long, value_parser = ["pvpanic"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: String, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + #[arg(long, alias = "supported-features", default_value = "3", value_parser = valid_supported_features)] + pub supported_features: u32, +} + +fn valid_supported_features(f: &str) -> Result { + let features = f.parse::()?; + let supported_features = match features & !(PVPANIC_PANICKED | PVPANIC_CRASHLOADED) { + 0 => features, + _ => bail!("Unsupported pvpanic device features {}", features), + }; + Ok(supported_features) +} + #[derive(Copy, Clone)] pub struct PvPanicState { supported_features: u32, @@ -244,6 +273,7 @@ impl PciDevOps for PvPanicPci { mod tests { use super::*; use crate::pci::{host::tests::create_pci_host, le_read_u16, PciHost}; + use machine_manager::config::str_slip_to_clap; fn init_pvpanic_dev(devfn: u8, supported_features: u32, dev_id: &str) -> Arc> { let pci_host = create_pci_host(); @@ -253,6 +283,9 @@ mod tests { let config = PvpanicDevConfig { id: dev_id.to_string(), supported_features, + classtype: "".to_string(), + bus: "pcie.0".to_string(), + addr: (3, 0), }; let pvpanic_dev = PvPanicPci::new(&config, devfn, root_bus.clone()); assert_eq!(pvpanic_dev.base.base.id, "pvpanic_test".to_string()); @@ -264,6 +297,24 @@ mod tests { pci_host } + #[test] + fn test_pvpanic_cmdline_parser() { + // Test1: Right. + let cmdline = "pvpanic,id=pvpanic0,bus=pcie.0,addr=0x7,supported-features=0"; + let result = PvpanicDevConfig::try_parse_from(str_slip_to_clap(cmdline, true, false)); + assert_eq!(result.unwrap().supported_features, 0); + + // Test2: Default value. + let cmdline = "pvpanic,id=pvpanic0,bus=pcie.0,addr=0x7"; + let result = PvpanicDevConfig::try_parse_from(str_slip_to_clap(cmdline, true, false)); + assert_eq!(result.unwrap().supported_features, 3); + + // Test3: Illegal value. + let cmdline = "pvpanic,id=pvpanic0,bus=pcie.0,addr=0x7,supported-features=4"; + let result = PvpanicDevConfig::try_parse_from(str_slip_to_clap(cmdline, true, false)); + assert!(result.is_err()); + } + #[test] fn test_pvpanic_attached() { let pci_host = init_pvpanic_dev(7, PVPANIC_PANICKED | PVPANIC_CRASHLOADED, "pvpanic_test"); diff --git a/devices/src/misc/scream/mod.rs b/devices/src/misc/scream/mod.rs index 85b73ee..496c96c 100644 --- a/devices/src/misc/scream/mod.rs +++ b/devices/src/misc/scream/mod.rs @@ -186,7 +186,7 @@ impl ShmemStreamFmt { } /// Audio stream data structure. -#[derive(Default)] +#[derive(Debug, Default)] pub struct StreamData { pub fmt: ShmemStreamFmt, chunk_idx: u16, @@ -295,8 +295,14 @@ impl StreamData { let header = &mut unsafe { std::slice::from_raw_parts_mut(hva as *mut ShmemHeader, 1) }[0]; let play = &header.play; - while play.fmt.fmt_generation == self.fmt.fmt_generation && self.chunk_idx != play.chunk_idx - { + loop { + if play.fmt.fmt_generation != self.fmt.fmt_generation { + break; + } + if self.chunk_idx == play.chunk_idx { + thread::sleep(time::Duration::from_micros(POLL_DELAY_US)); + continue; + } // If the difference between the currently processed chunk_idx and the chunk_idx in // the shared memory is greater than 4, the processing of the backend device is too // slow and the backward data is skipped. @@ -396,8 +402,10 @@ impl FromStr for ScreamInterface { } #[derive(Parser, Debug, Clone)] -#[command(name = "ivshmem_scream")] +#[command(no_binary_name(true))] pub struct ScreamConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] id: String, #[arg(long)] diff --git a/devices/src/misc/scream/ohaudio.rs b/devices/src/misc/scream/ohaudio.rs index 7029410..2349d93 100755 --- a/devices/src/misc/scream/ohaudio.rs +++ b/devices/src/misc/scream/ohaudio.rs @@ -12,12 +12,12 @@ use std::os::raw::c_void; use std::sync::{ - atomic::{fence, AtomicI32, Ordering}, + atomic::{fence, AtomicBool, AtomicI32, Ordering}, Arc, Mutex, }; use std::{cmp, ptr, thread, time}; -use log::{error, warn}; +use log::error; use crate::misc::scream::{AudioInterface, ScreamDirection, ShmemStreamHeader, StreamData}; use util::ohos_binding::audio::*; @@ -35,13 +35,17 @@ struct StreamUnit { pub len: u64, } -const STREAM_DATA_VEC_CAPACITY: usize = 30; +const STREAM_DATA_VEC_CAPACITY: usize = 15; +const FLUSH_DELAY_THRESHOLD_MS: u64 = 100; +const FLUSH_DELAY_MS: u64 = 5; +const FLUSH_DELAY_CNT: u64 = 200; struct OhAudioRender { ctx: Option, stream_data: Arc>>, - prepared_data: u32, + data_size: AtomicI32, start: bool, + flushing: AtomicBool, } impl Default for OhAudioRender { @@ -49,23 +53,14 @@ impl Default for OhAudioRender { OhAudioRender { ctx: None, stream_data: Arc::new(Mutex::new(Vec::with_capacity(STREAM_DATA_VEC_CAPACITY))), - prepared_data: 0, + data_size: AtomicI32::new(0), start: false, + flushing: AtomicBool::new(false), } } } impl OhAudioRender { - #[inline(always)] - fn check_data_ready(&self, recv_data: &StreamData) -> bool { - let size = recv_data.fmt.size as u32 / 8; - let channels = recv_data.fmt.channels as u32; - let rate = recv_data.fmt.get_rate(); - // Wait for data of 500 ms ready. - // FIXME: the value of rate is wrong. - self.prepared_data >= (size * channels * rate / 2) - } - fn check_fmt_update(&mut self, recv_data: &StreamData) { if self.ctx.is_some() && !self.ctx.as_ref().unwrap().check_fmt( @@ -77,46 +72,64 @@ impl OhAudioRender { self.destroy(); } } + + fn flush(&mut self) { + self.flushing.store(true, Ordering::Release); + let mut cnt = 0; + while (cnt < FLUSH_DELAY_CNT) && (self.flushing.load(Ordering::Acquire)) { + thread::sleep(time::Duration::from_millis(FLUSH_DELAY_MS)); + cnt += 1; + } + // We need to wait for 100ms to ensure the audio data has + // been flushed before stop renderer. + thread::sleep(time::Duration::from_millis(FLUSH_DELAY_THRESHOLD_MS)); + let _ = self.ctx.as_ref().unwrap().flush_renderer(); + } } impl OhAudioProcess for OhAudioRender { fn init(&mut self, stream: &StreamData) -> bool { - let mut context = AudioContext::new(AudioStreamType::Render); - match context.init( - stream.fmt.size, - stream.fmt.get_rate(), - stream.fmt.channels, - AudioProcessCb::RendererCb(Some(on_write_data_cb)), - ptr::addr_of!(*self) as *mut c_void, - ) { - Ok(()) => self.ctx = Some(context), - Err(e) => { - error!("failed to create oh audio render context: {}", e); - return false; + if self.ctx.is_none() { + let mut context = AudioContext::new(AudioStreamType::Render); + match context.init( + stream.fmt.size, + stream.fmt.get_rate(), + stream.fmt.channels, + AudioProcessCb::RendererCb(Some(on_write_data_cb)), + ptr::addr_of!(*self) as *mut c_void, + ) { + Ok(()) => self.ctx = Some(context), + Err(e) => { + error!("failed to create oh audio render context: {}", e); + return false; + } } } match self.ctx.as_ref().unwrap().start() { Ok(()) => { self.start = true; - true + trace::oh_scream_render_init(&self.ctx); } Err(e) => { error!("failed to start oh audio renderer: {}", e); - false } } + self.start } fn destroy(&mut self) { if self.ctx.is_some() { if self.start { + self.flush(); self.ctx.as_mut().unwrap().stop(); self.start = false; } self.ctx = None; } - self.stream_data.lock().unwrap().clear(); - self.prepared_data = 0; + let mut locked_data = self.stream_data.lock().unwrap(); + locked_data.clear(); + self.data_size.store(0, Ordering::Relaxed); + trace::oh_scream_render_destroy(); } fn process(&mut self, recv_data: &StreamData) -> i32 { @@ -124,18 +137,30 @@ impl OhAudioProcess for OhAudioRender { fence(Ordering::Acquire); + trace::trace_scope_start!(ohaudio_render_process, args = (recv_data)); + let su = StreamUnit { addr: recv_data.audio_base, len: recv_data.audio_size as u64, }; - self.stream_data.lock().unwrap().push(su); + let mut locked_data = self.stream_data.lock().unwrap(); + // When audio data is not consumed in time, we remove old chunk + // and push new one. So audio-playing won't delay. One chunk means + // 20 ms data. + if locked_data.len() >= STREAM_DATA_VEC_CAPACITY { + let remove_size = locked_data[0].len; + locked_data.remove(0); + self.data_size + .fetch_sub(remove_size as i32, Ordering::Relaxed); + } + locked_data.push(su); + self.data_size + .fetch_add(recv_data.audio_size as i32, Ordering::Relaxed); + drop(locked_data); - if !self.start { - self.prepared_data += recv_data.audio_size; - if self.check_data_ready(recv_data) && !self.init(recv_data) { - error!("failed to init oh audio"); - self.destroy(); - } + if !self.start && !self.init(recv_data) { + error!("failed to init oh audio"); + self.destroy(); } 0 } @@ -185,6 +210,7 @@ impl OhAudioProcess for OhAudioCapture { match self.ctx.as_ref().unwrap().start() { Ok(()) => { self.start = true; + trace::oh_scream_capture_init(&self.ctx); true } Err(e) => { @@ -197,11 +223,12 @@ impl OhAudioProcess for OhAudioCapture { fn destroy(&mut self) { if self.ctx.is_some() { if self.start { - self.ctx.as_mut().unwrap().stop(); self.start = false; + self.ctx.as_mut().unwrap().stop(); } self.ctx = None; } + trace::oh_scream_capture_destroy(); } fn preprocess(&mut self, start_addr: u64, sh_header: &ShmemStreamHeader) { @@ -214,6 +241,9 @@ impl OhAudioProcess for OhAudioCapture { fn process(&mut self, recv_data: &StreamData) -> i32 { self.check_fmt_update(recv_data); + + trace::trace_scope_start!(ohaudio_capturer_process, args = (recv_data)); + if !self.start && !self.init(recv_data) { self.destroy(); return 0; @@ -239,7 +269,13 @@ extern "C" fn on_write_data_cb( .as_mut() .unwrap_unchecked() }; - if !render.start { + let data_size = render.data_size.load(Ordering::Relaxed); + + trace::trace_scope_start!(ohaudio_write_cb, args = (length, data_size)); + + if !render.flushing.load(Ordering::Acquire) && data_size < length { + // SAFETY: we checked len. + unsafe { ptr::write_bytes(buffer as *mut u8, 0, length as usize) }; return 0; } @@ -255,6 +291,7 @@ extern "C" fn on_write_data_cb( unsafe { ptr::copy_nonoverlapping(su.addr as *const u8, dst_addr as *mut u8, len as usize) }; + trace::oh_scream_on_write_data_cb(len as usize); dst_addr += len; left -= len; @@ -265,8 +302,16 @@ extern "C" fn on_write_data_cb( su.addr += len; } } + render + .data_size + .fetch_sub(length - left as i32, Ordering::Relaxed); + if left > 0 { - warn!("data in stream unit list is not enough"); + // SAFETY: we checked len. + unsafe { ptr::write_bytes(dst_addr as *mut u8, 0, left as usize) }; + } + if render.flushing.load(Ordering::Acquire) && su_list.is_empty() { + render.flushing.store(false, Ordering::Release); } 0 } @@ -284,6 +329,8 @@ extern "C" fn on_read_data_cb( .unwrap_unchecked() }; + trace::trace_scope_start!(ohaudio_read_cb, args = (length)); + loop { if !capture.start { return 0; @@ -306,6 +353,7 @@ extern "C" fn on_read_data_cb( len as usize, ) }; + trace::oh_scream_on_read_data_cb(len as usize); left -= len; src_addr += len; capture.cur_pos += len; diff --git a/devices/src/pci/bus.rs b/devices/src/pci/bus.rs index b0f5dc8..7974666 100644 --- a/devices/src/pci/bus.rs +++ b/devices/src/pci/bus.rs @@ -94,7 +94,7 @@ impl PciBus { /// # Arguments /// /// * `bus_num` - The bus number. - /// * `devfn` - Slot number << 8 | device number. + /// * `devfn` - Slot number << 3 | Function number. pub fn get_device(&self, bus_num: u8, devfn: u8) -> Option>> { if let Some(dev) = self.devices.get(&devfn) { return Some((*dev).clone()); @@ -275,7 +275,7 @@ mod tests { use crate::pci::bus::PciBus; use crate::pci::config::{PciConfig, PCI_CONFIG_SPACE_SIZE}; use crate::pci::root_port::RootPort; - use crate::pci::{PciDevBase, PciHost}; + use crate::pci::{PciDevBase, PciHost, RootPortConfig}; use crate::{Device, DeviceBase}; use address_space::{AddressSpace, Region}; @@ -372,8 +372,12 @@ mod tests { let pci_host = create_pci_host(); let locked_pci_host = pci_host.lock().unwrap(); let root_bus = Arc::downgrade(&locked_pci_host.root_bus); - - let root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus.clone(), false); + let root_port_config = RootPortConfig { + addr: (1, 0), + id: "pcie.1".to_string(), + ..Default::default() + }; + let root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.realize().unwrap(); // Test device is attached to the root bus. @@ -421,7 +425,12 @@ mod tests { let locked_pci_host = pci_host.lock().unwrap(); let root_bus = Arc::downgrade(&locked_pci_host.root_bus); - let root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus.clone(), false); + let root_port_config = RootPortConfig { + id: "pcie.1".to_string(), + addr: (1, 0), + ..Default::default() + }; + let root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.realize().unwrap(); let bus = PciBus::find_bus_by_name(&locked_pci_host.root_bus, "pcie.1").unwrap(); diff --git a/devices/src/pci/config.rs b/devices/src/pci/config.rs index 1e66456..16c9b6a 100644 --- a/devices/src/pci/config.rs +++ b/devices/src/pci/config.rs @@ -1020,7 +1020,7 @@ impl PciConfig { /// /// # Arguments /// - /// * `devfn` - Slot number << 3 | function number. + /// * `devfn` - Slot number << 3 | Function number. /// * `port_num` - Port number. /// * `dev_type` - Device type. pub fn add_pcie_cap(&mut self, devfn: u8, port_num: u8, dev_type: u8) -> Result { diff --git a/devices/src/pci/demo_device/mod.rs b/devices/src/pci/demo_device/mod.rs index 7789536..ff35f47 100644 --- a/devices/src/pci/demo_device/mod.rs +++ b/devices/src/pci/demo_device/mod.rs @@ -42,6 +42,7 @@ use std::{ }; use anyhow::{bail, Result}; +use clap::Parser; use log::error; use crate::pci::demo_device::{ @@ -57,7 +58,30 @@ use crate::pci::{ use crate::pci::{demo_device::base_device::BaseDevice, PciDevBase}; use crate::{Device, DeviceBase}; use address_space::{AddressSpace, GuestAddress, Region, RegionOps}; -use machine_manager::config::DemoDevConfig; +use machine_manager::config::{get_pci_df, valid_id}; + +/// Config struct for `demo_dev`. +/// Contains demo_dev device's attr. +#[derive(Parser, Debug, Clone)] +#[command(no_binary_name(true))] +pub struct DemoDevConfig { + #[arg(long, value_parser = ["pcie-demo-dev"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: String, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + // Different device implementations can be configured based on this parameter + #[arg(long, alias = "device_type")] + pub device_type: Option, + #[arg(long, alias = "bar_num", default_value = "0")] + pub bar_num: u8, + // Every bar has the same size just for simplification. + #[arg(long, alias = "bar_size", default_value = "0")] + pub bar_size: u64, +} pub struct DemoDev { base: PciDevBase, @@ -71,14 +95,15 @@ impl DemoDev { pub fn new( cfg: DemoDevConfig, devfn: u8, - _sys_mem: Arc, + sys_mem: Arc, parent_bus: Weak>, ) -> Self { // You can choose different device function based on the parameter of device_type. - let device: Arc> = match cfg.device_type.as_str() { - "demo-gpu" => Arc::new(Mutex::new(DemoGpu::new(_sys_mem, cfg.id.clone()))), - "demo-input" => Arc::new(Mutex::new(DemoKbdMouse::new(_sys_mem))), - "demo-display" => Arc::new(Mutex::new(DemoDisplay::new(_sys_mem))), + let device_type = cfg.device_type.clone().unwrap_or_default(); + let device: Arc> = match device_type.as_str() { + "demo-gpu" => Arc::new(Mutex::new(DemoGpu::new(sys_mem, cfg.id.clone()))), + "demo-input" => Arc::new(Mutex::new(DemoKbdMouse::new(sys_mem))), + "demo-display" => Arc::new(Mutex::new(DemoDisplay::new(sys_mem))), _ => Arc::new(Mutex::new(BaseDevice::new())), }; DemoDev { @@ -234,3 +259,29 @@ pub trait DeviceTypeOperation: Send { fn realize(&mut self) -> Result<()>; fn unrealize(&mut self) -> Result<()>; } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + #[test] + fn test_parse_demo_dev() { + // Test1: Right. + let demo_cmd1 = "pcie-demo-dev,bus=pcie.0,addr=0x4,id=test_0,device_type=demo-gpu,bar_num=3,bar_size=4096"; + let result = DemoDevConfig::try_parse_from(str_slip_to_clap(demo_cmd1, true, false)); + assert!(result.is_ok()); + let demo_cfg = result.unwrap(); + assert_eq!(demo_cfg.id, "test_0".to_string()); + assert_eq!(demo_cfg.device_type, Some("demo-gpu".to_string())); + assert_eq!(demo_cfg.bar_num, 3); + assert_eq!(demo_cfg.bar_size, 4096); + + // Test2: Default bar_num/bar_size. + let demo_cmd2 = "pcie-demo-dev,bus=pcie.0,addr=4.0,id=test_0,device_type=demo-gpu"; + let result = DemoDevConfig::try_parse_from(str_slip_to_clap(demo_cmd2, true, false)); + assert!(result.is_ok()); + let demo_cfg = result.unwrap(); + assert_eq!(demo_cfg.bar_num, 0); + assert_eq!(demo_cfg.bar_size, 0); + } +} diff --git a/devices/src/pci/host.rs b/devices/src/pci/host.rs index b6a44da..c144723 100644 --- a/devices/src/pci/host.rs +++ b/devices/src/pci/host.rs @@ -327,7 +327,7 @@ fn build_osc_for_aml(pci_host_bridge: &mut AmlDevice) { pci_host_bridge.append_child(method); } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn build_osc_for_aml(pci_host_bridge: &mut AmlDevice) { // _OSC means Operating System Capabilities. pci_host_bridge.append_child(AmlNameDecl::new("SUPP", AmlInteger(0))); @@ -412,6 +412,8 @@ fn build_prt_for_aml(pci_bus: &mut AmlDevice, irq: i32) { let irqs = irq as u8 + i; #[cfg(target_arch = "aarch64")] let irqs = irq as u8 + PCI_INTR_BASE + i; + #[cfg(target_arch = "riscv64")] + let irqs = irq as u8 + i; let mut gsi = AmlDevice::new(format!("GSI{}", i).as_str()); gsi.append_child(AmlNameDecl::new("_HID", AmlEisaId::new("PNP0C0F"))); gsi.append_child(AmlNameDecl::new("_UID", AmlString(i.to_string()))); @@ -547,7 +549,7 @@ pub mod tests { use super::*; use crate::pci::bus::PciBus; use crate::pci::config::{PciConfig, PCI_CONFIG_SPACE_SIZE, SECONDARY_BUS_NUM}; - use crate::pci::root_port::RootPort; + use crate::pci::root_port::{RootPort, RootPortConfig}; use crate::pci::{PciDevBase, Result}; use crate::{Device, DeviceBase}; use address_space::Region; @@ -658,7 +660,12 @@ pub mod tests { let root_bus = Arc::downgrade(&pci_host.lock().unwrap().root_bus); let pio_addr_ops = PciHost::build_pio_addr_ops(pci_host.clone()); let pio_data_ops = PciHost::build_pio_data_ops(pci_host.clone()); - let root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus, false); + let root_port_config = RootPortConfig { + addr: (1, 0), + id: "pcie.1".to_string(), + ..Default::default() + }; + let root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.realize().unwrap(); let mut data = [0_u8; 4]; @@ -735,10 +742,20 @@ pub mod tests { let root_bus = Arc::downgrade(&pci_host.lock().unwrap().root_bus); let mmconfig_region_ops = PciHost::build_mmconfig_ops(pci_host.clone()); - let mut root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus.clone(), false); + let root_port_config = RootPortConfig { + addr: (1, 0), + id: "pcie.1".to_string(), + ..Default::default() + }; + let mut root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.write_config(SECONDARY_BUS_NUM as usize, &[1]); root_port.realize().unwrap(); - let mut root_port = RootPort::new("pcie.2".to_string(), 16, 0, root_bus, false); + let root_port_config = RootPortConfig { + addr: (2, 0), + id: "pcie.2".to_string(), + ..Default::default() + }; + let mut root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.write_config(SECONDARY_BUS_NUM as usize, &[2]); root_port.realize().unwrap(); diff --git a/devices/src/pci/intx.rs b/devices/src/pci/intx.rs index bf53fa5..b0624fe 100644 --- a/devices/src/pci/intx.rs +++ b/devices/src/pci/intx.rs @@ -16,8 +16,7 @@ use anyhow::Result; use log::error; use crate::interrupt_controller::LineIrqManager; -use crate::pci::{swizzle_map_irq, PciBus, PciConfig, INTERRUPT_PIN, PCI_INTR_BASE, PCI_PIN_NUM}; -use util::test_helper::{is_test_enabled, trigger_intx}; +use crate::pci::{swizzle_map_irq, PciBus, PciConfig, INTERRUPT_PIN, PCI_PIN_NUM}; pub type InterruptHandler = Box Result<()> + Send + Sync>; @@ -96,11 +95,6 @@ impl Intx { let irq = locked_intx_state.gsi_base + self.irq_pin; let level = locked_intx_state.irq_count[self.irq_pin as usize] != 0; - if is_test_enabled() { - trigger_intx(irq + PCI_INTR_BASE as u32, change); - return; - } - let irq_handler = &locked_intx_state.irq_handler; if let Err(e) = irq_handler.set_level_irq(irq, level) { error!( diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs index 4a8d942..7a90d6e 100644 --- a/devices/src/pci/mod.rs +++ b/devices/src/pci/mod.rs @@ -28,22 +28,25 @@ pub use error::PciError; pub use host::PciHost; pub use intx::{init_intx, InterruptHandler, PciIntxState}; pub use msix::{init_msix, MsiVector}; -pub use root_port::RootPort; +pub use root_port::{RootPort, RootPortConfig}; -use std::{ - mem::size_of, - sync::{Arc, Mutex, Weak}, -}; +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::mem::size_of; +use std::sync::{Arc, Mutex, Weak}; use anyhow::{bail, Result}; use byteorder::{ByteOrder, LittleEndian}; -use crate::{ - pci::config::{HEADER_TYPE, HEADER_TYPE_MULTIFUNC, MAX_FUNC}, - MsiIrqManager, -}; -use crate::{Device, DeviceBase}; -use util::AsAny; +#[cfg(feature = "scream")] +use crate::misc::ivshmem::Ivshmem; +#[cfg(feature = "pvpanic")] +use crate::misc::pvpanic::PvPanicPci; +use crate::pci::config::{HEADER_TYPE, HEADER_TYPE_MULTIFUNC, MAX_FUNC}; +use crate::usb::xhci::xhci_pci::XhciPciDevice; +use crate::{Device, DeviceBase, MsiIrqManager}; +#[cfg(feature = "demo_device")] +use demo_device::DemoDev; const BDF_FUNC_SHIFT: u8 = 3; pub const PCI_SLOT_MAX: u8 = 32; @@ -142,7 +145,7 @@ pub struct PciDevBase { pub parent_bus: Weak>, } -pub trait PciDevOps: Device + Send + AsAny { +pub trait PciDevOps: Device + Send { /// Get base property of pci device. fn pci_base(&self) -> &PciDevBase; @@ -200,7 +203,7 @@ pub trait PciDevOps: Device + Send + AsAny { /// # Arguments /// /// * `bus_num` - Bus number. - /// * `devfn` - Slot number << 8 | Function number. + /// * `devfn` - Slot number << 3 | Function number. /// /// # Returns /// @@ -270,6 +273,57 @@ pub trait PciDevOps: Device + Send + AsAny { } } +pub type ToPciDevOpsFunc = fn(&dyn Any) -> &dyn PciDevOps; + +static mut PCIDEVOPS_HASHMAP: Option> = None; + +pub fn convert_to_pcidevops(item: &dyn Any) -> &dyn PciDevOps { + // SAFETY: The typeid of `T` is the typeid recorded in the hashmap. The target structure type of + // the conversion is its own structure type, so the conversion result will definitely not be `None`. + let t = item.downcast_ref::().unwrap(); + t as &dyn PciDevOps +} + +pub fn register_pcidevops_type() -> Result<()> { + let type_id = TypeId::of::(); + // SAFETY: PCIDEVOPS_HASHMAP will be built in `type_init` function sequentially in the main thread. + // And will not be changed after `type_init`. + unsafe { + if PCIDEVOPS_HASHMAP.is_none() { + PCIDEVOPS_HASHMAP = Some(HashMap::new()); + } + let types = PCIDEVOPS_HASHMAP.as_mut().unwrap(); + if types.get(&type_id).is_some() { + bail!("Type Id {:?} has been registered.", type_id); + } + types.insert(type_id, convert_to_pcidevops::); + } + + Ok(()) +} + +pub fn devices_register_pcidevops_type() -> Result<()> { + #[cfg(feature = "scream")] + register_pcidevops_type::()?; + #[cfg(feature = "pvpanic")] + register_pcidevops_type::()?; + register_pcidevops_type::()?; + #[cfg(feature = "demo_device")] + register_pcidevops_type::()?; + register_pcidevops_type::() +} + +pub fn to_pcidevops(dev: &dyn Device) -> Option<&dyn PciDevOps> { + let type_id = dev.type_id(); + // SAFETY: PCIDEVOPS_HASHMAP has been built. And this function is called without changing hashmap. + unsafe { + let types = PCIDEVOPS_HASHMAP.as_mut().unwrap(); + let func = types.get(&type_id)?; + let pcidev = func(dev.as_any()); + Some(pcidev) + } +} + /// Init multifunction for pci devices. /// /// # Arguments @@ -349,11 +403,10 @@ pub fn swizzle_map_irq(devfn: u8, pin: u8) -> u32 { #[cfg(test)] mod tests { + use super::*; use crate::DeviceBase; use address_space::{AddressSpace, Region}; - use super::*; - #[test] fn test_le_write_u16_01() { let mut buf: [u8; 2] = [0; 2]; diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs index 1d1e30b..451a663 100644 --- a/devices/src/pci/msix.rs +++ b/devices/src/pci/msix.rs @@ -32,7 +32,6 @@ use migration_derive::{ByteCode, Desc}; use util::{ byte_code::ByteCode, num_ops::{ranges_overlap, round_up}, - test_helper::{add_msix_msg, is_test_enabled}, }; pub const MSIX_TABLE_ENTRY_SIZE: u16 = 16; @@ -401,14 +400,6 @@ impl Msix { pub fn send_msix(&self, vector: u16, dev_id: u16) { let msg = self.get_message(vector); - if is_test_enabled() { - let data = msg.data; - let mut addr: u64 = msg.address_hi as u64; - addr = (addr << 32) + msg.address_lo as u64; - add_msix_msg(addr, data); - return; - } - let msix_vector = MsiVector { msg_addr_lo: msg.address_lo, msg_addr_hi: msg.address_hi, diff --git a/devices/src/pci/root_port.rs b/devices/src/pci/root_port.rs index 4013027..6396c0c 100644 --- a/devices/src/pci/root_port.rs +++ b/devices/src/pci/root_port.rs @@ -14,6 +14,7 @@ use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::{Arc, Mutex, Weak}; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser}; use log::{error, info}; use once_cell::sync::OnceCell; @@ -39,17 +40,41 @@ use crate::pci::{ }; use crate::{Device, DeviceBase, MsiIrqManager}; use address_space::Region; +use machine_manager::config::{get_pci_df, parse_bool, valid_id}; use machine_manager::qmp::qmp_channel::send_device_deleted_msg; use migration::{ DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, MigrationManager, StateTransfer, }; use migration_derive::{ByteCode, Desc}; -use util::{byte_code::ByteCode, num_ops::ranges_overlap}; +use util::{ + byte_code::ByteCode, + num_ops::{ranges_overlap, str_to_num}, +}; const DEVICE_ID_RP: u16 = 0x000c; static FAST_UNPLUG_FEATURE: OnceCell = OnceCell::new(); +/// Basic information of RootPort like port number. +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct RootPortConfig { + #[arg(long, value_parser = ["pcie-root-port"])] + pub classtype: String, + #[arg(long, value_parser = str_to_num::)] + pub port: u8, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: String, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + #[arg(long, default_value = "off", value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: bool, + #[arg(long, default_value = "0")] + pub chassis: u8, +} + /// Device state root port. #[repr(C)] #[derive(Copy, Clone, Desc, ByteCode)] @@ -81,22 +106,15 @@ impl RootPort { /// /// # Arguments /// - /// * `name` - Root port name. - /// * `devfn` - Device number << 3 | Function number. - /// * `port_num` - Root port number. + /// * `cfg` - Root port config. /// * `parent_bus` - Weak reference to the parent bus. - pub fn new( - name: String, - devfn: u8, - port_num: u8, - parent_bus: Weak>, - multifunction: bool, - ) -> Self { + pub fn new(cfg: RootPortConfig, parent_bus: Weak>) -> Self { + let devfn = cfg.addr.0 << 3 | cfg.addr.1; #[cfg(target_arch = "x86_64")] let io_region = Region::init_container_region(1 << 16, "RootPortIo"); let mem_region = Region::init_container_region(u64::max_value(), "RootPortMem"); let sec_bus = Arc::new(Mutex::new(PciBus::new( - name.clone(), + cfg.id.clone(), #[cfg(target_arch = "x86_64")] io_region.clone(), mem_region.clone(), @@ -104,18 +122,18 @@ impl RootPort { Self { base: PciDevBase { - base: DeviceBase::new(name, true), + base: DeviceBase::new(cfg.id, true), config: PciConfig::new(PCIE_CONFIG_SPACE_SIZE, 2), devfn, parent_bus, }, - port_num, + port_num: cfg.port, sec_bus, #[cfg(target_arch = "x86_64")] io_region, mem_region, dev_id: Arc::new(AtomicU16::new(0)), - multifunction, + multifunction: cfg.multifunction, hpev_notified: false, } } @@ -694,7 +712,12 @@ mod tests { fn test_read_config() { let pci_host = create_pci_host(); let root_bus = Arc::downgrade(&pci_host.lock().unwrap().root_bus); - let root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus, false); + let root_port_config = RootPortConfig { + addr: (1, 0), + id: "pcie.1".to_string(), + ..Default::default() + }; + let root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.realize().unwrap(); let root_port = pci_host.lock().unwrap().find_device(0, 8).unwrap(); @@ -710,7 +733,12 @@ mod tests { fn test_write_config() { let pci_host = create_pci_host(); let root_bus = Arc::downgrade(&pci_host.lock().unwrap().root_bus); - let root_port = RootPort::new("pcie.1".to_string(), 8, 0, root_bus, false); + let root_port_config = RootPortConfig { + addr: (1, 0), + id: "pcie.1".to_string(), + ..Default::default() + }; + let root_port = RootPort::new(root_port_config, root_bus.clone()); root_port.realize().unwrap(); let root_port = pci_host.lock().unwrap().find_device(0, 8).unwrap(); diff --git a/devices/src/scsi/bus.rs b/devices/src/scsi/bus.rs index 9546743..0b018c6 100644 --- a/devices/src/scsi/bus.rs +++ b/devices/src/scsi/bus.rs @@ -666,7 +666,7 @@ impl ScsiRequest { let mut not_supported_flag = false; let mut sense = None; let mut status = GOOD; - let found_lun = self.dev.lock().unwrap().config.lun; + let found_lun = self.dev.lock().unwrap().dev_cfg.lun; // Requested lun id is not equal to found device id means it may be a target request. // REPORT LUNS is also a target request command. @@ -766,7 +766,7 @@ fn scsi_cdb_length(cdb: &[u8; SCSI_CMD_BUF_SIZE]) -> i32 { } } -fn scsi_cdb_xfer(cdb: &[u8; SCSI_CMD_BUF_SIZE], dev: Arc>) -> i32 { +pub fn scsi_cdb_xfer(cdb: &[u8; SCSI_CMD_BUF_SIZE], dev: Arc>) -> i32 { let dev_lock = dev.lock().unwrap(); let block_size = dev_lock.block_size as i32; drop(dev_lock); @@ -824,7 +824,7 @@ fn scsi_cdb_lba(cdb: &[u8; SCSI_CMD_BUF_SIZE]) -> i64 { } } -fn scsi_cdb_xfer_mode(cdb: &[u8; SCSI_CMD_BUF_SIZE]) -> ScsiXferMode { +pub fn scsi_cdb_xfer_mode(cdb: &[u8; SCSI_CMD_BUF_SIZE]) -> ScsiXferMode { match cdb[0] { WRITE_6 | WRITE_10 @@ -1174,7 +1174,7 @@ fn scsi_command_emulate_mode_sense( if dev_lock.state.features & (1 << SCSI_DISK_F_DPOFUA) != 0 { dev_specific_parameter = 0x10; } - if dev_lock.config.read_only { + if dev_lock.drive_cfg.readonly { // Readonly. dev_specific_parameter |= 0x80; } @@ -1362,7 +1362,7 @@ fn scsi_command_emulate_report_luns( let dev_lock = dev.lock().unwrap(); // Byte 0-3: Lun List Length. Byte 4-7: Reserved. let mut outbuf: Vec = vec![0; 8]; - let target = dev_lock.config.target; + let target = dev_lock.dev_cfg.target; if cmd.xfer < 16 { bail!("scsi REPORT LUNS xfer {} too short!", cmd.xfer); @@ -1383,17 +1383,17 @@ fn scsi_command_emulate_report_luns( for (_pos, device) in scsi_bus_clone.devices.iter() { let device_lock = device.lock().unwrap(); - if device_lock.config.target != target { + if device_lock.dev_cfg.target != target { drop(device_lock); continue; } let len = outbuf.len(); - if device_lock.config.lun < 256 { + if device_lock.dev_cfg.lun < 256 { outbuf.push(0); - outbuf.push(device_lock.config.lun as u8); + outbuf.push(device_lock.dev_cfg.lun as u8); } else { - outbuf.push(0x40 | ((device_lock.config.lun >> 8) & 0xff) as u8); - outbuf.push((device_lock.config.lun & 0xff) as u8); + outbuf.push(0x40 | ((device_lock.dev_cfg.lun >> 8) & 0xff) as u8); + outbuf.push((device_lock.dev_cfg.lun & 0xff) as u8); } outbuf.resize(len + 8, 0); drop(device_lock); diff --git a/devices/src/scsi/disk.rs b/devices/src/scsi/disk.rs index 6c4652e..37aca3d 100644 --- a/devices/src/scsi/disk.rs +++ b/devices/src/scsi/disk.rs @@ -14,11 +14,12 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex, Weak}; use anyhow::{bail, Result}; +use clap::Parser; use crate::ScsiBus::{aio_complete_cb, ScsiBus, ScsiCompleteCb}; use crate::{Device, DeviceBase}; use block_backend::{create_block_backend, BlockDriverOps, BlockProperty}; -use machine_manager::config::{DriveFile, ScsiDevConfig, VmConfig}; +use machine_manager::config::{valid_id, DriveConfig, DriveFile, VmConfig}; use machine_manager::event_loop::EventLoop; use util::aio::{Aio, AioEngine, WriteZeroesState}; @@ -57,6 +58,49 @@ pub const SCSI_DISK_DEFAULT_BLOCK_SIZE: u32 = 1 << SCSI_DISK_DEFAULT_BLOCK_SIZE_ pub const SCSI_CDROM_DEFAULT_BLOCK_SIZE_SHIFT: u32 = 11; pub const SCSI_CDROM_DEFAULT_BLOCK_SIZE: u32 = 1 << SCSI_CDROM_DEFAULT_BLOCK_SIZE_SHIFT; +// Stratovirt uses scsi mod in only virtio-scsi and usb-storage. Scsi's channel/target/lun +// of usb-storage are both 0. Scsi's channel/target/lun of virtio-scsi is no more than 0/255/16383. +// Set valid range of channel/target according to the range of virtio-scsi as 0/255. +// +// For stratovirt doesn't support `Flat space addressing format`(14 bits for lun) and only supports +// `peripheral device addressing format`(8 bits for lun) now, lun should be less than 255(2^8 - 1) temporarily. +const SCSI_MAX_CHANNEL: i64 = 0; +const SCSI_MAX_TARGET: i64 = 255; +const SUPPORT_SCSI_MAX_LUN: i64 = 255; + +#[derive(Parser, Clone, Debug, Default)] +#[command(no_binary_name(true))] +pub struct ScsiDevConfig { + #[arg(long, value_parser = ["scsi-cd", "scsi-hd"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long, value_parser = valid_scsi_bus)] + pub bus: String, + /// Scsi four level hierarchical address(host, channel, target, lun). + #[arg(long, default_value = "0", value_parser = clap::value_parser!(u8).range(..=SCSI_MAX_CHANNEL))] + pub channel: u8, + #[arg(long, alias = "scsi-id", value_parser = clap::value_parser!(u8).range(..=SCSI_MAX_TARGET))] + pub target: u8, + #[arg(long, value_parser = clap::value_parser!(u16).range(..=SUPPORT_SCSI_MAX_LUN))] + pub lun: u16, + #[arg(long)] + pub drive: String, + #[arg(long)] + pub serial: Option, + #[arg(long)] + pub bootindex: Option, +} + +// Scsi device should has bus named as "$parent_cntlr_name.0". +fn valid_scsi_bus(bus: &str) -> Result { + let strs = bus.split('.').collect::>(); + if strs.len() != 2 || strs[1] != "0" { + bail!("Invalid scsi bus {}", bus); + } + Ok(bus.to_string()) +} + #[derive(Clone, Default)] pub struct ScsiDevState { /// Features which the scsi device supports. @@ -99,7 +143,9 @@ impl Device for ScsiDevice { pub struct ScsiDevice { pub base: DeviceBase, /// Configuration of the scsi device. - pub config: ScsiDevConfig, + pub dev_cfg: ScsiDevConfig, + /// Configuration of the scsi device's drive. + pub drive_cfg: DriveConfig, /// State of the scsi device. pub state: ScsiDevState, /// Block backend opened by scsi device. @@ -129,13 +175,19 @@ unsafe impl Sync for ScsiDevice {} impl ScsiDevice { pub fn new( - config: ScsiDevConfig, - scsi_type: u32, + dev_cfg: ScsiDevConfig, + drive_cfg: DriveConfig, drive_files: Arc>>, ) -> ScsiDevice { + let scsi_type = match dev_cfg.classtype.as_str() { + "scsi-hd" => SCSI_TYPE_DISK, + _ => SCSI_TYPE_ROM, + }; + ScsiDevice { - base: DeviceBase::new(config.id.clone(), false), - config, + base: DeviceBase::new(dev_cfg.id.clone(), false), + dev_cfg, + drive_cfg, state: ScsiDevState::new(), block_backend: None, req_align: 1, @@ -164,35 +216,35 @@ impl ScsiDevice { } } - if let Some(serial) = &self.config.serial { + if let Some(serial) = &self.dev_cfg.serial { self.state.serial = serial.clone(); } let drive_files = self.drive_files.lock().unwrap(); - // File path can not be empty string. And it has also been checked in CmdParser::parse. - let file = VmConfig::fetch_drive_file(&drive_files, &self.config.path_on_host)?; + // File path can not be empty string. And it has also been checked in command parsing by using `Clap`. + let file = VmConfig::fetch_drive_file(&drive_files, &self.drive_cfg.path_on_host)?; - let alignments = VmConfig::fetch_drive_align(&drive_files, &self.config.path_on_host)?; + let alignments = VmConfig::fetch_drive_align(&drive_files, &self.drive_cfg.path_on_host)?; self.req_align = alignments.0; self.buf_align = alignments.1; - let drive_id = VmConfig::get_drive_id(&drive_files, &self.config.path_on_host)?; + let drive_id = VmConfig::get_drive_id(&drive_files, &self.drive_cfg.path_on_host)?; let mut thread_pool = None; - if self.config.aio_type != AioEngine::Off { + if self.drive_cfg.aio != AioEngine::Off { thread_pool = Some(EventLoop::get_ctx(None).unwrap().thread_pool.clone()); } - let aio = Aio::new(Arc::new(aio_complete_cb), self.config.aio_type, thread_pool)?; + let aio = Aio::new(Arc::new(aio_complete_cb), self.drive_cfg.aio, thread_pool)?; let conf = BlockProperty { id: drive_id, - format: self.config.format, + format: self.drive_cfg.format, iothread, - direct: self.config.direct, + direct: self.drive_cfg.direct, req_align: self.req_align, buf_align: self.buf_align, discard: false, write_zeroes: WriteZeroesState::Off, - l2_cache_size: self.config.l2_cache_size, - refcount_cache_size: self.config.refcount_cache_size, + l2_cache_size: self.drive_cfg.l2_cache_size, + refcount_cache_size: self.drive_cfg.refcount_cache_size, }; let backend = create_block_backend(file, aio, conf)?; let disk_size = backend.lock().unwrap().disk_size()?; @@ -202,3 +254,60 @@ impl ScsiDevice { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_scsi_device_cmdline_parser() { + // Test1: Right. + let cmdline1 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0,serial=123456,bootindex=1"; + let config = + ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline1, true, false)).unwrap(); + assert_eq!(config.id, "scsi0-0-0-0"); + assert_eq!(config.bus, "scsi0.0"); + assert_eq!(config.target, 0); + assert_eq!(config.lun, 0); + assert_eq!(config.drive, "drive-0-0-0-0"); + assert_eq!(config.serial.unwrap(), "123456"); + assert_eq!(config.bootindex.unwrap(), 1); + + // Test2: Default value. + let cmdline2 = "scsi-cd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let config = + ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline2, true, false)).unwrap(); + assert_eq!(config.channel, 0); + assert_eq!(config.serial, None); + assert_eq!(config.bootindex, None); + + // Test3: Illegal value. + let cmdline3 = "scsi-hd,bus=scsi0.0,scsi-id=256,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + let cmdline3 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=256,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + let cmdline3 = "illegal,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + + // Test4: Missing necessary parameters. + let cmdline4 = "scsi-hd,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "scsi-hd,bus=scsi0.0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,drive=drive-0-0-0-0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,id=scsi0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0"; + let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + } +} diff --git a/devices/src/smbios/smbios_table.rs b/devices/src/smbios/smbios_table.rs index 9211bd8..f476025 100644 --- a/devices/src/smbios/smbios_table.rs +++ b/devices/src/smbios/smbios_table.rs @@ -656,9 +656,9 @@ impl SmbiosTable { fn build_type0(&mut self, type0: SmbiosType0Config) { let mut table0: SmbiosType0Table = SmbiosType0Table::new(); - if let Some(vender) = type0.vender { + if let Some(vendor) = type0.vendor { table0.header.vendor_idx = table0.str_index + 1; - table0.set_str(vender); + table0.set_str(vendor); } if let Some(version) = type0.version { diff --git a/devices/src/sysbus/mod.rs b/devices/src/sysbus/mod.rs index ca039ab..4bcc760 100644 --- a/devices/src/sysbus/mod.rs +++ b/devices/src/sysbus/mod.rs @@ -14,16 +14,30 @@ pub mod error; pub use error::SysBusError; +use std::any::{Any, TypeId}; +use std::collections::HashMap; use std::fmt; use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; use vmm_sys_util::eventfd::EventFd; +#[cfg(target_arch = "x86_64")] +use crate::acpi::cpu_controller::CpuController; +use crate::acpi::ged::Ged; +#[cfg(target_arch = "aarch64")] +use crate::acpi::power::PowerDev; +#[cfg(all(feature = "ramfb", target_arch = "aarch64"))] +use crate::legacy::Ramfb; +#[cfg(target_arch = "x86_64")] +use crate::legacy::{FwCfgIO, RTC}; +#[cfg(target_arch = "aarch64")] +use crate::legacy::{FwCfgMem, PL011, PL031}; +use crate::legacy::{PFlash, Serial}; +use crate::pci::PciHost; use crate::{Device, DeviceBase, IrqState, LineIrqManager, TriggerMode}; use acpi::{AmlBuilder, AmlScope}; use address_space::{AddressSpace, GuestAddress, Region, RegionIoEventFd, RegionOps}; -use util::AsAny; // Now that the serial device use a hardcoded IRQ number (4), and the starting // free IRQ number can be 5. @@ -38,6 +52,11 @@ pub const IRQ_BASE: i32 = 32; #[cfg(target_arch = "aarch64")] pub const IRQ_MAX: i32 = 191; +#[cfg(target_arch = "riscv64")] +pub const IRQ_BASE: i32 = 1; +#[cfg(target_arch = "riscv64")] +pub const IRQ_MAX: i32 = 2047; + pub struct SysBus { #[cfg(target_arch = "x86_64")] pub sys_io: Arc, @@ -52,26 +71,18 @@ pub struct SysBus { impl fmt::Debug for SysBus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("SysBus"); + #[cfg(target_arch = "x86_64")] - let debug = f - .debug_struct("SysBus") - .field("sys_io", &self.sys_io) - .field("sys_mem", &self.sys_mem) - .field("free_irqs", &self.free_irqs) - .field("min_free_irq", &self.min_free_irq) - .field("mmio_region", &self.mmio_region) - .field("min_free_base", &self.min_free_base) - .finish(); - #[cfg(target_arch = "aarch64")] - let debug = f - .debug_struct("SysBus") + let debug = debug.field("sys_io", &self.sys_io); + + debug .field("sys_mem", &self.sys_mem) .field("free_irqs", &self.free_irqs) .field("min_free_irq", &self.min_free_irq) .field("mmio_region", &self.mmio_region) .field("min_free_base", &self.min_free_base) - .finish(); - debug + .finish() } } @@ -112,84 +123,82 @@ impl SysBus { } } - pub fn attach_device( - &mut self, - dev: &Arc>, - region_base: u64, - region_size: u64, - region_name: &str, - ) -> Result<()> { - let region_ops = self.build_region_ops(dev); - let region = Region::init_io_region(region_size, region_ops, region_name); - let locked_dev = dev.lock().unwrap(); - - region.set_ioeventfds(&locked_dev.ioeventfds()); - match locked_dev.sysbusdev_base().dev_type { - SysBusDevType::Serial if cfg!(target_arch = "x86_64") => { - #[cfg(target_arch = "x86_64")] - self.sys_io - .root() - .add_subregion(region, region_base) - .with_context(|| { - format!( - "Failed to register region in I/O space: offset={},size={}", - region_base, region_size - ) - })?; - } - SysBusDevType::FwCfg if cfg!(target_arch = "x86_64") => { - #[cfg(target_arch = "x86_64")] - self.sys_io - .root() - .add_subregion(region, region_base) - .with_context(|| { - format!( - "Failed to register region in I/O space: offset 0x{:x}, size {}", - region_base, region_size - ) - })?; - } - SysBusDevType::Rtc if cfg!(target_arch = "x86_64") => { - #[cfg(target_arch = "x86_64")] - self.sys_io + pub fn attach_device(&mut self, dev: &Arc>) -> Result<()> { + let res = dev.lock().unwrap().get_sys_resource().clone(); + let region_base = res.region_base; + let region_size = res.region_size; + let region_name = res.region_name; + + // region_base/region_size are both 0 means this device doesn't have its own memory layout. + // The normally allocated device region_base is above the `MEM_LAYOUT[LayoutEntryType::Mmio as usize].0`. + if region_base != 0 && region_size != 0 { + let region_ops = self.build_region_ops(dev); + let region = Region::init_io_region(region_size, region_ops, ®ion_name); + let locked_dev = dev.lock().unwrap(); + + region.set_ioeventfds(&locked_dev.ioeventfds()); + match locked_dev.sysbusdev_base().dev_type { + SysBusDevType::Serial if cfg!(target_arch = "x86_64") => { + #[cfg(target_arch = "x86_64")] + self.sys_io + .root() + .add_subregion(region, region_base) + .with_context(|| { + format!( + "Failed to register region in I/O space: offset={},size={}", + region_base, region_size + ) + })?; + } + SysBusDevType::FwCfg if cfg!(target_arch = "x86_64") => { + #[cfg(target_arch = "x86_64")] + self.sys_io + .root() + .add_subregion(region, region_base) + .with_context(|| { + format!( + "Failed to register region in I/O space: offset 0x{:x}, size {}", + region_base, region_size + ) + })?; + } + SysBusDevType::Rtc if cfg!(target_arch = "x86_64") => { + #[cfg(target_arch = "x86_64")] + self.sys_io + .root() + .add_subregion(region, region_base) + .with_context(|| { + format!( + "Failed to register region in I/O space: offset 0x{:x}, size {}", + region_base, region_size + ) + })?; + } + _ => self + .sys_mem .root() .add_subregion(region, region_base) .with_context(|| { format!( - "Failed to register region in I/O space: offset 0x{:x}, size {}", + "Failed to register region in memory space: offset={},size={}", region_base, region_size ) - })?; + })?, } - _ => self - .sys_mem - .root() - .add_subregion(region, region_base) - .with_context(|| { - format!( - "Failed to register region in memory space: offset={},size={}", - region_base, region_size - ) - })?, } self.devices.push(dev.clone()); Ok(()) } - - pub fn attach_dynamic_device( - &mut self, - dev: &Arc>, - ) -> Result<()> { - self.devices.push(dev.clone()); - Ok(()) - } } -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct SysRes { + // Note: region_base/region_size are both 0 means that this device doesn't have its own memory layout. + // The normally allocated device memory region is above the `MEM_LAYOUT[LayoutEntryType::Mmio as usize].0`. pub region_base: u64, pub region_size: u64, + pub region_name: String, pub irq: i32, } @@ -198,6 +207,7 @@ impl Default for SysRes { Self { region_base: 0, region_size: 0, + region_name: "".to_string(), irq: -1, } } @@ -251,15 +261,16 @@ impl SysBusDevBase { } } - pub fn set_sys(&mut self, irq: i32, region_base: u64, region_size: u64) { + pub fn set_sys(&mut self, irq: i32, region_base: u64, region_size: u64, region_name: &str) { self.res.irq = irq; self.res.region_base = region_base; self.res.region_size = region_size; + self.res.region_name = region_name.to_string(); } } /// Operations for sysbus devices. -pub trait SysBusDevOps: Device + Send + AmlBuilder + AsAny { +pub trait SysBusDevOps: Device + Send + AmlBuilder { fn sysbusdev_base(&self) -> &SysBusDevBase; fn sysbusdev_base_mut(&mut self) -> &mut SysBusDevBase; @@ -300,8 +311,8 @@ pub trait SysBusDevOps: Device + Send + AmlBuilder + AsAny { Ok(irq) } - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - None + fn get_sys_resource(&mut self) -> &mut SysRes { + &mut self.sysbusdev_base_mut().res } fn set_sys_resource( @@ -309,18 +320,20 @@ pub trait SysBusDevOps: Device + Send + AmlBuilder + AsAny { sysbus: &mut SysBus, region_base: u64, region_size: u64, + region_name: &str, ) -> Result<()> { let irq = self.get_irq(sysbus)?; let interrupt_evt = self.sysbusdev_base().interrupt_evt.clone(); let irq_manager = sysbus.irq_manager.clone(); + // marker self.sysbusdev_base_mut().irq_state = - IrqState::new(irq as u32, interrupt_evt, irq_manager, TriggerMode::Edge); + IrqState::new(irq as u32, None, irq_manager, TriggerMode::Edge); let irq_state = &mut self.sysbusdev_base_mut().irq_state; irq_state.register_irq()?; self.sysbusdev_base_mut() - .set_sys(irq, region_base, region_size); + .set_sys(irq, region_base, region_size, region_name); Ok(()) } @@ -350,3 +363,65 @@ impl AmlBuilder for SysBus { scope.aml_bytes() } } + +pub type ToSysBusDevOpsFunc = fn(&dyn Any) -> &dyn SysBusDevOps; + +static mut SYSBUSDEVTYPE_HASHMAP: Option> = None; + +pub fn convert_to_sysbusdevops(item: &dyn Any) -> &dyn SysBusDevOps { + // SAFETY: The typeid of `T` is the typeid recorded in the hashmap. The target structure type of + // the conversion is its own structure type, so the conversion result will definitely not be `None`. + let t = item.downcast_ref::().unwrap(); + t as &dyn SysBusDevOps +} + +pub fn register_sysbusdevops_type() -> Result<()> { + let type_id = TypeId::of::(); + // SAFETY: SYSBUSDEVTYPE_HASHMAP will be built in `type_init` function sequentially in the main thread. + // And will not be changed after `type_init`. + unsafe { + if SYSBUSDEVTYPE_HASHMAP.is_none() { + SYSBUSDEVTYPE_HASHMAP = Some(HashMap::new()); + } + let types = SYSBUSDEVTYPE_HASHMAP.as_mut().unwrap(); + if types.get(&type_id).is_some() { + bail!("Type Id {:?} has been registered.", type_id); + } + types.insert(type_id, convert_to_sysbusdevops::); + } + + Ok(()) +} + +pub fn devices_register_sysbusdevops_type() -> Result<()> { + #[cfg(target_arch = "x86_64")] + { + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + } + #[cfg(target_arch = "aarch64")] + { + register_sysbusdevops_type::()?; + #[cfg(all(feature = "ramfb"))] + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + } + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + register_sysbusdevops_type::()?; + register_sysbusdevops_type::() +} + +pub fn to_sysbusdevops(dev: &dyn Device) -> Option<&dyn SysBusDevOps> { + let type_id = dev.type_id(); + // SAFETY: SYSBUSDEVTYPE_HASHMAP has been built. And this function is called without changing hashmap. + unsafe { + let types = SYSBUSDEVTYPE_HASHMAP.as_mut().unwrap(); + let func = types.get(&type_id)?; + let sysbusdev = func(dev.as_any()); + Some(sysbusdev) + } +} diff --git a/devices/src/usb/camera.rs b/devices/src/usb/camera.rs index bd7ae9f..79cc1ba 100644 --- a/devices/src/usb/camera.rs +++ b/devices/src/usb/camera.rs @@ -41,14 +41,10 @@ use machine_manager::event_loop::{register_event_helper, unregister_event_helper use util::aio::{iov_discard_front_direct, Iovec}; use util::byte_code::ByteCode; use util::loop_context::{ - read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, + create_new_eventfd, read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, + NotifierOperation, }; -// CRC16 of "STRATOVIRT" -const UVC_VENDOR_ID: u16 = 0xB74C; -// The first 4 chars of "VIDEO", 5 substitutes V. -const UVC_PRODUCT_ID: u16 = 0x51DE; - const INTERFACE_ID_CONTROL: u8 = 0; const INTERFACE_ID_STREAMING: u8 = 1; @@ -95,8 +91,10 @@ const FRAME_SIZE_1280_720: u32 = 1280 * 720 * 2; const USB_CAMERA_BUFFER_LEN: usize = 12 * 1024; #[derive(Parser, Debug, Clone)] -#[command(name = "usb_camera")] +#[command(no_binary_name(true))] pub struct UsbCameraConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] pub id: String, #[arg(long)] @@ -433,8 +431,8 @@ fn gen_desc_device_camera(fmt_list: Vec) -> Result Result<()> { info!("Camera {} unrealize", self.device_id()); + self.unregister_camera_fd()?; self.camera_backend.lock().unwrap().reset(); Ok(()) } + fn cancel_packet(&mut self, _packet: &Arc>) {} + fn reset(&mut self) { info!("Camera {} device reset", self.device_id()); self.base.addr = 0; @@ -809,7 +810,7 @@ impl UsbDevice for UsbCamera { } } Err(e) => { - warn!("Camera descriptor error {:?}", e); + warn!("Received incorrect USB Camera descriptor message: {:?}", e); locked_packet.status = UsbPacketStatus::Stall; return; } diff --git a/devices/src/usb/config.rs b/devices/src/usb/config.rs index 3dda38b..1b34345 100644 --- a/devices/src/usb/config.rs +++ b/devices/src/usb/config.rs @@ -206,6 +206,7 @@ pub const USB_DT_DEBUG: u8 = 10; pub const USB_DT_INTERFACE_ASSOCIATION: u8 = 11; pub const USB_DT_BOS: u8 = 15; pub const USB_DT_DEVICE_CAPABILITY: u8 = 16; +pub const USB_DT_PIPE_USAGE: u8 = 36; pub const USB_DT_ENDPOINT_COMPANION: u8 = 48; /// USB SuperSpeed Device Capability. @@ -218,8 +219,10 @@ pub const USB_DT_DEVICE_SIZE: u8 = 18; pub const USB_DT_CONFIG_SIZE: u8 = 9; pub const USB_DT_INTERFACE_SIZE: u8 = 9; pub const USB_DT_ENDPOINT_SIZE: u8 = 7; +pub const USB_DT_DEVICE_QUALIFIER_SIZE: u8 = 10; pub const USB_DT_BOS_SIZE: u8 = 5; pub const USB_DT_SS_CAP_SIZE: u8 = 10; +pub const USB_DT_PIPE_USAGE_SIZE: u8 = 4; pub const USB_DT_SS_EP_COMP_SIZE: u8 = 6; /// USB Endpoint Descriptor @@ -236,8 +239,27 @@ pub const USB_CONFIGURATION_ATTR_ONE: u8 = 1 << 7; pub const USB_CONFIGURATION_ATTR_SELF_POWER: u8 = 1 << 6; pub const USB_CONFIGURATION_ATTR_REMOTE_WAKEUP: u8 = 1 << 5; -// USB Class +/// USB Class pub const USB_CLASS_HID: u8 = 3; pub const USB_CLASS_MASS_STORAGE: u8 = 8; pub const USB_CLASS_VIDEO: u8 = 0xe; pub const USB_CLASS_MISCELLANEOUS: u8 = 0xef; + +/// USB Subclass +pub const USB_SUBCLASS_BOOT: u8 = 0x01; +pub const USB_SUBCLASS_SCSI: u8 = 0x06; + +/// USB Interface Protocol +pub const USB_IFACE_PROTOCOL_KEYBOARD: u8 = 0x01; +pub const USB_IFACE_PROTOCOL_BOT: u8 = 0x50; +pub const USB_IFACE_PROTOCOL_UAS: u8 = 0x62; + +/// CRC16 of "STRATOVIRT" +pub const USB_VENDOR_ID_STRATOVIRT: u16 = 0xB74C; + +/// USB Product IDs +pub const USB_PRODUCT_ID_UVC: u16 = 0x0001; +pub const USB_PRODUCT_ID_KEYBOARD: u16 = 0x0002; +pub const USB_PRODUCT_ID_STORAGE: u16 = 0x0003; +pub const USB_PRODUCT_ID_TABLET: u16 = 0x0004; +pub const USB_PRODUCT_ID_UAS: u16 = 0x0005; diff --git a/devices/src/usb/descriptor.rs b/devices/src/usb/descriptor.rs index bdb30f7..ca4c51e 100644 --- a/devices/src/usb/descriptor.rs +++ b/devices/src/usb/descriptor.rs @@ -349,11 +349,25 @@ impl UsbDescriptor { } fn get_device_qualifier_descriptor(&self) -> Result> { - if let Some(desc) = self.device_qualifier_desc.as_ref() { - Ok(desc.qualifier_desc.as_bytes().to_vec()) - } else { - bail!("Device qualifier descriptor not found"); + if self.device_desc.is_none() { + bail!("device qualifier descriptor not found"); } + + // SAFETY: device_desc has just been checked + let device_desc = &self.device_desc.as_ref().unwrap().device_desc; + let device_qualifier_desc = UsbDeviceQualifierDescriptor { + bLength: USB_DT_DEVICE_QUALIFIER_SIZE, + bDescriptorType: USB_DT_DEVICE_QUALIFIER, + bcdUSB: device_desc.bcdUSB, + bDeviceClass: device_desc.bDeviceClass, + bDeviceSubClass: device_desc.bDeviceSubClass, + bDeviceProtocol: device_desc.bDeviceProtocol, + bMaxPacketSize0: device_desc.bMaxPacketSize0, + bNumConfigurations: device_desc.bNumConfigurations, + bReserved: 0, + }; + + Ok(device_qualifier_desc.as_bytes().to_vec()) } fn get_debug_descriptor(&self) -> Result> { diff --git a/devices/src/usb/keyboard.rs b/devices/src/usb/keyboard.rs index 73dc8ed..5329142 100644 --- a/devices/src/usb/keyboard.rs +++ b/devices/src/usb/keyboard.rs @@ -38,7 +38,7 @@ static DESC_DEVICE_KEYBOARD: Lazy> = Lazy::new(|| { bLength: USB_DT_DEVICE_SIZE, bDescriptorType: USB_DT_DEVICE, idVendor: 0x0627, - idProduct: 0x0001, + idProduct: USB_PRODUCT_ID_KEYBOARD, bcdDevice: 0, iManufacturer: STR_MANUFACTURER_INDEX, iProduct: STR_PRODUCT_KEYBOARD_INDEX, @@ -76,8 +76,8 @@ static DESC_IFACE_KEYBOARD: Lazy> = Lazy::new(|| { bAlternateSetting: 0, bNumEndpoints: 1, bInterfaceClass: USB_CLASS_HID, - bInterfaceSubClass: 1, - bInterfaceProtocol: 1, + bInterfaceSubClass: USB_SUBCLASS_BOOT, + bInterfaceProtocol: USB_IFACE_PROTOCOL_KEYBOARD, iInterface: 0, }, other_desc: vec![Arc::new(UsbDescOther { @@ -121,8 +121,10 @@ const DESC_STRINGS: [&str; 5] = [ ]; #[derive(Parser, Clone, Debug, Default)] -#[command(name = "usb_keyboard")] +#[command(no_binary_name(true))] pub struct UsbKeyboardConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] id: String, #[arg(long)] @@ -217,6 +219,8 @@ impl UsbDevice for UsbKeyboard { Ok(()) } + fn cancel_packet(&mut self, _packet: &Arc>) {} + fn reset(&mut self) { info!("Keyboard device reset"); self.base.remote_wakeup = 0; @@ -237,7 +241,10 @@ impl UsbDevice for UsbKeyboard { } } Err(e) => { - warn!("Keyboard descriptor error {:?}", e); + warn!( + "Received incorrect USB Keyboard descriptor message: {:?}", + e + ); locked_packet.status = UsbPacketStatus::Stall; return; } diff --git a/devices/src/usb/mod.rs b/devices/src/usb/mod.rs index 08cef7e..44f5d88 100644 --- a/devices/src/usb/mod.rs +++ b/devices/src/usb/mod.rs @@ -20,6 +20,7 @@ pub mod hid; pub mod keyboard; pub mod storage; pub mod tablet; +pub mod uas; #[cfg(feature = "usb_host")] pub mod usbhost; pub mod xhci; @@ -60,6 +61,12 @@ pub enum UsbPacketStatus { IoError, } +impl Default for UsbPacketStatus { + fn default() -> Self { + Self::NoDev + } +} + /// USB request used to transfer to USB device. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] @@ -343,6 +350,10 @@ pub trait UsbDevice: Send + Sync { fn unrealize(&mut self) -> Result<()> { Ok(()) } + + /// Cancel specified USB packet. + fn cancel_packet(&mut self, packet: &Arc>); + /// Handle the attach ops when attach device to controller. fn handle_attach(&mut self) -> Result<()> { let usb_dev = self.usb_device_base_mut(); @@ -385,10 +396,10 @@ pub trait UsbDevice: Send + Sync { } } - /// Handle control pakcet. + /// Handle control packet. fn handle_control(&mut self, packet: &Arc>, device_req: &UsbDeviceRequest); - /// Handle data pakcet. + /// Handle data packet. fn handle_data(&mut self, packet: &Arc>); /// Unique device id. @@ -483,7 +494,10 @@ pub trait TransferOps: Send + Sync { } /// Usb packet used for device transfer data. +#[derive(Default)] pub struct UsbPacket { + /// USB packet unique identifier. + pub packet_id: u32, /// USB packet id. pub pid: u32, pub is_async: bool, @@ -498,6 +512,10 @@ pub struct UsbPacket { pub ep_number: u8, /// Transfer for complete packet. pub xfer_ops: Option>>, + /// Target USB device for this packet. + pub target_dev: Option>>, + /// Stream id. + pub stream: u32, } impl std::fmt::Display for UsbPacket { @@ -512,12 +530,15 @@ impl std::fmt::Display for UsbPacket { impl UsbPacket { pub fn new( + packet_id: u32, pid: u32, ep_number: u8, iovecs: Vec, xfer_ops: Option>>, + target_dev: Option>>, ) -> Self { Self { + packet_id, pid, is_async: false, iovecs, @@ -526,6 +547,8 @@ impl UsbPacket { actual_length: 0, ep_number, xfer_ops, + target_dev, + stream: 0, } } @@ -573,7 +596,7 @@ impl UsbPacket { self.actual_length = copied as u32; } - pub fn get_iovecs_size(&mut self) -> u64 { + pub fn get_iovecs_size(&self) -> u64 { let mut size = 0; for iov in &self.iovecs { size += iov.iov_len; @@ -583,21 +606,6 @@ impl UsbPacket { } } -impl Default for UsbPacket { - fn default() -> UsbPacket { - UsbPacket { - pid: 0, - is_async: false, - iovecs: Vec::new(), - parameter: 0, - status: UsbPacketStatus::NoDev, - actual_length: 0, - ep_number: 0, - xfer_ops: None, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/devices/src/usb/storage.rs b/devices/src/usb/storage.rs index b227ecc..2f924d8 100644 --- a/devices/src/usb/storage.rs +++ b/devices/src/usb/storage.rs @@ -17,6 +17,7 @@ use std::{ use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; +use clap::Parser; use log::{error, info, warn}; use once_cell::sync::Lazy; @@ -32,9 +33,10 @@ use crate::{ ScsiBus, ScsiRequest, ScsiRequestOps, ScsiSense, ScsiXferMode, EMULATE_SCSI_OPS, GOOD, SCSI_CMD_BUF_SIZE, }, - ScsiDisk::{ScsiDevice, SCSI_TYPE_DISK, SCSI_TYPE_ROM}, + ScsiDisk::{ScsiDevConfig, ScsiDevice}, }; -use machine_manager::config::{DriveFile, UsbStorageConfig}; +use machine_manager::config::{DriveConfig, DriveFile}; +use util::aio::AioEngine; // Storage device descriptor static DESC_DEVICE_STORAGE: Lazy> = Lazy::new(|| { @@ -43,7 +45,7 @@ static DESC_DEVICE_STORAGE: Lazy> = Lazy::new(|| { bLength: USB_DT_DEVICE_SIZE, bDescriptorType: USB_DT_DEVICE, idVendor: USB_STORAGE_VENDOR_ID, - idProduct: 0x0001, + idProduct: USB_PRODUCT_ID_STORAGE, bcdDevice: 0, iManufacturer: STR_MANUFACTURER_INDEX, iProduct: STR_PRODUCT_STORAGE_INDEX, @@ -82,8 +84,8 @@ static DESC_IFACE_STORAGE: Lazy> = Lazy::new(|| { bAlternateSetting: 0, bNumEndpoints: 2, bInterfaceClass: USB_CLASS_MASS_STORAGE, - bInterfaceSubClass: 0x06, // SCSI - bInterfaceProtocol: 0x50, // Bulk-only + bInterfaceSubClass: USB_SUBCLASS_SCSI, + bInterfaceProtocol: USB_IFACE_PROTOCOL_BOT, iInterface: 0, }, other_desc: vec![], @@ -221,6 +223,21 @@ impl UsbStorageState { } } +#[derive(Parser, Clone, Debug)] +#[command(no_binary_name(true))] +pub struct UsbStorageConfig { + #[arg(long, value_parser = ["usb-storage"])] + pub classtype: String, + #[arg(long)] + pub id: String, + #[arg(long)] + pub drive: String, + #[arg(long)] + bus: Option, + #[arg(long)] + port: Option, +} + /// USB storage device. pub struct UsbStorage { base: UsbDeviceBase, @@ -228,7 +245,9 @@ pub struct UsbStorage { /// USB controller used to notify controller to transfer data. cntlr: Option>>, /// Configuration of the USB storage device. - pub config: UsbStorageConfig, + pub dev_cfg: UsbStorageConfig, + /// Configuration of the USB storage device's drive. + pub drive_cfg: DriveConfig, /// Scsi bus attached to this usb-storage device. scsi_bus: Arc>, /// Effective scsi backend. @@ -305,26 +324,37 @@ impl UsbMsdCsw { impl UsbStorage { pub fn new( - config: UsbStorageConfig, + dev_cfg: UsbStorageConfig, + drive_cfg: DriveConfig, drive_files: Arc>>, - ) -> Self { - let scsi_type = match &config.media as &str { - "disk" => SCSI_TYPE_DISK, - _ => SCSI_TYPE_ROM, + ) -> Result { + if drive_cfg.aio != AioEngine::Off || drive_cfg.direct { + bail!("USB-storage: \"aio=off,direct=false\" must be configured."); + } + + let scsidev_classtype = match &drive_cfg.media as &str { + "disk" => "scsi-hd".to_string(), + _ => "scsi-cd".to_string(), + }; + let scsi_dev_cfg = ScsiDevConfig { + classtype: scsidev_classtype, + drive: dev_cfg.drive.clone(), + ..Default::default() }; - Self { - base: UsbDeviceBase::new(config.id.clone().unwrap(), USB_DEVICE_BUFFER_DEFAULT_LEN), + Ok(Self { + base: UsbDeviceBase::new(dev_cfg.id.clone(), USB_DEVICE_BUFFER_DEFAULT_LEN), state: UsbStorageState::new(), cntlr: None, - config: config.clone(), + dev_cfg, + drive_cfg: drive_cfg.clone(), scsi_bus: Arc::new(Mutex::new(ScsiBus::new("".to_string()))), scsi_dev: Arc::new(Mutex::new(ScsiDevice::new( - config.scsi_cfg, - scsi_type, + scsi_dev_cfg, + drive_cfg, drive_files, ))), - } + }) } fn handle_control_packet(&mut self, packet: &mut UsbPacket, device_req: &UsbDeviceRequest) { @@ -542,6 +572,8 @@ impl UsbDevice for UsbStorage { Ok(storage) } + fn cancel_packet(&mut self, _packet: &Arc>) {} + fn reset(&mut self) { info!("Storage device reset"); self.base.remote_wakeup = 0; @@ -563,7 +595,7 @@ impl UsbDevice for UsbStorage { self.handle_control_packet(&mut locked_packet, device_req) } Err(e) => { - warn!("Storage descriptor error {:?}", e); + warn!("Received incorrect USB Storage descriptor message: {:?}", e); locked_packet.status = UsbPacketStatus::Stall; } } diff --git a/devices/src/usb/tablet.rs b/devices/src/usb/tablet.rs index 67a1513..a4bfebf 100644 --- a/devices/src/usb/tablet.rs +++ b/devices/src/usb/tablet.rs @@ -44,7 +44,7 @@ static DESC_DEVICE_TABLET: Lazy> = Lazy::new(|| { bLength: USB_DT_DEVICE_SIZE, bDescriptorType: USB_DT_DEVICE, idVendor: 0x0627, - idProduct: 0x0001, + idProduct: USB_PRODUCT_ID_TABLET, bcdDevice: 0, iManufacturer: STR_MANUFACTURER_INDEX, iProduct: STR_PRODUCT_TABLET_INDEX, @@ -114,8 +114,10 @@ const STR_SERIAL_TABLET_INDEX: u8 = 4; const DESC_STRINGS: [&str; 5] = ["", "StratoVirt", "StratoVirt USB Tablet", "HID Tablet", "2"]; #[derive(Parser, Clone, Debug, Default)] -#[command(name = "usb_tablet")] +#[command(no_binary_name(true))] pub struct UsbTabletConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] id: String, #[arg(long)] @@ -260,6 +262,8 @@ impl UsbDevice for UsbTablet { Ok(()) } + fn cancel_packet(&mut self, _packet: &Arc>) {} + fn reset(&mut self) { info!("Tablet device reset"); self.base.remote_wakeup = 0; @@ -280,7 +284,7 @@ impl UsbDevice for UsbTablet { } } Err(e) => { - warn!("Tablet descriptor error {:?}", e); + warn!("Received incorrect USB Tablet descriptor message: {:?}", e); locked_packet.status = UsbPacketStatus::Stall; return; } diff --git a/devices/src/usb/uas.rs b/devices/src/usb/uas.rs new file mode 100644 index 0000000..4bdfc39 --- /dev/null +++ b/devices/src/usb/uas.rs @@ -0,0 +1,1289 @@ +// Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::array; +use std::cmp::min; +use std::collections::{HashMap, VecDeque}; +use std::mem::size_of; +use std::sync::{Arc, Mutex, Weak}; + +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; +use log::{debug, error, info, warn}; +use once_cell::sync::Lazy; +use strum::EnumCount; +use strum_macros::EnumCount; + +use super::config::*; +use super::descriptor::{ + UsbConfigDescriptor, UsbDescConfig, UsbDescDevice, UsbDescEndpoint, UsbDescIface, + UsbDescriptorOps, UsbDeviceDescriptor, UsbEndpointDescriptor, UsbInterfaceDescriptor, + UsbSuperSpeedEndpointCompDescriptor, +}; +use super::xhci::xhci_controller::XhciDevice; +use super::{ + UsbDevice, UsbDeviceBase, UsbDeviceRequest, UsbEndpoint, UsbPacket, UsbPacketStatus, + USB_DEVICE_BUFFER_DEFAULT_LEN, +}; +use crate::{ + ScsiBus::{ + scsi_cdb_xfer, scsi_cdb_xfer_mode, ScsiBus, ScsiRequest, ScsiRequestOps, ScsiSense, + ScsiXferMode, CHECK_CONDITION, EMULATE_SCSI_OPS, GOOD, SCSI_SENSE_INVALID_PARAM_VALUE, + SCSI_SENSE_INVALID_TAG, SCSI_SENSE_NO_SENSE, SCSI_SENSE_OVERLAPPED_COMMANDS, + }, + ScsiDisk::{ScsiDevConfig, ScsiDevice}, +}; +use machine_manager::config::{DriveConfig, DriveFile}; +use util::byte_code::ByteCode; + +// Size of UasIUBody +const UAS_IU_BODY_SIZE: usize = 30; + +// Size of cdb in UAS Command IU +const UAS_COMMAND_CDB_SIZE: usize = 16; + +// UAS Pipe IDs +const UAS_PIPE_ID_COMMAND: u8 = 0x01; +const UAS_PIPE_ID_STATUS: u8 = 0x02; +const UAS_PIPE_ID_DATA_IN: u8 = 0x03; +const UAS_PIPE_ID_DATA_OUT: u8 = 0x04; + +// UAS Streams Attributes +const UAS_MAX_STREAMS_BM_ATTR: u8 = 0; +const UAS_MAX_STREAMS: usize = 1 << UAS_MAX_STREAMS_BM_ATTR; + +// UAS IU IDs +const UAS_IU_ID_COMMAND: u8 = 0x01; +const UAS_IU_ID_SENSE: u8 = 0x03; +const UAS_IU_ID_RESPONSE: u8 = 0x04; +const UAS_IU_ID_TASK_MGMT: u8 = 0x05; +const UAS_IU_ID_READ_READY: u8 = 0x06; +const UAS_IU_ID_WRITE_READY: u8 = 0x07; + +// UAS Response Codes +const _UAS_RC_TMF_COMPLETE: u8 = 0x00; +const _UAS_RC_INVALID_IU: u8 = 0x02; +const UAS_RC_TMF_NOT_SUPPORTED: u8 = 0x04; +const _UAS_RC_TMF_FAILED: u8 = 0x05; +const _UAS_RC_TMF_SUCCEEDED: u8 = 0x08; +const _UAS_RC_INCORRECT_LUN: u8 = 0x09; +const _UAS_RC_OVERLAPPED_TAG: u8 = 0x0A; + +// UAS Task Management Functions +const _UAS_TMF_ABORT_TASK: u8 = 0x01; +const _UAS_TMF_ABORT_TASK_SET: u8 = 0x02; +const _UAS_TMF_CLEAR_TASK_SET: u8 = 0x04; +const _UAS_TMF_LOGICAL_UNIT_RESET: u8 = 0x08; +const _UAS_TMF_I_T_NEXUS_RESET: u8 = 0x10; +const _UAS_TMF_CLEAR_ACA: u8 = 0x40; +const _UAS_TMF_QUERY_TASK: u8 = 0x80; +const _UAS_TMF_QUERY_TASK_SET: u8 = 0x81; +const _UAS_TMF_QUERY_ASYNC_EVENT: u8 = 0x82; + +#[derive(Parser, Clone, Debug)] +#[command(no_binary_name(true))] +pub struct UsbUasConfig { + #[arg(long, value_parser = ["usb-uas"])] + pub classtype: String, + #[arg(long)] + pub drive: String, + #[arg(long)] + pub id: Option, + #[arg(long)] + pub speed: Option, + #[arg(long)] + bus: Option, + #[arg(long)] + port: Option, +} + +pub struct UsbUas { + base: UsbDeviceBase, + scsi_bus: Arc>, + scsi_device: Arc>, + commands_high: VecDeque, + statuses_high: VecDeque>>, + commands_super: [Option; UAS_MAX_STREAMS + 1], + statuses_super: [Option>>; UAS_MAX_STREAMS + 1], + data: [Option>>; UAS_MAX_STREAMS + 1], + data_ready_sent: bool, +} + +#[derive(Debug, EnumCount)] +enum UsbUasStringId { + #[allow(unused)] + Invalid = 0, + Manufacturer = 1, + Product = 2, + SerialNumber = 3, + ConfigHigh = 4, + ConfigSuper = 5, +} + +const UAS_DESC_STRINGS: [&str; UsbUasStringId::COUNT] = [ + "", + "StratoVirt", + "StratoVirt USB Uas", + "5", + "High speed config (usb 2.0)", + "Super speed config (usb 3.0)", +]; + +struct UasRequest { + data: Option>>, + status: Arc>, + iu: UasIU, + completed: bool, +} + +impl ScsiRequestOps for UasRequest { + fn scsi_request_complete_cb( + &mut self, + scsi_status: u8, + scsi_sense: Option, + ) -> Result<()> { + let tag = u16::from_be(self.iu.header.tag); + let sense = scsi_sense.unwrap_or(SCSI_SENSE_NO_SENSE); + UsbUas::fill_sense(&mut self.status.lock().unwrap(), tag, scsi_status, &sense); + self.complete(); + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq)] +enum UasPacketStatus { + Completed = 0, + Pending = 1, +} + +impl From for UasPacketStatus { + fn from(status: bool) -> Self { + match status { + true => Self::Completed, + false => Self::Pending, + } + } +} + +#[allow(non_snake_case)] +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, Default)] +struct UsbPipeUsageDescriptor { + bLength: u8, + bDescriptorType: u8, + bPipeId: u8, + bReserved: u8, +} + +impl ByteCode for UsbPipeUsageDescriptor {} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIUHeader { + id: u8, + reserved: u8, + tag: u16, +} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIUCommand { + prio_task_attr: u8, // 6:3 priority, 2:0 task attribute + reserved_1: u8, + add_cdb_len: u8, + reserved_2: u8, + lun: u64, + cdb: [u8; UAS_COMMAND_CDB_SIZE], + add_cdb: [u8; 1], // not supported by stratovirt +} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIUSense { + status_qualifier: u16, + status: u8, + reserved: [u8; 7], + sense_length: u16, + sense_data: [u8; 18], +} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIUResponse { + add_response_info: [u8; 3], + response_code: u8, +} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIUTaskManagement { + function: u8, + reserved: u8, + task_tag: u16, + lun: u64, +} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +union UasIUBody { + command: UasIUCommand, + sense: UasIUSense, + response: UasIUResponse, + task_management: UasIUTaskManagement, + raw_data: [u8; UAS_IU_BODY_SIZE], +} + +impl Default for UasIUBody { + fn default() -> Self { + Self { + raw_data: [0; UAS_IU_BODY_SIZE], + } + } +} + +#[repr(C, packed)] +#[derive(Default, Clone, Copy)] +struct UasIU { + header: UasIUHeader, + body: UasIUBody, +} + +impl ByteCode for UasIU {} + +static DESC_DEVICE_UAS_SUPER: Lazy> = Lazy::new(|| { + Arc::new(UsbDescDevice { + device_desc: UsbDeviceDescriptor { + bLength: USB_DT_DEVICE_SIZE, + bDescriptorType: USB_DT_DEVICE, + bcdUSB: 0x0300, + bDeviceClass: 0, + bDeviceSubClass: 0, + bDeviceProtocol: 0, + bMaxPacketSize0: 9, + idVendor: USB_VENDOR_ID_STRATOVIRT, + idProduct: USB_PRODUCT_ID_UAS, + bcdDevice: 0, + iManufacturer: UsbUasStringId::Manufacturer as u8, + iProduct: UsbUasStringId::Product as u8, + iSerialNumber: UsbUasStringId::SerialNumber as u8, + bNumConfigurations: 1, + }, + configs: vec![Arc::new(UsbDescConfig { + config_desc: UsbConfigDescriptor { + bLength: USB_DT_CONFIG_SIZE, + bDescriptorType: USB_DT_CONFIGURATION, + wTotalLength: 0, + bNumInterfaces: 1, + bConfigurationValue: 1, + iConfiguration: UsbUasStringId::ConfigSuper as u8, + bmAttributes: USB_CONFIGURATION_ATTR_ONE | USB_CONFIGURATION_ATTR_SELF_POWER, + bMaxPower: 50, + }, + iad_desc: vec![], + interfaces: vec![DESC_IFACE_EMPTY.clone(), DESC_IFACE_UAS_SUPER.clone()], + })], + }) +}); + +static DESC_IFACE_UAS_SUPER: Lazy> = Lazy::new(|| { + Arc::new(UsbDescIface { + interface_desc: UsbInterfaceDescriptor { + bLength: USB_DT_INTERFACE_SIZE, + bDescriptorType: USB_DT_INTERFACE, + bInterfaceNumber: 0, + bAlternateSetting: 1, + bNumEndpoints: 4, + bInterfaceClass: USB_CLASS_MASS_STORAGE, + bInterfaceSubClass: USB_SUBCLASS_SCSI, + bInterfaceProtocol: USB_IFACE_PROTOCOL_UAS, + iInterface: 0, + }, + other_desc: vec![], + endpoints: vec![ + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_HOST_TO_DEVICE | UAS_PIPE_ID_COMMAND, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 1024, + bInterval: 0, + }, + extra: [ + UsbSuperSpeedEndpointCompDescriptor { + bLength: USB_DT_SS_EP_COMP_SIZE, + bDescriptorType: USB_DT_ENDPOINT_COMPANION, + bMaxBurst: 15, + bmAttributes: 0, + wBytesPerInterval: 0, + } + .as_bytes(), + UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_COMMAND, + bReserved: 0, + } + .as_bytes(), + ] + .concat() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_DEVICE_TO_HOST | UAS_PIPE_ID_STATUS, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 1024, + bInterval: 0, + }, + extra: [ + UsbSuperSpeedEndpointCompDescriptor { + bLength: USB_DT_SS_EP_COMP_SIZE, + bDescriptorType: USB_DT_ENDPOINT_COMPANION, + bMaxBurst: 15, + bmAttributes: UAS_MAX_STREAMS_BM_ATTR, + wBytesPerInterval: 0, + } + .as_bytes(), + UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_STATUS, + bReserved: 0, + } + .as_bytes(), + ] + .concat() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_DEVICE_TO_HOST | UAS_PIPE_ID_DATA_IN, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 1024, + bInterval: 0, + }, + extra: [ + UsbSuperSpeedEndpointCompDescriptor { + bLength: USB_DT_SS_EP_COMP_SIZE, + bDescriptorType: USB_DT_ENDPOINT_COMPANION, + bMaxBurst: 15, + bmAttributes: UAS_MAX_STREAMS_BM_ATTR, + wBytesPerInterval: 0, + } + .as_bytes(), + UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_DATA_IN, + bReserved: 0, + } + .as_bytes(), + ] + .concat() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_HOST_TO_DEVICE | UAS_PIPE_ID_DATA_OUT, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 1024, + bInterval: 0, + }, + extra: [ + UsbSuperSpeedEndpointCompDescriptor { + bLength: USB_DT_SS_EP_COMP_SIZE, + bDescriptorType: USB_DT_ENDPOINT_COMPANION, + bMaxBurst: 15, + bmAttributes: UAS_MAX_STREAMS_BM_ATTR, + wBytesPerInterval: 0, + } + .as_bytes(), + UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_DATA_OUT, + bReserved: 0, + } + .as_bytes(), + ] + .concat() + .to_vec(), + }), + ], + }) +}); + +static DESC_DEVICE_UAS_HIGH: Lazy> = Lazy::new(|| { + Arc::new(UsbDescDevice { + device_desc: UsbDeviceDescriptor { + bLength: USB_DT_DEVICE_SIZE, + bDescriptorType: USB_DT_DEVICE, + bcdUSB: 0x0200, + bDeviceClass: 0, + bDeviceSubClass: 0, + bDeviceProtocol: 0, + bMaxPacketSize0: 64, + idVendor: USB_VENDOR_ID_STRATOVIRT, + idProduct: USB_PRODUCT_ID_UAS, + bcdDevice: 0, + iManufacturer: UsbUasStringId::Manufacturer as u8, + iProduct: UsbUasStringId::Product as u8, + iSerialNumber: UsbUasStringId::SerialNumber as u8, + bNumConfigurations: 1, + }, + configs: vec![Arc::new(UsbDescConfig { + config_desc: UsbConfigDescriptor { + bLength: USB_DT_CONFIG_SIZE, + bDescriptorType: USB_DT_CONFIGURATION, + wTotalLength: 0, + bNumInterfaces: 1, + bConfigurationValue: 1, + iConfiguration: UsbUasStringId::ConfigHigh as u8, + bmAttributes: USB_CONFIGURATION_ATTR_ONE | USB_CONFIGURATION_ATTR_SELF_POWER, + bMaxPower: 50, + }, + iad_desc: vec![], + interfaces: vec![DESC_IFACE_EMPTY.clone(), DESC_IFACE_UAS_HIGH.clone()], + })], + }) +}); + +static DESC_IFACE_UAS_HIGH: Lazy> = Lazy::new(|| { + Arc::new(UsbDescIface { + interface_desc: UsbInterfaceDescriptor { + bLength: USB_DT_INTERFACE_SIZE, + bDescriptorType: USB_DT_INTERFACE, + bInterfaceNumber: 0, + bAlternateSetting: 1, + bNumEndpoints: 4, + bInterfaceClass: USB_CLASS_MASS_STORAGE, + bInterfaceSubClass: USB_SUBCLASS_SCSI, + bInterfaceProtocol: USB_IFACE_PROTOCOL_UAS, + iInterface: 0, + }, + other_desc: vec![], + endpoints: vec![ + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_HOST_TO_DEVICE | UAS_PIPE_ID_COMMAND, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 512, + bInterval: 0, + }, + extra: UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_COMMAND, + bReserved: 0, + } + .as_bytes() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_DEVICE_TO_HOST | UAS_PIPE_ID_STATUS, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 512, + bInterval: 0, + }, + extra: UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_STATUS, + bReserved: 0, + } + .as_bytes() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_DEVICE_TO_HOST | UAS_PIPE_ID_DATA_IN, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 512, + bInterval: 0, + }, + extra: UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_DATA_IN, + bReserved: 0, + } + .as_bytes() + .to_vec(), + }), + Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_HOST_TO_DEVICE | UAS_PIPE_ID_DATA_OUT, + bmAttributes: USB_ENDPOINT_ATTR_BULK, + wMaxPacketSize: 512, + bInterval: 0, + }, + extra: UsbPipeUsageDescriptor { + bLength: USB_DT_PIPE_USAGE_SIZE, + bDescriptorType: USB_DT_PIPE_USAGE, + bPipeId: UAS_PIPE_ID_DATA_OUT, + bReserved: 0, + } + .as_bytes() + .to_vec(), + }), + ], + }) +}); + +// NOTE: Fake BOT interface descriptor is needed here since Windows UASP driver always expects two +// interfaces: both BOT and UASP. It also anticipates the UASP descriptor to be the second one. +// Therefore, the first one can be a BOT storage stub. +static DESC_IFACE_EMPTY: Lazy> = Lazy::new(|| { + Arc::new(UsbDescIface { + interface_desc: UsbInterfaceDescriptor { + bLength: USB_DT_INTERFACE_SIZE, + bDescriptorType: USB_DT_INTERFACE, + bInterfaceNumber: 0, + bAlternateSetting: 0, + bNumEndpoints: 0, + bInterfaceClass: USB_CLASS_MASS_STORAGE, + bInterfaceSubClass: USB_SUBCLASS_SCSI, + bInterfaceProtocol: USB_IFACE_PROTOCOL_BOT, + iInterface: 0, + }, + other_desc: vec![], + endpoints: vec![], + }) +}); + +fn complete_async_packet(packet: &Arc>) { + let locked_packet = packet.lock().unwrap(); + + if let Some(xfer_ops) = locked_packet.xfer_ops.as_ref() { + if let Some(xfer_ops) = xfer_ops.clone().upgrade() { + drop(locked_packet); + xfer_ops.lock().unwrap().submit_transfer(); + } + } +} + +impl UsbUas { + pub fn new( + uas_config: UsbUasConfig, + drive_cfg: DriveConfig, + drive_files: Arc>>, + ) -> Self { + let scsidev_classtype = match &drive_cfg.media as &str { + "disk" => "scsi-hd".to_string(), + _ => "scsi-cd".to_string(), + }; + let scsi_dev_cfg = ScsiDevConfig { + classtype: scsidev_classtype, + drive: uas_config.drive.clone(), + ..Default::default() + }; + + let mut base = UsbDeviceBase::new( + uas_config.id.clone().unwrap(), + USB_DEVICE_BUFFER_DEFAULT_LEN, + ); + + base.speed = match uas_config.speed.as_deref() { + Some("super") => USB_SPEED_SUPER, + _ => USB_SPEED_HIGH, + }; + + Self { + base, + scsi_bus: Arc::new(Mutex::new(ScsiBus::new("".to_string()))), + scsi_device: Arc::new(Mutex::new(ScsiDevice::new( + scsi_dev_cfg, + drive_cfg, + drive_files, + ))), + commands_high: VecDeque::new(), + commands_super: array::from_fn(|_| None), + statuses_high: VecDeque::new(), + statuses_super: array::from_fn(|_| None), + data: array::from_fn(|_| None), + data_ready_sent: false, + } + } + + fn streams_enabled(&self) -> bool { + self.base.speed == USB_SPEED_SUPER + } + + fn cancel_io(&mut self) { + self.commands_high = VecDeque::new(); + self.commands_super = array::from_fn(|_| None); + self.statuses_high = VecDeque::new(); + self.statuses_super = array::from_fn(|_| None); + self.data = array::from_fn(|_| None); + self.data_ready_sent = false; + } + + fn peek_next_status(&self, stream: usize) -> Option<&Arc>> { + match self.streams_enabled() { + true => self.statuses_super[stream].as_ref(), + false => self.statuses_high.front(), + } + } + + fn take_next_status(&mut self, stream: usize) -> Arc> { + match self.streams_enabled() { + true => self.statuses_super[stream].take().unwrap(), + false => self.statuses_high.pop_front().unwrap(), + } + } + + fn queue_status(&mut self, status: &Arc>, stream: usize) { + match self.streams_enabled() { + true => self.statuses_super[stream] = Some(Arc::clone(status)), + false => self.statuses_high.push_back(Arc::clone(status)), + }; + } + + fn peek_next_command(&self, stream: usize) -> Option<&UasIU> { + match self.streams_enabled() { + true => self.commands_super[stream].as_ref(), + false => self.commands_high.front(), + } + } + + fn take_next_command(&mut self, stream: usize) -> UasIU { + match self.streams_enabled() { + true => self.commands_super[stream].take().unwrap(), + false => self.commands_high.pop_front().unwrap(), + } + } + + fn queue_command(&mut self, command: UasIU, stream: usize) { + match self.streams_enabled() { + true => self.commands_super[stream] = Some(command), + false => self.commands_high.push_back(command), + } + } + + fn handle_iu_command( + &mut self, + iu: &UasIU, + mut uas_request: UasRequest, + ) -> Result { + // SAFETY: iu is guaranteed to be of type command + let add_cdb_len = unsafe { iu.body.command.add_cdb_len }; + let tag = u16::from_be(iu.header.tag); + + if add_cdb_len > 0 { + Self::fill_fake_sense( + &mut uas_request.status.lock().unwrap(), + tag, + &SCSI_SENSE_INVALID_PARAM_VALUE, + ); + uas_request.complete(); + bail!("additional cdb length is not supported"); + } + + if self.streams_enabled() && tag > UAS_MAX_STREAMS as u16 { + Self::fill_fake_sense( + &mut uas_request.status.lock().unwrap(), + tag, + &SCSI_SENSE_INVALID_TAG, + ); + uas_request.complete(); + bail!("invalid tag {}", tag); + } + + if self.streams_enabled() && self.commands_super[tag as usize].is_some() { + Self::fill_fake_sense( + &mut uas_request.status.lock().unwrap(), + tag, + &SCSI_SENSE_OVERLAPPED_COMMANDS, + ); + uas_request.complete(); + bail!("overlapped tag {}", tag); + } + + let (scsi_iovec, scsi_iovec_size) = match uas_request.data.as_ref() { + Some(data) => { + let mut locked_data = data.lock().unwrap(); + let iov_size = locked_data.get_iovecs_size() as u32; + locked_data.actual_length = iov_size; + (locked_data.iovecs.clone(), iov_size) + } + None => (Vec::new(), 0), + }; + + // SAFETY: iu is guaranteed to of type command + let cdb = unsafe { iu.body.command.cdb }; + // SAFETY: iu is guaranteed to of type command + let lun = unsafe { iu.body.command.lun } as u16; + trace::usb_uas_handle_iu_command(self.device_id(), cdb[0]); + let uas_request = Box::new(uas_request); + let scsi_request = ScsiRequest::new( + cdb, + lun, + scsi_iovec, + scsi_iovec_size, + Arc::clone(&self.scsi_device), + uas_request, + ) + .with_context(|| "Failed to create SCSI request.")?; + + if scsi_request.cmd.xfer > scsi_request.datalen + && scsi_request.cmd.mode != ScsiXferMode::ScsiXferNone + { + bail!( + "insufficient buffer provided (requested length {}, provided length {})", + scsi_request.cmd.xfer, + scsi_request.datalen + ); + } + + let scsi_request = match scsi_request.opstype { + EMULATE_SCSI_OPS => scsi_request.emulate_execute(), + _ => scsi_request.execute(), + } + .with_context(|| "Failed to execute SCSI request.")?; + + let upper_request = &mut scsi_request.lock().unwrap().upper_req; + let uas_request = upper_request + .as_mut() + .as_any_mut() + .downcast_mut::() + .unwrap(); + + Ok(uas_request.completed.into()) + } + + fn handle_iu_task_management( + &mut self, + iu: &UasIU, + mut uas_request: UasRequest, + ) -> Result { + let tag = u16::from_be(iu.header.tag); + + if self.streams_enabled() && tag > UAS_MAX_STREAMS as u16 { + Self::fill_fake_sense( + &mut uas_request.status.lock().unwrap(), + tag, + &SCSI_SENSE_INVALID_TAG, + ); + uas_request.complete(); + bail!("invalid tag {}", tag); + } + + if self.streams_enabled() && self.commands_super[tag as usize].is_some() { + Self::fill_fake_sense( + &mut uas_request.status.lock().unwrap(), + tag, + &SCSI_SENSE_OVERLAPPED_COMMANDS, + ); + uas_request.complete(); + bail!("overlapped tag {}", tag); + } + + // SAFETY: iu is guaranteed to be of type task management + let tmf = unsafe { iu.body.task_management.function }; + + #[allow(clippy::match_single_binding)] + match tmf { + _ => { + warn!("UAS {} device unsupported TMF {}.", self.device_id(), tmf); + Self::fill_response( + &mut uas_request.status.lock().unwrap(), + tag, + UAS_RC_TMF_NOT_SUPPORTED, + ); + } + }; + + uas_request.complete(); + Ok(UasPacketStatus::Completed) + } + + fn fill_response(packet: &mut UsbPacket, tag: u16, code: u8) { + let mut iu = UasIU::new(UAS_IU_ID_RESPONSE, tag); + iu.body.response.response_code = code; + let iu_len = size_of::() + size_of::(); + Self::fill_packet(packet, &mut iu, iu_len); + } + + fn fill_sense(packet: &mut UsbPacket, tag: u16, status: u8, sense: &ScsiSense) { + let mut iu = UasIU::new(UAS_IU_ID_SENSE, tag); + // SAFETY: iu is guaranteed to be of type status + let iu_sense = unsafe { &mut iu.body.sense }; + + iu_sense.status = status; + iu_sense.status_qualifier = 0_u16.to_be(); + iu_sense.sense_length = 0_u16.to_be(); + + if status != GOOD { + iu_sense.sense_length = 18_u16.to_be(); + iu_sense.sense_data[0] = 0x71; // Error code: deferred errors + iu_sense.sense_data[2] = sense.key; + iu_sense.sense_data[7] = 10; // Additional sense length: total length - 8 + iu_sense.sense_data[12] = sense.asc; + iu_sense.sense_data[13] = sense.ascq; + } + + let sense_len = iu_sense.sense_length as usize; + let real_sense_len = size_of::() - iu_sense.sense_data.len() + sense_len; + let iu_len = size_of::() + real_sense_len; + trace::usb_uas_fill_sense(status, iu_len, sense_len); + Self::fill_packet(packet, &mut iu, iu_len); + } + + fn fill_fake_sense(packet: &mut UsbPacket, tag: u16, sense: &ScsiSense) { + let mut iu = UasIU::new(UAS_IU_ID_SENSE, tag); + // SAFETY: iu is guaranteed to be of type status + let iu_sense = unsafe { &mut iu.body.sense }; + + iu_sense.status = CHECK_CONDITION; + iu_sense.status_qualifier = 0_u16.to_be(); + iu_sense.sense_length = 18_u16.to_be(); + iu_sense.sense_data[0] = 0x70; // Error code: current errors + iu_sense.sense_data[2] = sense.key; + iu_sense.sense_data[7] = 10; // Additional sense length: total length - 8 + iu_sense.sense_data[12] = sense.asc; + iu_sense.sense_data[13] = sense.ascq; + + let iu_len = size_of::() + size_of::(); + trace::usb_uas_fill_fake_sense(CHECK_CONDITION, iu_len, 18); + Self::fill_packet(packet, &mut iu, iu_len); + } + + fn fill_read_ready(packet: &mut UsbPacket, tag: u16) { + let mut iu = UasIU::new(UAS_IU_ID_READ_READY, tag); + let iu_len = size_of::(); + Self::fill_packet(packet, &mut iu, iu_len); + } + + fn fill_write_ready(packet: &mut UsbPacket, tag: u16) { + let mut iu = UasIU::new(UAS_IU_ID_WRITE_READY, tag); + let iu_len = size_of::(); + Self::fill_packet(packet, &mut iu, iu_len); + } + + fn fill_packet(packet: &mut UsbPacket, iu: &mut UasIU, iu_len: usize) { + let iov_size = packet.get_iovecs_size() as usize; + let iu_len = min(iov_size, iu_len); + trace::usb_uas_fill_packet(iov_size); + packet.transfer_packet(iu.as_mut_bytes(), iu_len); + } + + fn try_start_next_transfer(&mut self, stream: usize) -> UasPacketStatus { + let command = self.peek_next_command(stream); + + if let Some(command) = command { + // SAFETY: iu is guaranteed to be of type command + let cdb = unsafe { &command.body.command.cdb }; + let xfer_len = scsi_cdb_xfer(cdb, Arc::clone(&self.scsi_device)); + trace::usb_uas_try_start_next_transfer(self.device_id(), xfer_len); + + if xfer_len > 0 { + self.try_start_next_data(stream) + } else { + self.try_start_next_non_data(stream) + } + } else { + debug!( + "UAS {} device no inflight command when trying to start the next transfer.", + self.device_id() + ); + UasPacketStatus::Pending + } + } + + fn try_start_next_data(&mut self, stream: usize) -> UasPacketStatus { + let status = self.peek_next_status(stream); + + if status.is_none() { + debug!( + "UAS {} device no inflight status when trying to start the next data transfer.", + self.device_id() + ); + return UasPacketStatus::Pending; + } + + if !self.data_ready_sent { + return self.fill_data_ready(stream); + } + + if self.data[stream].is_some() { + self.start_next_transfer(stream) + } else { + debug!( + "UAS {} device no inflight data when trying to start the next data transfer.", + self.device_id() + ); + UasPacketStatus::Pending + } + } + + fn fill_data_ready(&mut self, stream: usize) -> UasPacketStatus { + // SAFETY: status must have been checked in try_start_next_data + let status = self.take_next_status(stream); + let mut locked_status = status.lock().unwrap(); + + // SAFETY: command must have been checked in try_start_next_transfer + let iu = self.peek_next_command(stream).unwrap(); + let tag = u16::from_be(iu.header.tag); + + // SAFETY: iu is guaranteed to be of type command + let cdb = unsafe { &iu.body.command.cdb }; + let xfer_mode = scsi_cdb_xfer_mode(cdb); + + match xfer_mode { + ScsiXferMode::ScsiXferFromDev => Self::fill_read_ready(&mut locked_status, tag), + ScsiXferMode::ScsiXferToDev => Self::fill_write_ready(&mut locked_status, tag), + ScsiXferMode::ScsiXferNone => { + warn!( + "UAS {} device cannot fill data ready, operation {} is not a data transfer.", + self.device_id(), + cdb[0] + ); + Self::fill_fake_sense(&mut locked_status, tag, &SCSI_SENSE_INVALID_PARAM_VALUE); + } + } + + let status_async = locked_status.is_async; + drop(locked_status); + + if status_async { + complete_async_packet(&status); + } + + self.data_ready_sent = true; + trace::usb_uas_fill_data_ready(self.device_id(), self.data_ready_sent); + UasPacketStatus::Completed + } + + fn try_start_next_non_data(&mut self, stream: usize) -> UasPacketStatus { + let status = self.peek_next_status(stream); + + if status.is_none() { + debug!( + "UAS {} device no inflight status when trying to start the next non-data transfer.", + self.device_id() + ); + return UasPacketStatus::Pending; + } + + self.start_next_transfer(stream) + } + + fn start_next_transfer(&mut self, stream: usize) -> UasPacketStatus { + trace::usb_uas_start_next_transfer(self.device_id(), stream); + + // SAFETY: status must have been checked in try_start_next_data or try_start_next_non_data + let status = self.take_next_status(stream); + + // SAFETY: command must have been checked in try_start_next_transfer + let command = self.take_next_command(stream); + + let mut uas_request = UasRequest::new(&status, &command); + uas_request.data = self.data[stream].take(); + + let result = match command.header.id { + UAS_IU_ID_COMMAND => self.handle_iu_command(&command, uas_request), + UAS_IU_ID_TASK_MGMT => self.handle_iu_task_management(&command, uas_request), + _ => Err(anyhow!("impossible command IU {}", command.header.id)), + }; + + self.data_ready_sent = false; + self.try_start_next_transfer(stream); + + match result { + Ok(result) => result, + Err(err) => { + error!("UAS {} device error: {:#?}.", self.device_id(), err); + UasPacketStatus::Completed + } + } + } +} + +impl UsbDevice for UsbUas { + fn usb_device_base(&self) -> &UsbDeviceBase { + &self.base + } + + fn usb_device_base_mut(&mut self) -> &mut UsbDeviceBase { + &mut self.base + } + + fn realize(mut self) -> Result>> { + info!("UAS {} device realize.", self.device_id()); + self.base.reset_usb_endpoint(); + let mut desc_strings: Vec = + UAS_DESC_STRINGS.iter().map(|str| str.to_string()).collect(); + let prefix = &desc_strings[UsbUasStringId::SerialNumber as usize]; + desc_strings[UsbUasStringId::SerialNumber as usize] = + self.base.generate_serial_number(prefix); + + match self.base.speed { + USB_SPEED_HIGH => self + .base + .init_descriptor(DESC_DEVICE_UAS_HIGH.clone(), desc_strings)?, + USB_SPEED_SUPER => self + .base + .init_descriptor(DESC_DEVICE_UAS_SUPER.clone(), desc_strings)?, + _ => bail!("USB UAS unsupported device speed {}.", self.base.speed), + } + + // NOTE: "aio=off,direct=false" must be configured and other aio/direct values are not + // supported. + let mut locked_scsi_device = self.scsi_device.lock().unwrap(); + locked_scsi_device.realize(None)?; + locked_scsi_device.parent_bus = Arc::downgrade(&self.scsi_bus); + drop(locked_scsi_device); + self.scsi_bus + .lock() + .unwrap() + .devices + .insert((0, 0), Arc::clone(&self.scsi_device)); + + let uas = Arc::new(Mutex::new(self)); + Ok(uas) + } + + fn cancel_packet(&mut self, _packet: &Arc>) { + self.cancel_io(); + } + + fn reset(&mut self) { + info!("UAS {} device reset.", self.device_id()); + self.base.remote_wakeup = 0; + self.base.addr = 0; + self.cancel_io(); + } + + fn handle_control(&mut self, packet: &Arc>, device_req: &UsbDeviceRequest) { + let mut locked_packet = packet.lock().unwrap(); + trace::usb_uas_handle_control( + locked_packet.packet_id, + self.device_id(), + device_req.as_bytes(), + ); + + match self + .base + .handle_control_for_descriptor(&mut locked_packet, device_req) + { + Ok(handled) => { + if handled { + debug!( + "UAS {} device control handled by descriptor, return directly.", + self.device_id() + ); + return; + } + + error!( + "UAS {} device unhandled control request {:?}.", + self.device_id(), + device_req + ); + locked_packet.status = UsbPacketStatus::Stall; + } + Err(err) => { + warn!( + "{} received incorrect UAS descriptor message: {:?}", + self.device_id(), + err + ); + locked_packet.status = UsbPacketStatus::Stall; + } + } + } + + fn handle_data(&mut self, packet: &Arc>) { + let locked_packet = packet.lock().unwrap(); + let stream = locked_packet.stream as usize; + let ep_number = locked_packet.ep_number; + let packet_id = locked_packet.packet_id; + trace::usb_uas_handle_data(self.device_id(), ep_number, stream); + drop(locked_packet); + + if self.streams_enabled() && (stream > UAS_MAX_STREAMS || stream == 0) { + warn!("UAS {} device invalid stream {}.", self.device_id(), stream); + packet.lock().unwrap().status = UsbPacketStatus::Stall; + return; + } + + // NOTE: The architecture of this device is rather simple: it first waits for all of the + // required USB packets to arrive, and only then creates and sends an actual UAS request. + // The number of USB packets differs from 2 to 4 and depends on whether the command involves + // data transfers or not. Since the packets arrive in arbitrary order, some of them may be + // queued asynchronously. Note that the command packet is always completed right away. For + // all the other types of packets, their asynchronous status is determined by the return + // value of try_start_next_transfer(). All the asynchronously queued packets will be + // completed in scsi_request_complete_cb() callback. + match ep_number { + UAS_PIPE_ID_COMMAND => { + if self.streams_enabled() && self.commands_super[stream].is_some() { + warn!( + "UAS {} device multiple command packets on stream {}.", + self.device_id(), + stream + ); + packet.lock().unwrap().status = UsbPacketStatus::Stall; + return; + } + + let mut locked_packet = packet.lock().unwrap(); + let mut iu = UasIU::default(); + let iov_size = locked_packet.get_iovecs_size() as usize; + let iu_len = min(iov_size, size_of::()); + locked_packet.transfer_packet(iu.as_mut_bytes(), iu_len); + + trace::usb_uas_command_received(packet_id, self.device_id()); + self.queue_command(iu, stream); + self.try_start_next_transfer(stream); + trace::usb_uas_command_completed(packet_id, self.device_id()); + } + UAS_PIPE_ID_STATUS => { + if self.streams_enabled() && self.statuses_super[stream].is_some() { + warn!( + "UAS {} device multiple status packets on stream {}.", + self.device_id(), + stream + ); + packet.lock().unwrap().status = UsbPacketStatus::Stall; + return; + } + + trace::usb_uas_status_received(packet_id, self.device_id()); + self.queue_status(packet, stream); + let result = self.try_start_next_transfer(stream); + + match result { + UasPacketStatus::Completed => { + trace::usb_uas_status_completed(packet_id, self.device_id()) + } + UasPacketStatus::Pending => { + packet.lock().unwrap().is_async = true; + trace::usb_uas_status_queued_async(packet_id, self.device_id()); + } + } + } + UAS_PIPE_ID_DATA_OUT | UAS_PIPE_ID_DATA_IN => { + if self.data[stream].is_some() { + warn!( + "UAS {} device multiple data packets on stream {}.", + self.device_id(), + stream + ); + packet.lock().unwrap().status = UsbPacketStatus::Stall; + return; + } + + trace::usb_uas_data_received(packet_id, self.device_id()); + self.data[stream] = Some(Arc::clone(packet)); + let result = self.try_start_next_transfer(stream); + + match result { + UasPacketStatus::Completed => { + trace::usb_uas_data_completed(packet_id, self.device_id()) + } + UasPacketStatus::Pending => { + packet.lock().unwrap().is_async = true; + trace::usb_uas_data_queued_async(packet_id, self.device_id()); + } + } + } + _ => { + error!( + "UAS {} device bad endpoint number {}.", + self.device_id(), + ep_number + ); + } + } + } + + fn set_controller(&mut self, _controller: std::sync::Weak>) {} + + fn get_controller(&self) -> Option>> { + None + } + + fn get_wakeup_endpoint(&self) -> &UsbEndpoint { + self.base.get_endpoint(true, 1) + } +} + +impl UasRequest { + fn new(status: &Arc>, iu: &UasIU) -> Self { + Self { + data: None, + status: Arc::clone(status), + iu: *iu, + completed: false, + } + } + + fn complete(&mut self) { + let status = &self.status; + let status_async = status.lock().unwrap().is_async; + + // NOTE: Due to the specifics of this device, it waits for all of the required USB packets + // to arrive before starting an actual transfer. Therefore, some packets may arrive earlier + // than others, and they won't be completed right away (except for command packets) but + // rather queued asynchronously. A certain packet may also be async if it was the last to + // arrive and UasRequest didn't complete right away. + if status_async { + complete_async_packet(status); + } + + if let Some(data) = &self.data { + let data_async = data.lock().unwrap().is_async; + + if data_async { + complete_async_packet(data); + } + } + + self.completed = true; + } +} + +impl UasIUHeader { + fn new(id: u8, tag: u16) -> Self { + UasIUHeader { + id, + reserved: 0, + tag: tag.to_be(), + } + } +} + +impl UasIU { + fn new(id: u8, tag: u16) -> Self { + Self { + header: UasIUHeader::new(id, tag), + body: UasIUBody::default(), + } + } +} diff --git a/devices/src/usb/usbhost/host_usblib.rs b/devices/src/usb/usbhost/host_usblib.rs index 2ee7a9d..e558f81 100644 --- a/devices/src/usb/usbhost/host_usblib.rs +++ b/devices/src/usb/usbhost/host_usblib.rs @@ -16,6 +16,8 @@ use std::{ }; use libc::{c_int, c_uint, c_void, EPOLLIN, EPOLLOUT}; +#[cfg(all(target_arch = "aarch64", target_env = "ohos"))] +use libusb1_sys::{constants::LIBUSB_SUCCESS, libusb_context, libusb_set_option}; use libusb1_sys::{ constants::{ LIBUSB_ERROR_ACCESS, LIBUSB_ERROR_BUSY, LIBUSB_ERROR_INTERRUPTED, @@ -380,3 +382,20 @@ pub fn free_host_transfer(transfer: *mut libusb_transfer) { // SAFETY: have checked the validity of transfer before call libusb_free_transfer. unsafe { libusb1_sys::libusb_free_transfer(transfer) }; } + +#[cfg(all(target_arch = "aarch64", target_env = "ohos"))] +pub fn set_option(opt: u32) -> Result<()> { + // SAFETY: This function will only configure a specific option within libusb, null for ctx is valid. + let err = unsafe { + libusb_set_option( + std::ptr::null_mut() as *mut libusb_context, + opt, + std::ptr::null_mut() as *mut c_void, + ) + }; + if err != LIBUSB_SUCCESS { + return Err(from_libusb(err)); + } + + Ok(()) +} diff --git a/devices/src/usb/usbhost/mod.rs b/devices/src/usb/usbhost/mod.rs index e34d117..3da8cf0 100644 --- a/devices/src/usb/usbhost/mod.rs +++ b/devices/src/usb/usbhost/mod.rs @@ -11,6 +11,8 @@ // See the Mulan PSL v2 for more details. mod host_usblib; +#[cfg(all(target_arch = "aarch64", target_env = "ohos"))] +mod ohusb; use std::{ collections::LinkedList, @@ -20,7 +22,9 @@ use std::{ time::Duration, }; -use anyhow::{anyhow, bail, Result}; +#[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] +use anyhow::Context as anyhowContext; +use anyhow::{anyhow, Result}; use clap::Parser; use libc::c_int; use libusb1_sys::{ @@ -50,6 +54,8 @@ use machine_manager::{ event_loop::{register_event_helper, unregister_event_helper}, temp_cleaner::{ExitNotifier, TempCleaner}, }; +#[cfg(all(target_arch = "aarch64", target_env = "ohos"))] +use ohusb::OhUsbDev; use util::{ byte_code::ByteCode, link_list::{List, Node}, @@ -357,8 +363,10 @@ impl IsoQueue { } #[derive(Parser, Clone, Debug, Default)] -#[command(name = "usb_host")] +#[command(no_binary_name(true))] pub struct UsbHostConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] id: String, #[arg(long, default_value = "0")] @@ -402,6 +410,8 @@ pub struct UsbHost { iso_queues: Arc>>>>, iso_urb_frames: u32, iso_urb_count: u32, + #[cfg(all(target_arch = "aarch64", target_env = "ohos"))] + oh_dev: OhUsbDev, } // SAFETY: Send and Sync is not auto-implemented for util::link_list::List. @@ -412,6 +422,9 @@ unsafe impl Send for UsbHost {} impl UsbHost { pub fn new(config: UsbHostConfig) -> Result { + #[cfg(all(target_arch = "aarch64", target_env = "ohos"))] + let oh_dev = OhUsbDev::new()?; + let mut context = Context::new()?; context.set_log_level(rusb::LogLevel::None); let iso_urb_frames = config.iso_urb_frames; @@ -432,9 +445,12 @@ impl UsbHost { iso_queues: Arc::new(Mutex::new(LinkedList::new())), iso_urb_frames, iso_urb_count, + #[cfg(all(target_arch = "aarch64", target_env = "ohos"))] + oh_dev, }) } + #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] fn find_libdev(&self) -> Option> { if self.config.vendorid != 0 && self.config.productid != 0 { self.find_dev_by_vendor_product() @@ -447,6 +463,7 @@ impl UsbHost { } } + #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] fn find_dev_by_bus_addr(&self) -> Option> { self.context .devices() @@ -463,6 +480,7 @@ impl UsbHost { .unwrap_or_else(|| None) } + #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] fn find_dev_by_vendor_product(&self) -> Option> { self.context .devices() @@ -480,6 +498,7 @@ impl UsbHost { .unwrap_or_else(|| None) } + #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] fn find_dev_by_bus_port(&self) -> Option> { let hostport: Vec<&str> = self.config.hostport.as_ref().unwrap().split('.').collect(); let mut port: Vec = Vec::new(); @@ -642,8 +661,7 @@ impl UsbHost { } } - fn open_and_init(&mut self) -> Result<()> { - self.handle = Some(self.libdev.as_ref().unwrap().open()?); + fn init_usbdev(&mut self) -> Result<()> { self.config.hostbus = self.libdev.as_ref().unwrap().bus_number(); self.config.hostaddr = self.libdev.as_ref().unwrap().address(); trace::usb_host_open_started(self.config.hostbus, self.config.hostaddr); @@ -980,6 +998,26 @@ impl UsbHost { locked_packet.is_async = true; } + + #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] + fn open_usbdev(&mut self) -> Result<()> { + self.libdev = Some( + self.find_libdev() + .with_context(|| format!("Invalid USB host config: {:?}", self.config))?, + ); + self.handle = Some(self.libdev.as_ref().unwrap().open()?); + Ok(()) + } + + #[cfg(all(target_arch = "aarch64", target_env = "ohos"))] + fn open_usbdev(&mut self) -> Result<()> { + self.handle = Some( + self.oh_dev + .open(self.config.clone(), self.context.clone())?, + ); + self.libdev = Some(self.handle.as_ref().unwrap().device()); + Ok(()) + } } impl Drop for UsbHost { @@ -1021,13 +1059,9 @@ impl UsbDevice for UsbHost { } fn realize(mut self) -> Result>> { - self.libdev = self.find_libdev(); - if self.libdev.is_none() { - bail!("Invalid USB host config: {:?}", self.config); - } - info!("Open and init usbhost device: {:?}", self.config); - self.open_and_init()?; + self.open_usbdev()?; + self.init_usbdev()?; let usbhost = Arc::new(Mutex::new(self)); let notifiers = EventNotifierHelper::internal_notifiers(usbhost.clone()); @@ -1045,6 +1079,8 @@ impl UsbDevice for UsbHost { Ok(()) } + fn cancel_packet(&mut self, _packet: &Arc>) {} + fn reset(&mut self) { info!("Usb Host device {} reset", self.device_id()); if self.handle.is_none() { @@ -1292,7 +1328,7 @@ impl UsbDevice for UsbHost { } } -fn check_device_valid(device: &Device) -> bool { +pub fn check_device_valid(device: &Device) -> bool { let ddesc = match device.device_descriptor() { Ok(ddesc) => ddesc, Err(_) => return false, diff --git a/devices/src/usb/usbhost/ohusb.rs b/devices/src/usb/usbhost/ohusb.rs new file mode 100644 index 0000000..469a17a --- /dev/null +++ b/devices/src/usb/usbhost/ohusb.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::os::fd::AsRawFd; +use std::ptr; + +use anyhow::{bail, Context as anyhowContext, Result}; +use libusb1_sys::constants::LIBUSB_OPTION_NO_DEVICE_DISCOVERY; +use log::{error, info}; +use rusb::{Context, DeviceHandle, UsbContext}; + +use super::host_usblib::set_option; +use super::{check_device_valid, UsbHostConfig}; +use util::ohos_binding::usb::*; + +pub struct OhUsbDev { + dev: OhusbDevice, + lib: OhUsb, +} + +impl Drop for OhUsbDev { + fn drop(&mut self) { + if let Err(e) = self.lib.close_device(ptr::addr_of_mut!(self.dev)) { + error!("Failed to close usb device with error {:?}", e) + } + } +} + +impl OhUsbDev { + pub fn new() -> Result { + // In combination with libusb_wrap_sys_device(), in order to access a device directly without prior device scanning on ohos. + set_option(LIBUSB_OPTION_NO_DEVICE_DISCOVERY)?; + + Ok(Self { + dev: OhusbDevice { + busNum: u8::MAX, + devAddr: u8::MAX, + fd: -1, + }, + lib: OhUsb::new()?, + }) + } + + pub fn open(&mut self, cfg: UsbHostConfig, ctx: Context) -> Result> { + self.dev.busNum = cfg.hostbus; + self.dev.devAddr = cfg.hostaddr; + + match self.lib.open_device(ptr::addr_of_mut!(self.dev))? { + 0 => { + if self.dev.fd < 0 { + bail!( + "Failed to open usb device due to invalid fd {}", + self.dev.fd + ); + } + } + _ => bail!("Failed to open usb device"), + } + info!("OH USB: open_device: returned fd is {}", self.dev.fd); + + // SAFETY: fd is valid. + let handle = unsafe { + ctx.open_device_with_fd(self.dev.fd.as_raw_fd()) + .with_context(|| format!("os last error: {:?}", std::io::Error::last_os_error()))? + }; + + if !check_device_valid(&handle.device()) { + bail!("Invalid USB host config: {:?}", cfg); + } + + Ok(handle) + } +} diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs index 178aafc..a588771 100644 --- a/devices/src/usb/xhci/xhci_controller.rs +++ b/devices/src/usb/xhci/xhci_controller.rs @@ -690,6 +690,7 @@ pub struct XhciDevice { /// Runtime Register. mfindex_start: Duration, timer_id: Option, + packet_count: u32, } impl XhciDevice { @@ -726,6 +727,7 @@ impl XhciDevice { } let xhci = XhciDevice { + packet_count: 0, oper, usb_ports: Vec::new(), numports_3: p3, @@ -923,6 +925,11 @@ impl XhciDevice { Ok(()) } + fn generate_packet_id(&mut self) -> u32 { + self.packet_count = self.packet_count.wrapping_add(1); + self.packet_count + } + fn get_slot_id(&self, evt: &mut XhciEvent, trb: &XhciTRB) -> u32 { let slot_id = (trb.control >> TRB_CR_SLOTID_SHIFT) & TRB_CR_SLOTID_MASK; if slot_id < 1 || slot_id > self.slots.len() as u32 { @@ -1075,7 +1082,7 @@ impl XhciDevice { for i in 1..=self.slots[(slot_id - 1) as usize].endpoints.len() as u32 { let epctx = &mut self.slots[(slot_id - 1) as usize].endpoints[(i - 1) as usize]; if epctx.enabled { - self.flush_ep_transfer(slot_id, i, TRBCCode::Invalid)?; + self.cancel_all_ep_transfers(slot_id, i, TRBCCode::Invalid)?; } } self.slots[(slot_id - 1) as usize].usb_port = None; @@ -1182,11 +1189,15 @@ impl XhciDevice { index: 0, length: 0, }; + let target_dev = Arc::downgrade(dev) as Weak>; + let packet_id = self.generate_packet_id(); let p = Arc::new(Mutex::new(UsbPacket::new( + packet_id, USB_TOKEN_OUT as u32, 0, Vec::new(), None, + Some(target_dev), ))); trace::usb_handle_control(&locked_dev.usb_device_base().base.id, &device_req); locked_dev.handle_control(&p, &device_req); @@ -1432,7 +1443,7 @@ impl XhciDevice { trace::usb_xhci_unimplemented(&"Endpoint already disabled".to_string()); return Ok(TRBCCode::Success); } - self.flush_ep_transfer(slot_id, ep_id, TRBCCode::Invalid)?; + self.cancel_all_ep_transfers(slot_id, ep_id, TRBCCode::Invalid)?; let epctx = &mut self.slots[(slot_id - 1) as usize].endpoints[(ep_id - 1) as usize]; if self.oper.dcbaap != 0 { epctx.set_state(EP_DISABLED)?; @@ -1465,7 +1476,7 @@ impl XhciDevice { ); return Ok(TRBCCode::ContextStateError); } - if self.flush_ep_transfer(slot_id, ep_id, TRBCCode::Stopped)? > 0 { + if self.cancel_all_ep_transfers(slot_id, ep_id, TRBCCode::Stopped)? > 0 { trace::usb_xhci_unimplemented(&format!( "Endpoint stop when xfers running, slot_id {} epid {}", slot_id, ep_id @@ -1495,7 +1506,7 @@ impl XhciDevice { error!("Endpoint is not halted"); return Ok(TRBCCode::ContextStateError); } - if self.flush_ep_transfer(slot_id, ep_id, TRBCCode::Invalid)? > 0 { + if self.cancel_all_ep_transfers(slot_id, ep_id, TRBCCode::Invalid)? > 0 { warn!("endpoint reset when xfers running!"); } let slot = &mut self.slots[(slot_id - 1) as usize]; @@ -1925,13 +1936,33 @@ impl XhciDevice { trb.parameter }; - self.mem_space - .get_address_map(GuestAddress(dma_addr), chunk as u64, &mut vec)?; + self.mem_space.get_address_map( + &None, + GuestAddress(dma_addr), + chunk as u64, + &mut vec, + )?; } } + + let target_dev = + if let Ok(target_dev) = self.get_usb_dev(locked_xfer.slotid, locked_xfer.epid) { + Some(Arc::downgrade(&target_dev) as Weak>) + } else { + None + }; + + let packet_id = self.generate_packet_id(); let (_, ep_number) = endpoint_id_to_number(locked_xfer.epid as u8); let xfer_ops = Arc::downgrade(xfer) as Weak>; - let packet = UsbPacket::new(dir as u32, ep_number, vec, Some(xfer_ops)); + let packet = UsbPacket::new( + packet_id, + dir as u32, + ep_number, + vec, + Some(xfer_ops), + target_dev, + ); Ok(Arc::new(Mutex::new(packet))) } @@ -1971,8 +2002,8 @@ impl XhciDevice { } /// Flush transfer in endpoint in some case such as stop endpoint. - fn flush_ep_transfer(&mut self, slotid: u32, epid: u32, report: TRBCCode) -> Result { - trace::usb_xhci_flush_ep_transfer(&slotid, &epid); + fn cancel_all_ep_transfers(&mut self, slotid: u32, epid: u32, report: TRBCCode) -> Result { + trace::usb_xhci_cancel_all_ep_transfers(&slotid, &epid); let mut cnt = 0; let mut report = report; while let Some(xfer) = self.slots[(slotid - 1) as usize].endpoints[(epid - 1) as usize] @@ -1983,7 +2014,7 @@ impl XhciDevice { if locked_xfer.complete { continue; } - cnt += self.do_ep_transfer(slotid, epid, &mut locked_xfer, report)?; + cnt += self.cancel_one_ep_transfer(slotid, epid, &mut locked_xfer, report)?; if cnt != 0 { // Only report once. report = TRBCCode::Invalid; @@ -1995,7 +2026,7 @@ impl XhciDevice { Ok(cnt) } - fn do_ep_transfer( + fn cancel_one_ep_transfer( &mut self, slotid: u32, ep_id: u32, @@ -2008,6 +2039,15 @@ impl XhciDevice { if report != TRBCCode::Invalid { xfer.status = report; xfer.submit_transfer()?; + let locked_packet = xfer.packet.lock().unwrap(); + + if let Some(usb_dev) = locked_packet.target_dev.as_ref() { + if let Some(usb_dev) = usb_dev.clone().upgrade() { + drop(locked_packet); + let mut locked_usb_dev = usb_dev.lock().unwrap(); + locked_usb_dev.cancel_packet(&xfer.packet); + } + } } xfer.running_async = false; killed = 1; diff --git a/devices/src/usb/xhci/xhci_pci.rs b/devices/src/usb/xhci/xhci_pci.rs index 0b3373c..0e8ffd2 100644 --- a/devices/src/usb/xhci/xhci_pci.rs +++ b/devices/src/usb/xhci/xhci_pci.rs @@ -39,7 +39,8 @@ use address_space::{AddressRange, AddressSpace, Region, RegionIoEventFd}; use machine_manager::config::{get_pci_df, valid_id}; use machine_manager::event_loop::register_event_helper; use util::loop_context::{ - read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, + create_new_eventfd, read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, + NotifierOperation, }; /// 5.2 PCI Configuration Registers(USB) @@ -67,8 +68,10 @@ const XHCI_MSIX_PBA_OFFSET: u32 = 0x3800; /// XHCI controller configuration. #[derive(Parser, Clone, Debug, Default)] -#[command(name = "nec-usb-xhci")] +#[command(no_binary_name(true))] pub struct XhciConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] id: Option, #[arg(long)] @@ -120,7 +123,7 @@ impl XhciPciDevice { XHCI_PCI_CONFIG_LENGTH as u64, "XhciPciContainer", ), - doorbell_fd: Arc::new(EventFd::new(libc::EFD_NONBLOCK).unwrap()), + doorbell_fd: Arc::new(create_new_eventfd().unwrap()), delete_evts: Vec::new(), iothread: config.iothread.clone(), } diff --git a/docs/config_guidebook.md b/docs/config_guidebook.md index 7a3c87e..1619540 100644 --- a/docs/config_guidebook.md +++ b/docs/config_guidebook.md @@ -938,7 +938,7 @@ Six properties can be set for Virtio-Scsi controller. * bus: bus number of the device. * addr: including slot number and function number. * iothread: indicate which iothread will be used, if not specified the main thread will be used. (optional) -* num-queues: the optional num-queues attribute controls the number of request queues to be used for the scsi controller. If not set, the default block queue number is 1. The max queues number supported is no more than 32. (optional) +* num-queues: the optional num-queues attribute controls the number of request queues to be used for the scsi controller. If not set, the default queue number is the smaller one of vCPU count and the max queues number (e.g, min(vcpu_count, 32)). The max queues number supported is no more than 32. (optional) * queue-size: the optional virtqueue size for all the queues. Configuration range is (2, 1024] and queue size must be power of 2. Default queue size is 256. ```shell -device virtio-scsi-pci,id=,bus=,addr=<0x3>[,multifunction={on|off}][,iothread=][,num-queues=][,queue-size=] @@ -1036,7 +1036,7 @@ Sample Configuration: ```shell -object authz-simple,id=authz0,identity=username -object tls-creds-x509,id=vnc-tls-creds0,dir=/etc/pki/vnc --vnc 0.0.0.0:0,tls-creds=vnc-tls-creds0,sasl=on,sasl-authz=authz0 +-vnc 0.0.0.0:0,tls-creds=vnc-tls-creds0,sasl,sasl-authz=authz0 ``` Note: 1. Only one client can be connected at the same time. Follow-up clients connections will result in failure. 2. TLS encrypted transmission can be configured separately, but authentication must be used together with encryption. @@ -1107,14 +1107,15 @@ Usually used in conjunction with VNC, the final images is rendered to the VNC cl Sample Configuration: ```shell --device virtio-gpu-pci,id=,bus=pcie.0,addr=0x2.0x0[,max_outputs=][,edid=true|false][,xres=][,yres= ][,max_hostmem=] +-device virtio-gpu-pci,id=,bus=pcie.0,addr=0x2.0x0[,max_outputs=][,edid=true|false][,xres=][,yres= ][,max_hostmem=][,enable_bar0=true|false] ``` -In addition to the required slot information, five optional properties are supported for virtio-gpu. +In addition to the required slot information, six optional properties are supported for virtio-gpu. * max_outputs: Number of screens supported by the current graphics card. The maximum value is 16. (can switch by using ctrl + alt + , for details, see vnc Client switchover) * edid: Edid feature, the virtual machine's kernel may checks this feature for HiDPi. You are advised to set to true. * xres/yres: The size of the login windows. * max_hostmem: The maximum memory that a graphics card can occupy on the host is expressed in byte. You are advised to set not less than 256MiB, otherwise the final supported resolutions is affected. +* enable_bar0: Enable a 64M bar0 in virtio-gpu. Note: 1. Only virtio-gpu 2D supported. diff --git a/docs/stratovirt-img.md b/docs/stratovirt-img.md index e44be8d..7021e56 100644 --- a/docs/stratovirt-img.md +++ b/docs/stratovirt-img.md @@ -35,6 +35,16 @@ stratovirt-img create -f qcow2 -o cluster-size=65536 img_path img_size Note: 1. The cluster size can be only be set for `qcow2` or default to 65536. 2. Disk format is default to raw. +## Info + +Query the information of virtual disk. + +Sample Configuration: + +```shell +stratovirt-img info img_path +``` + ## Check Check if there are some mistakes on the image and choose to fix. diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index 97f4190..b766c95 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -8,7 +8,7 @@ license = "Mulan PSL v2" [dependencies] anyhow = "1.0" thiserror = "1.0" -kvm-bindings = { version = "0.10.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } kvm-ioctls = "0.19.1" libc = "0.2" log = "0.4" diff --git a/hypervisor/src/kvm/aarch64/mod.rs b/hypervisor/src/kvm/aarch64/mod.rs index 5d2a938..0721236 100644 --- a/hypervisor/src/kvm/aarch64/mod.rs +++ b/hypervisor/src/kvm/aarch64/mod.rs @@ -135,7 +135,7 @@ impl KvmCpu { pub fn arch_set_boot_config( &self, arch_cpu: Arc>, - boot_config: &Option, + boot_config: &CPUBootConfig, vcpu_config: &CPUFeatures, ) -> Result<()> { let mut kvi = self.kvi.lock().unwrap(); @@ -169,9 +169,7 @@ impl KvmCpu { } drop(kvi); - if let Some(cfg) = boot_config { - arch_cpu.lock().unwrap().set_core_reg(cfg); - } + arch_cpu.lock().unwrap().set_core_reg(boot_config); self.arch_vcpu_init()?; diff --git a/hypervisor/src/kvm/interrupt.rs b/hypervisor/src/kvm/interrupt.rs index ea9e779..c98eba8 100644 --- a/hypervisor/src/kvm/interrupt.rs +++ b/hypervisor/src/kvm/interrupt.rs @@ -34,9 +34,9 @@ const IOAPIC_NUM_PINS: u32 = 24; const PIC_MASTER_PINS: u32 = 8; #[cfg(target_arch = "x86_64")] const PIC_SLACE_PINS: u32 = 8; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const IOCHIP_NUM_PINS: u32 = 192; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const KVM_IRQCHIP: u32 = 0; /// Return the max number kvm supports. @@ -122,9 +122,9 @@ impl IrqRouteTable { } /// Init irq route table in arch aarch64. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn init_irq_route_table(&mut self) { - for i in 0..IOCHIP_NUM_PINS { + for i in 0..20 { self.irq_routes .push(create_irq_route_entry(i, KVM_IRQCHIP, i)); // This unwrap() will never fail, it is safe. diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index b88aeed..5669655 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -12,6 +12,8 @@ #[cfg(target_arch = "aarch64")] pub mod aarch64; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -25,6 +27,8 @@ pub mod vm_state; pub use aarch64::gicv2::KvmGICv2; #[cfg(target_arch = "aarch64")] pub use aarch64::gicv3::{KvmGICv3, KvmGICv3Its}; +#[cfg(target_arch = "riscv64")] +pub use riscv64::aia::KvmAIA; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; @@ -63,6 +67,8 @@ use cpu::{ CpuLifecycleState, RegsIndex, CPU, VCPU_TASK_SIGNAL, }; use devices::{pci::MsiVector, IrqManager, LineIrqManager, MsiIrqManager, TriggerMode}; +#[cfg(target_arch = "riscv64")] +use devices::{AIAConfig, InterruptController, AIA}; #[cfg(target_arch = "aarch64")] use devices::{ GICVersion, GICv2, GICv3, GICv3ItsState, GICv3State, ICGICConfig, InterruptController, @@ -73,6 +79,8 @@ use machine_manager::machine::HypervisorType; #[cfg(target_arch = "aarch64")] use migration::snapshot::{GICV3_ITS_SNAPSHOT_ID, GICV3_SNAPSHOT_ID}; use migration::{MigrateMemSlot, MigrateOps, MigrationManager}; +#[cfg(target_arch = "riscv64")] +use riscv64::cpu_caps::RISCVCPUCaps as CPUCaps; use util::test_helper::is_test_enabled; #[cfg(target_arch = "x86_64")] use x86_64::cpu_caps::X86CPUCaps as CPUCaps; @@ -88,6 +96,7 @@ ioctl_iow_nr!(KVM_GET_DIRTY_LOG, KVMIO, 0x42, kvm_dirty_log); ioctl_iowr_nr!(KVM_CREATE_DEVICE, KVMIO, 0xe0, kvm_create_device); ioctl_io_nr!(KVM_GET_API_VERSION, KVMIO, 0x00); ioctl_ior_nr!(KVM_GET_MP_STATE, KVMIO, 0x98, kvm_mp_state); +#[cfg(not(target_arch = "riscv64"))] ioctl_ior_nr!(KVM_GET_VCPU_EVENTS, KVMIO, 0x9f, kvm_vcpu_events); ioctl_ior_nr!(KVM_GET_CLOCK, KVMIO, 0x7c, kvm_clock_data); ioctl_ior_nr!(KVM_GET_REGS, KVMIO, 0x81, kvm_regs); @@ -98,13 +107,8 @@ ioctl_iow_nr!(KVM_IRQFD, KVMIO, 0x76, kvm_irqfd); ioctl_iowr_nr!(KVM_GET_IRQCHIP, KVMIO, 0x62, kvm_irqchip); ioctl_iow_nr!(KVM_IRQ_LINE, KVMIO, 0x61, kvm_irq_level); ioctl_iow_nr!(KVM_SET_MP_STATE, KVMIO, 0x99, kvm_mp_state); +#[cfg(not(target_arch = "riscv64"))] ioctl_iow_nr!(KVM_SET_VCPU_EVENTS, KVMIO, 0xa0, kvm_vcpu_events); -#[cfg(target_arch = "x86_64")] -ioctl_iow_nr!(KVM_SET_PIT2, KVMIO, 0xa0, kvm_pit_state2); -#[cfg(target_arch = "x86_64")] -ioctl_iow_nr!(KVM_SET_CLOCK, KVMIO, 0x7b, kvm_clock_data); -#[cfg(target_arch = "x86_64")] -ioctl_ior_nr!(KVM_SET_IRQCHIP, KVMIO, 0x63, kvm_irqchip); #[allow(clippy::upper_case_acronyms)] #[derive(Default)] @@ -112,7 +116,7 @@ pub struct KvmHypervisor { pub fd: Option, pub vm_fd: Option>, pub mem_slots: Arc>>, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub irq_chip: Option>, } @@ -131,7 +135,7 @@ impl KvmHypervisor { fd: Some(kvm_fd), vm_fd, mem_slots: Arc::new(Mutex::new(HashMap::new())), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: None, }) } @@ -219,6 +223,24 @@ impl HypervisorOps for KvmHypervisor { } } + #[cfg(target_arch = "riscv64")] + fn create_interrupt_controller( + &mut self, + aia_conf: &AIAConfig, + ) -> Result> { + aia_conf.check_sanity()?; + + let create_aia = || { + let hypervisor_aia = KvmAIA::new(self.vm_fd.clone().unwrap(), aia_conf.vcpu_count)?; + let aia = Arc::new(AIA::new(Arc::new(hypervisor_aia), aia_conf)?); + // Need register AIA to MigrationManager here + + Ok(Arc::new(InterruptController::new(aia))) + }; + + create_aia() + } + #[cfg(target_arch = "x86_64")] fn create_interrupt_controller(&mut self) -> Result<()> { self.vm_fd @@ -242,7 +264,7 @@ impl HypervisorOps for KvmHypervisor { .with_context(|| "Create vcpu failed")?; Ok(Arc::new(KvmCpu::new( vcpu_id, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.vm_fd.clone(), vcpu_fd, ))) @@ -253,7 +275,7 @@ impl HypervisorOps for KvmHypervisor { let irqfd_enable = kvm.check_extension(Cap::Irqfd); let irq_route_table = Mutex::new(IrqRouteTable::new(self.fd.as_ref().unwrap())); let irq_manager = Arc::new(KVMInterruptManager::new( - irqfd_enable, + false, self.vm_fd.clone().unwrap(), irq_route_table, )); @@ -372,8 +394,11 @@ impl MigrateOps for KvmHypervisor { pub struct KvmCpu { id: u8, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd: Option>, + #[cfg(target_arch = "riscv64")] + fd: Arc>, + #[cfg(not(target_arch = "riscv64"))] fd: Arc, /// The capability of VCPU. caps: CPUCaps, @@ -385,13 +410,16 @@ pub struct KvmCpu { impl KvmCpu { pub fn new( id: u8, - #[cfg(target_arch = "aarch64")] vm_fd: Option>, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd: Option>, vcpu_fd: VcpuFd, ) -> Self { Self { id, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd, + #[cfg(target_arch = "riscv64")] + fd: Arc::new(Mutex::new(vcpu_fd)), + #[cfg(not(target_arch = "riscv64"))] fd: Arc::new(vcpu_fd), caps: CPUCaps::init_capabilities(), #[cfg(target_arch = "aarch64")] @@ -422,6 +450,7 @@ impl KvmCpu { .upgrade() .with_context(|| CpuError::NoMachineInterface)?; + #[cfg(not(target_arch = "riscv64"))] match self.fd.run() { Ok(run) => { trace::kvm_vcpu_run_exit(cpu.id, &run); @@ -458,7 +487,7 @@ impl KvmCpu { return Ok(false); } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] VcpuExit::SystemEvent(event, flags) => { if event == kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN { info!( @@ -517,6 +546,108 @@ impl KvmCpu { }; } } + + #[cfg(target_arch = "riscv64")] + { + let mut vcpu_fd = self.fd.lock().unwrap(); + let res = vcpu_fd.run(); + match res { + Ok(run) => { + trace::kvm_vcpu_run_exit(cpu.id, &run); + match run { + #[cfg(target_arch = "x86_64")] + VcpuExit::IoIn(addr, data) => { + vm.lock().unwrap().pio_in(u64::from(addr), data); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::IoOut(addr, data) => { + #[cfg(feature = "boot_time")] + capture_boot_signal(addr as u64, data); + + vm.lock().unwrap().pio_out(u64::from(addr), data); + } + VcpuExit::MmioRead(addr, data) => { + vm.lock().unwrap().mmio_read(addr, data); + } + VcpuExit::MmioWrite(addr, data) => { + #[cfg(all(target_arch = "aarch64", feature = "boot_time"))] + capture_boot_signal(addr, data); + + vm.lock().unwrap().mmio_write(addr, data); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::Hlt => { + info!("Vcpu{} received KVM_EXIT_HLT signal", cpu.id); + return Err(anyhow!(CpuError::VcpuHltEvent(cpu.id))); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::Shutdown => { + info!("Vcpu{} received an KVM_EXIT_SHUTDOWN signal", cpu.id); + cpu.guest_shutdown()?; + + return Ok(false); + } + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + VcpuExit::SystemEvent(event, flags) => { + if event == kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN { + info!( + "Vcpu{} received an KVM_SYSTEM_EVENT_SHUTDOWN signal", + cpu.id() + ); + cpu.guest_shutdown() + .with_context(|| "Some error occurred in guest shutdown")?; + return Ok(true); + } else if event == kvm_bindings::KVM_SYSTEM_EVENT_RESET { + info!("Vcpu{} received an KVM_SYSTEM_EVENT_RESET signal", cpu.id()); + cpu.guest_reset() + .with_context(|| "Some error occurred in guest reset")?; + return Ok(true); + } else { + error!( + "Vcpu{} received unexpected system event with type 0x{:x}, flags 0x{:?}", + cpu.id(), + event, + flags + ); + } + return Ok(false); + } + VcpuExit::FailEntry(reason, cpuid) => { + info!( + "Vcpu{} received KVM_EXIT_FAIL_ENTRY signal. the vcpu could not be run due to unknown reasons({})", + cpuid, reason + ); + return Ok(false); + } + VcpuExit::InternalError => { + info!("Vcpu{} received KVM_EXIT_INTERNAL_ERROR signal", cpu.id()); + return Ok(false); + } + r => { + return Err(anyhow!(CpuError::VcpuExitReason( + cpu.id(), + format!("{:?}", r) + ))); + } + } + } + Err(ref e) => { + match e.errno() { + libc::EAGAIN => {} + libc::EINTR => { + self.fd.lock().unwrap().set_kvm_immediate_exit(0); + } + _ => { + return Err(anyhow!(CpuError::UnhandledHypervisorExit( + cpu.id(), + e.errno() + ))); + } + }; + } + } + } + Ok(true) } @@ -550,13 +681,20 @@ impl CPUHypervisorOps for KvmCpu { fn set_boot_config( &self, arch_cpu: Arc>, - boot_config: &Option, + boot_config: &CPUBootConfig, #[cfg(target_arch = "aarch64")] vcpu_config: &CPUFeatures, ) -> Result<()> { + #[cfg(target_arch = "riscv64")] + { + arch_cpu.lock().unwrap().timer_regs = self.get_timer_regs()?; + arch_cpu.lock().unwrap().config_regs = self.get_config_regs()?; + } #[cfg(target_arch = "aarch64")] return self.arch_set_boot_config(arch_cpu, boot_config, vcpu_config); #[cfg(target_arch = "x86_64")] return self.arch_set_boot_config(arch_cpu, boot_config); + #[cfg(target_arch = "riscv64")] + return self.arch_set_boot_config(arch_cpu, boot_config); } fn get_one_reg(&self, reg_id: u64) -> Result { @@ -610,10 +748,6 @@ impl CPUHypervisorOps for KvmCpu { while let Ok(true) = cpu_thread_worker.ready_for_running() { #[cfg(not(test))] { - if is_test_enabled() { - thread::sleep(Duration::from_millis(5)); - continue; - } if !self .kvm_vcpu_exec(cpu_thread_worker.thread_cpu.clone()) .with_context(|| { @@ -641,6 +775,13 @@ impl CPUHypervisorOps for KvmCpu { Ok(()) } + #[cfg(target_arch = "riscv64")] + fn set_hypervisor_exit(&self) -> Result<()> { + self.fd.lock().unwrap().set_kvm_immediate_exit(1); + Ok(()) + } + + #[cfg(not(target_arch = "riscv64"))] fn set_hypervisor_exit(&self) -> Result<()> { self.fd.set_kvm_immediate_exit(1); Ok(()) @@ -763,6 +904,11 @@ impl KVMInterruptManager { let irqtype = KVM_ARM_IRQ_TYPE_SPI; irqtype << KVM_ARM_IRQ_TYPE_SHIFT | irq } + + #[cfg(target_arch = "riscv64")] + pub fn arch_map_irq(&self, gsi: u32) -> u32 { + gsi + } } impl LineIrqManager for KVMInterruptManager { @@ -829,6 +975,10 @@ impl LineIrqManager for KVMInterruptManager { } impl MsiIrqManager for KVMInterruptManager { + fn irqfd_enable(&self) -> bool { + self.irqfd_cap + } + fn allocate_irq(&self, vector: MsiVector) -> Result { let mut locked_irq_route_table = self.irq_route_table.lock().unwrap(); let gsi = locked_irq_route_table.allocate_gsi().map_err(|e| { @@ -888,7 +1038,7 @@ impl MsiIrqManager for KVMInterruptManager { trace::kvm_trigger_irqfd(irq_fd.as_ref().unwrap()); irq_fd.unwrap().write(1)?; } else { - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let flags: u32 = kvm_bindings::KVM_MSI_VALID_DEVID; #[cfg(target_arch = "x86_64")] let flags: u32 = 0; @@ -984,7 +1134,7 @@ mod test { } fn mmio_read(&self, addr: u64, data: &mut [u8]) -> bool { - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { data[3] = 0x0; data[2] = 0x0; @@ -1073,7 +1223,7 @@ mod test { let cpu = CPU::new(hypervisor_cpu.clone(), 0, x86_cpu, vm.clone()); // test `set_boot_config` function assert!(hypervisor_cpu - .set_boot_config(cpu.arch().clone(), &Some(cpu_config)) + .set_boot_config(cpu.arch().clone(), &cpu_config) .is_ok()); // test setup special registers @@ -1122,7 +1272,7 @@ mod test { let vcpu_fd = kvm_hyp.vm_fd.as_ref().unwrap().create_vcpu(0).unwrap(); let hypervisor_cpu = Arc::new(KvmCpu::new( 0, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] kvm_hyp.vm_fd.clone(), vcpu_fd, )); diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs new file mode 100644 index 0000000..3c56c2a --- /dev/null +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -0,0 +1,150 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::sync::Arc; + +use anyhow::{Context, Ok, Result}; +use kvm_ioctls::{DeviceFd, VmFd}; + +use super::KvmDevice; +use devices::AIAAccess; + +pub struct KvmAIA { + fd: DeviceFd, + vcpu_count: u32, +} + +impl KvmAIA { + pub fn new(vm_fd: Arc, vcpu_count: u32) -> Result { + let mut aia_device = kvm_bindings::kvm_create_device { + type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_RISCV_AIA, + fd: 0, + flags: 0, + }; + + let aia_fd = vm_fd + .create_device(&mut aia_device) + .expect("Cannot create AIA device, is SSAIA ext. enabled?"); + + Ok(Self { + fd: aia_fd, + vcpu_count, + }) + } +} + +impl AIAAccess for KvmAIA { + fn init_aia(&self, nr_irqs: u32, aia_addr: u64) -> Result<()> { + use kvm_bindings::{ + KVM_DEV_RISCV_AIA_ADDR_APLIC, KVM_DEV_RISCV_AIA_CONFIG_HART_BITS, + KVM_DEV_RISCV_AIA_CONFIG_IDS, KVM_DEV_RISCV_AIA_CONFIG_MODE, + KVM_DEV_RISCV_AIA_CONFIG_SRCS, KVM_DEV_RISCV_AIA_CTRL_INIT, KVM_DEV_RISCV_AIA_GRP_ADDR, + KVM_DEV_RISCV_AIA_GRP_CONFIG, KVM_DEV_RISCV_AIA_GRP_CTRL, + }; + + // Get AIA mode, variants: EMUL, HW_ACCL, AUTO + KvmDevice::kvm_device_check( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CONFIG, + KVM_DEV_RISCV_AIA_CONFIG_MODE as u64, + ) + .unwrap(); + + // Get number of interrupt IDs + KvmDevice::kvm_device_check( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CONFIG, + KVM_DEV_RISCV_AIA_CONFIG_IDS as u64, + ) + .unwrap(); + + // Calculate number of interrupt sources, or allocated lines + + // Set number of interrupt sources + KvmDevice::kvm_device_check( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CONFIG, + KVM_DEV_RISCV_AIA_CONFIG_SRCS as u64, + ) + .unwrap(); + + KvmDevice::kvm_device_access( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CONFIG, + KVM_DEV_RISCV_AIA_CONFIG_SRCS as u64, + &nr_irqs as *const u32 as u64, + true, + ) + .with_context(|| "Failed to set number of irq sources of APLIC")?; + + // Calculate AIA hart bits + + // Set AIA hart bits + KvmDevice::kvm_device_check( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CONFIG, + KVM_DEV_RISCV_AIA_CONFIG_HART_BITS as u64, + ) + .unwrap(); + + // Need complete logic here + let max_hart_index = self.vcpu_count as u64 - 1; + let hart_bits = std::cmp::max(64 - max_hart_index.leading_zeros(), 1); + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_HART_BITS), + &hart_bits as *const u32 as u64, + true, + ) + .with_context(|| "Failed to set HART index bits for IMSIC")?; + + // Set AIA device addresses + let aplic_addr = + aia_addr + (self.vcpu_count * kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE) as u64; + KvmDevice::kvm_device_access( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_ADDR, + KVM_DEV_RISCV_AIA_ADDR_APLIC as u64, + &aplic_addr as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set APLIC base address")?; + + for i in 0..self.vcpu_count { + let imsic = aia_addr + (i * kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE) as u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR, + u64::from(i + 1), + &imsic as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set IMSIC addr")?; + } + + // Finalize AIA. + KvmDevice::kvm_device_access( + &self.fd, + KVM_DEV_RISCV_AIA_GRP_CTRL, + KVM_DEV_RISCV_AIA_CTRL_INIT as u64, + 0, + true, + ) + .with_context(|| "KVM failed to finalize AIA") + } + + fn pause(&self) -> Result<()> { + // TODO + Ok(()) + } +} diff --git a/hypervisor/src/kvm/riscv64/config_regs.rs b/hypervisor/src/kvm/riscv64/config_regs.rs new file mode 100644 index 0000000..37eacd3 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/config_regs.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::mem::size_of; + +use kvm_bindings::{kvm_riscv_config, KVM_REG_RISCV, KVM_REG_RISCV_CONFIG, KVM_REG_SIZE_U64}; +use util::offset_of; + +/// RISCV cpu config register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L49 +pub enum RISCVConfigRegs { + ISA, +} + +impl Into for RISCVConfigRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVConfigRegs::ISA => { + offset_of!(kvm_riscv_config, isa) + } + }; + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_CONFIG) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/kvm/riscv64/core_regs.rs b/hypervisor/src/kvm/riscv64/core_regs.rs new file mode 100644 index 0000000..409c026 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/core_regs.rs @@ -0,0 +1,171 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::mem::size_of; + +use kvm_bindings::{ + kvm_riscv_core, user_regs_struct, KVM_REG_RISCV, KVM_REG_RISCV_CORE, KVM_REG_SIZE_U64, +}; +use util::offset_of; + +/// RISCV cpu core register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L54 +/// User-mode register state for core dumps, ptrace, sigcontext +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/ptrace.h#L19 +#[allow(dead_code)] +pub enum RISCVCoreRegs { + PC, + RA, + SP, + GP, + TP, + T0, + T1, + T2, + S0, + S1, + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + T3, + T4, + T5, + T6, + MODE, +} + +impl Into for RISCVCoreRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVCoreRegs::PC => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, pc) + } + RISCVCoreRegs::RA => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, ra) + } + RISCVCoreRegs::SP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, sp) + } + RISCVCoreRegs::GP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, gp) + } + RISCVCoreRegs::TP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, tp) + } + RISCVCoreRegs::T0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t0) + } + RISCVCoreRegs::T1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t1) + } + RISCVCoreRegs::T2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t2) + } + RISCVCoreRegs::S0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s0) + } + RISCVCoreRegs::S1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s1) + } + RISCVCoreRegs::A0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a0) + } + RISCVCoreRegs::A1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a1) + } + RISCVCoreRegs::A2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a2) + } + RISCVCoreRegs::A3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a3) + } + RISCVCoreRegs::A4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a4) + } + RISCVCoreRegs::A5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a5) + } + RISCVCoreRegs::A6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a6) + } + RISCVCoreRegs::A7 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a7) + } + RISCVCoreRegs::S2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s2) + } + RISCVCoreRegs::S3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s3) + } + RISCVCoreRegs::S4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s4) + } + RISCVCoreRegs::S5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s5) + } + RISCVCoreRegs::S6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s6) + } + RISCVCoreRegs::S7 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s7) + } + RISCVCoreRegs::S8 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s8) + } + RISCVCoreRegs::S9 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s9) + } + RISCVCoreRegs::S10 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s10) + } + RISCVCoreRegs::S11 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s11) + } + RISCVCoreRegs::T3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t3) + } + RISCVCoreRegs::T4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t4) + } + RISCVCoreRegs::T5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t5) + } + RISCVCoreRegs::T6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t6) + } + RISCVCoreRegs::MODE => { + offset_of!(kvm_riscv_core, mode) + } + }; + + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_CORE) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/kvm/riscv64/cpu_caps.rs b/hypervisor/src/kvm/riscv64/cpu_caps.rs new file mode 100644 index 0000000..56bad05 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/cpu_caps.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use kvm_ioctls::Cap; +use kvm_ioctls::Kvm; + +// Capabilities for RISC-V cpu. +#[derive(Debug, Clone)] +pub struct RISCVCPUCaps { + pub irq_chip: bool, + pub ioevent_fd: bool, + pub irq_fd: bool, + pub user_mem: bool, + pub mp_state: bool, +} + +impl RISCVCPUCaps { + /// Initialize ArmCPUCaps instance. + pub fn init_capabilities() -> Self { + let kvm = Kvm::new().unwrap(); + RISCVCPUCaps { + irq_chip: kvm.check_extension(Cap::Irqchip), + ioevent_fd: kvm.check_extension(Cap::Ioeventfd), + irq_fd: kvm.check_extension(Cap::Irqfd), + user_mem: kvm.check_extension(Cap::UserMemory), + mp_state: kvm.check_extension(Cap::MpState), + } + } +} diff --git a/hypervisor/src/kvm/riscv64/mod.rs b/hypervisor/src/kvm/riscv64/mod.rs new file mode 100644 index 0000000..c14b6f2 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/mod.rs @@ -0,0 +1,404 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +// mod aia_csrs; +mod config_regs; +mod core_regs; +pub mod cpu_caps; +// mod csrs; +pub mod aia; +mod timer_regs; + +use std::sync::{Arc, Mutex}; + +use anyhow::{Context, Result}; +use kvm_bindings::*; +use kvm_ioctls::DeviceFd; +use vmm_sys_util::{ioctl_ioc_nr, ioctl_iow_nr, ioctl_iowr_nr}; + +use self::config_regs::RISCVConfigRegs; +use self::core_regs::RISCVCoreRegs; +use self::timer_regs::RISCVTimerRegs; +use crate::kvm::{KvmCpu, KvmHypervisor}; +use cpu::{ArchCPU, CPUBootConfig, RegsIndex, CPU}; + +pub const KVM_MAX_CPREG_ENTRIES: usize = 500; + +ioctl_iow_nr!(KVM_GET_DEVICE_ATTR, KVMIO, 0xe2, kvm_device_attr); +ioctl_iow_nr!(KVM_GET_ONE_REG, KVMIO, 0xab, kvm_one_reg); +ioctl_iow_nr!(KVM_SET_ONE_REG, KVMIO, 0xac, kvm_one_reg); +ioctl_iowr_nr!(KVM_GET_REG_LIST, KVMIO, 0xb0, kvm_reg_list); +// ioctl_iow_nr!(KVM_ARM_VCPU_INIT, KVMIO, 0xae, kvm_vcpu_init); + +/// A wrapper for kvm_based device check and access. +pub struct KvmDevice; +impl KvmDevice { + fn kvm_device_check(fd: &DeviceFd, group: u32, attr: u64) -> Result<()> { + let attr = kvm_bindings::kvm_device_attr { + group, + attr, + addr: 0, + flags: 0, + }; + fd.has_device_attr(&attr) + .with_context(|| "Failed to check device attributes.")?; + Ok(()) + } + + pub fn kvm_device_access( + fd: &DeviceFd, + group: u32, + attr: u64, + addr: u64, + write: bool, + ) -> Result<()> { + let attr = kvm_bindings::kvm_device_attr { + group, + attr, + addr, + flags: 0, + }; + + if write { + fd.set_device_attr(&attr) + .with_context(|| "Failed to set device attributes.")?; + } else { + let mut attr = attr; + unsafe { fd.get_device_attr(&mut attr) } + .with_context(|| "Failed to get device attributes.")?; + }; + + Ok(()) + } +} + +impl KvmHypervisor { + pub fn arch_init(&self) -> Result<()> { + Ok(()) + } +} + +impl KvmCpu { + pub fn arch_init_pmu(&self) -> Result<()> { + // let pmu_attr = kvm_device_attr { + // group: KVM_ARM_VCPU_PMU_V3_CTRL, + // attr: KVM_ARM_VCPU_PMU_V3_INIT as u64, + // addr: 0, + // flags: 0, + // }; + // // SAFETY: The fd can be guaranteed to be legal during creation. + // let vcpu_device = unsafe { DeviceFd::from_raw_fd(self.fd.as_raw_fd()) }; + // vcpu_device + // .has_device_attr(&pmu_attr) + // .with_context(|| "Kernel does not support PMU for vCPU")?; + // // Set IRQ 23, PPI 7 for PMU. + // let irq = PMU_INTR + PPI_BASE; + // let pmu_irq_attr = kvm_device_attr { + // group: KVM_ARM_VCPU_PMU_V3_CTRL, + // attr: KVM_ARM_VCPU_PMU_V3_IRQ as u64, + // addr: &irq as *const u32 as u64, + // flags: 0, + // }; + + // vcpu_device + // .set_device_attr(&pmu_irq_attr) + // .with_context(|| "Failed to set IRQ for PMU")?; + // // Init PMU after setting IRQ. + // vcpu_device + // .set_device_attr(&pmu_attr) + // .with_context(|| "Failed to enable PMU for vCPU")?; + // // forget `vcpu_device` to avoid fd close on exit, as DeviceFd is backed by File. + // forget(vcpu_device); + + // TODO + + Ok(()) + } + + pub fn arch_vcpu_init(&self) -> Result<()> { + // self.fd + // .vcpu_init(&self.kvi.lock().unwrap()) + // .with_context(|| "Failed to init kvm vcpu") + Ok(()) + } + + pub fn arch_set_boot_config( + &self, + arch_cpu: Arc>, + boot_config: &CPUBootConfig, + ) -> Result<()> { + arch_cpu.lock().unwrap().set_core_reg(boot_config); + + self.arch_vcpu_init()?; + + Ok(()) + } + + fn get_one_reg(&self, reg_id: u64) -> Result { + let mut val = [0_u8; 16]; + self.fd.lock().unwrap().get_one_reg(reg_id, &mut val)?; + Ok(u128::from_le_bytes(val)) + } + + fn set_one_reg(&self, reg_id: u64, val: u128) -> Result<()> { + self.fd + .lock() + .unwrap() + .set_one_reg(reg_id, &val.to_le_bytes())?; + Ok(()) + } + + pub fn arch_get_one_reg(&self, reg_id: u64) -> Result { + self.get_one_reg(reg_id) + } + + pub fn arch_get_regs( + &self, + arch_cpu: Arc>, + regs_index: RegsIndex, + ) -> Result<()> { + let mut locked_arch_cpu = arch_cpu.lock().unwrap(); + + match regs_index { + RegsIndex::CoreRegs => { + locked_arch_cpu.core_regs = self.get_core_regs()?; + } + RegsIndex::TimerRegs => { + locked_arch_cpu.timer_regs = self.get_timer_regs()?; + } + RegsIndex::ConfigRegs => { + locked_arch_cpu.config_regs = self.get_config_regs()?; + } + RegsIndex::MpState => { + if self.caps.mp_state { + let mut mp_state = self.fd.lock().unwrap().get_mp_state()?; + if mp_state.mp_state != KVM_MP_STATE_STOPPED { + mp_state.mp_state = KVM_MP_STATE_RUNNABLE; + } + locked_arch_cpu.mp_state = mp_state; + } + } + RegsIndex::AiaCsrs | RegsIndex::Csrs => {} + } + + Ok(()) + } + + pub fn arch_set_regs( + &self, + arch_cpu: Arc>, + regs_index: RegsIndex, + ) -> Result<()> { + let locked_arch_cpu = arch_cpu.lock().unwrap(); + let apic_id = locked_arch_cpu.apic_id; + match regs_index { + RegsIndex::CoreRegs => { + self.set_core_regs(locked_arch_cpu.core_regs) + .with_context(|| format!("Failed to set core register for CPU {}", apic_id))?; + } + RegsIndex::TimerRegs => { + self.set_timer_regs(locked_arch_cpu.timer_regs) + .with_context(|| format!("Failed to set timer register for CPU {}", apic_id))?; + } + RegsIndex::ConfigRegs => { + self.set_config_regs(locked_arch_cpu.config_regs) + .with_context(|| { + format!("Failed to set config register for CPU {}", apic_id) + })?; + } + RegsIndex::MpState => { + if self.caps.mp_state { + self.fd + .lock() + .unwrap() + .set_mp_state(locked_arch_cpu.mp_state) + .with_context(|| format!("Failed to set mpstate for CPU {}", apic_id))?; + } + } + RegsIndex::AiaCsrs | RegsIndex::Csrs => {} + } + + Ok(()) + } + + /// Returns the vcpu's current `core_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + fn get_core_regs(&self) -> Result { + // vcpu_fd.set_one_reg(RISCVCoreRegs::PC.into(), core_regs.regs.pc as u128)?; + // vcpu_fd.set_one_reg(RISCVCoreRegs::A0.into(), core_regs.regs.a0 as u128)?; + // vcpu_fd.set_one_reg(RISCVCoreRegs::A1.into(), core_regs.regs.a1 as u128)?; + let mut core_regs = kvm_riscv_core::default(); + + core_regs.regs.pc = self.get_one_reg(RISCVCoreRegs::PC.into())? as u64; + core_regs.regs.ra = self.get_one_reg(RISCVCoreRegs::RA.into())? as u64; + core_regs.regs.sp = self.get_one_reg(RISCVCoreRegs::SP.into())? as u64; + core_regs.regs.gp = self.get_one_reg(RISCVCoreRegs::GP.into())? as u64; + core_regs.regs.tp = self.get_one_reg(RISCVCoreRegs::TP.into())? as u64; + core_regs.regs.t0 = self.get_one_reg(RISCVCoreRegs::T0.into())? as u64; + core_regs.regs.t1 = self.get_one_reg(RISCVCoreRegs::T1.into())? as u64; + core_regs.regs.t2 = self.get_one_reg(RISCVCoreRegs::T2.into())? as u64; + core_regs.regs.t3 = self.get_one_reg(RISCVCoreRegs::T3.into())? as u64; + core_regs.regs.t4 = self.get_one_reg(RISCVCoreRegs::T4.into())? as u64; + core_regs.regs.t5 = self.get_one_reg(RISCVCoreRegs::T5.into())? as u64; + core_regs.regs.t6 = self.get_one_reg(RISCVCoreRegs::T6.into())? as u64; + core_regs.regs.a0 = self.get_one_reg(RISCVCoreRegs::A0.into())? as u64; + core_regs.regs.a1 = self.get_one_reg(RISCVCoreRegs::A1.into())? as u64; + core_regs.regs.a2 = self.get_one_reg(RISCVCoreRegs::A2.into())? as u64; + core_regs.regs.a3 = self.get_one_reg(RISCVCoreRegs::A3.into())? as u64; + core_regs.regs.a4 = self.get_one_reg(RISCVCoreRegs::A4.into())? as u64; + core_regs.regs.a5 = self.get_one_reg(RISCVCoreRegs::A5.into())? as u64; + core_regs.regs.a6 = self.get_one_reg(RISCVCoreRegs::A6.into())? as u64; + core_regs.regs.a7 = self.get_one_reg(RISCVCoreRegs::A7.into())? as u64; + core_regs.regs.s0 = self.get_one_reg(RISCVCoreRegs::S0.into())? as u64; + core_regs.regs.s1 = self.get_one_reg(RISCVCoreRegs::S1.into())? as u64; + core_regs.regs.s2 = self.get_one_reg(RISCVCoreRegs::S2.into())? as u64; + core_regs.regs.s3 = self.get_one_reg(RISCVCoreRegs::S3.into())? as u64; + core_regs.regs.s4 = self.get_one_reg(RISCVCoreRegs::S4.into())? as u64; + core_regs.regs.s5 = self.get_one_reg(RISCVCoreRegs::S5.into())? as u64; + core_regs.regs.s6 = self.get_one_reg(RISCVCoreRegs::S6.into())? as u64; + core_regs.regs.s7 = self.get_one_reg(RISCVCoreRegs::S7.into())? as u64; + core_regs.regs.s8 = self.get_one_reg(RISCVCoreRegs::S8.into())? as u64; + core_regs.regs.s9 = self.get_one_reg(RISCVCoreRegs::S9.into())? as u64; + core_regs.regs.s10 = self.get_one_reg(RISCVCoreRegs::S10.into())? as u64; + core_regs.regs.s11 = self.get_one_reg(RISCVCoreRegs::S11.into())? as u64; + + Ok(core_regs) + } + + /// Sets the vcpu's current "core_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `core_regs` - kvm_regs state to be written. + fn set_core_regs(&self, core_regs: kvm_riscv_core) -> Result<()> { + self.set_one_reg(RISCVCoreRegs::PC.into(), core_regs.regs.pc as u128)?; + //self.set_one_reg(RISCVCoreRegs::RA.into(), core_regs.regs.ra as u128)?; + //self.set_one_reg(RISCVCoreRegs::SP.into(), core_regs.regs.sp as u128)?; + //self.set_one_reg(RISCVCoreRegs::GP.into(), core_regs.regs.gp as u128)?; + //self.set_one_reg(RISCVCoreRegs::TP.into(), core_regs.regs.tp as u128)?; + //self.set_one_reg(RISCVCoreRegs::T0.into(), core_regs.regs.t0 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T1.into(), core_regs.regs.t1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T2.into(), core_regs.regs.t2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T3.into(), core_regs.regs.t3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T4.into(), core_regs.regs.t4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T5.into(), core_regs.regs.t5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T6.into(), core_regs.regs.t6 as u128)?; + self.set_one_reg(RISCVCoreRegs::A0.into(), core_regs.regs.a0 as u128)?; + self.set_one_reg(RISCVCoreRegs::A1.into(), core_regs.regs.a1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A2.into(), core_regs.regs.a2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A3.into(), core_regs.regs.a3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A4.into(), core_regs.regs.a4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A5.into(), core_regs.regs.a5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A6.into(), core_regs.regs.a6 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A7.into(), core_regs.regs.a7 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S0.into(), core_regs.regs.s0 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S1.into(), core_regs.regs.s1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S2.into(), core_regs.regs.s2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S3.into(), core_regs.regs.s3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S4.into(), core_regs.regs.s4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S5.into(), core_regs.regs.s5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S6.into(), core_regs.regs.s6 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S7.into(), core_regs.regs.s7 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S8.into(), core_regs.regs.s8 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S9.into(), core_regs.regs.s9 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S10.into(), core_regs.regs.s10 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S11.into(), core_regs.regs.s11 as u128)?; + + Ok(()) + } + + /// Returns the vcpu's current `config_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + pub fn get_config_regs(&self) -> Result { + let mut config_regs = kvm_riscv_config::default(); + config_regs.isa = self.get_one_reg(RISCVConfigRegs::ISA.into())? as u64; + + Ok(config_regs) + } + + /// Sets the vcpu's current "config_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `config_regs` - kvm_regs state to be written. + pub fn set_config_regs(&self, config_regs: kvm_riscv_config) -> Result<()> { + self.set_one_reg(RISCVConfigRegs::ISA.into(), config_regs.isa as u128)?; + Ok(()) + } + + /// Returns the vcpu's current `timer_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + pub fn get_timer_regs(&self) -> Result { + let mut timer_regs = kvm_riscv_timer::default(); + timer_regs.frequency = self.get_one_reg(RISCVTimerRegs::FREQUENCY.into())? as u64; + timer_regs.time = self.get_one_reg(RISCVTimerRegs::TIME.into())? as u64; + timer_regs.compare = self.get_one_reg(RISCVTimerRegs::COMPARE.into())? as u64; + timer_regs.state = self.get_one_reg(RISCVTimerRegs::STATE.into())? as u64; + + Ok(timer_regs) + } + + /// Sets the vcpu's current "timer_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `timer_regs` - kvm_regs state to be written. + pub fn set_timer_regs(&self, timer_regs: kvm_riscv_timer) -> Result<()> { + self.set_one_reg( + RISCVTimerRegs::FREQUENCY.into(), + timer_regs.frequency as u128, + )?; + self.set_one_reg(RISCVTimerRegs::TIME.into(), timer_regs.time as u128)?; + self.set_one_reg(RISCVTimerRegs::COMPARE.into(), timer_regs.compare as u128)?; + self.set_one_reg(RISCVTimerRegs::STATE.into(), timer_regs.state as u128)?; + Ok(()) + } + + pub fn arch_put_register(&self, cpu: Arc) -> Result<()> { + let arch_cpu = &cpu.arch_cpu; + self.arch_set_regs(arch_cpu.clone(), RegsIndex::CoreRegs)?; + //self.arch_set_regs(arch_cpu.clone(), RegsIndex::TimerRegs)?; + //self.arch_set_regs(arch_cpu.clone(), RegsIndex::ConfigRegs)?; + self.arch_set_regs(arch_cpu.clone(), RegsIndex::MpState)?; + // Put AIA and CSR + Ok(()) + } + + pub fn arch_reset_vcpu(&self, cpu: Arc) -> Result<()> { + cpu.arch_cpu.lock().unwrap().set(&cpu.boot_state()); + self.arch_put_register(cpu) + } +} diff --git a/hypervisor/src/kvm/riscv64/timer_regs.rs b/hypervisor/src/kvm/riscv64/timer_regs.rs new file mode 100644 index 0000000..c0a2944 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/timer_regs.rs @@ -0,0 +1,50 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::mem::size_of; + +use kvm_bindings::{kvm_riscv_timer, KVM_REG_RISCV, KVM_REG_RISCV_TIMER, KVM_REG_SIZE_U64}; +use util::offset_of; + +/// RISCV cpu time register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L78 +pub enum RISCVTimerRegs { + FREQUENCY, + TIME, + COMPARE, + STATE, +} + +impl Into for RISCVTimerRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVTimerRegs::FREQUENCY => { + offset_of!(kvm_riscv_timer, frequency) + } + RISCVTimerRegs::TIME => { + offset_of!(kvm_riscv_timer, time) + } + RISCVTimerRegs::COMPARE => { + offset_of!(kvm_riscv_timer, compare) + } + RISCVTimerRegs::STATE => { + offset_of!(kvm_riscv_timer, state) + } + }; + + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_TIMER) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/kvm/x86_64/mod.rs b/hypervisor/src/kvm/x86_64/mod.rs index e7d08ef..7d7e7b5 100644 --- a/hypervisor/src/kvm/x86_64/mod.rs +++ b/hypervisor/src/kvm/x86_64/mod.rs @@ -84,7 +84,7 @@ impl KvmCpu { pub fn arch_set_boot_config( &self, arch_cpu: Arc>, - boot_config: &Option, + boot_config: &CPUBootConfig, ) -> Result<()> { let mut locked_arch_cpu = arch_cpu.lock().unwrap(); let apic_id = locked_arch_cpu.apic_id; @@ -93,14 +93,12 @@ impl KvmCpu { .get_lapic() .with_context(|| format!("Failed to get lapic for CPU {}/KVM", apic_id))?; locked_arch_cpu.setup_lapic(lapic)?; + locked_arch_cpu.setup_regs(boot_config); let sregs = self .fd .get_sregs() .with_context(|| format!("Failed to get sregs for CPU {}/KVM", apic_id))?; - if let Some(cfg) = boot_config { - locked_arch_cpu.setup_regs(cfg); - locked_arch_cpu.setup_sregs(sregs, cfg)?; - } + locked_arch_cpu.setup_sregs(sregs, boot_config)?; locked_arch_cpu.setup_fpu(); locked_arch_cpu.setup_msrs(); diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index f53f232..84e5b18 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -14,6 +14,8 @@ pub mod error; pub mod kvm; +#[cfg(not(target_arch = "riscv64"))] +pub mod test; pub use error::HypervisorError; @@ -26,6 +28,8 @@ use kvm_ioctls::DeviceFd; use address_space::AddressSpace; use cpu::CPUHypervisorOps; use devices::IrqManager; +#[cfg(target_arch = "riscv64")] +use devices::{AIAConfig, InterruptController}; #[cfg(target_arch = "aarch64")] use devices::{ICGICConfig, InterruptController}; use machine_manager::machine::HypervisorType; @@ -50,6 +54,12 @@ pub trait HypervisorOps: Send + Sync + Any { #[cfg(target_arch = "x86_64")] fn create_interrupt_controller(&mut self) -> Result<()>; + #[cfg(target_arch = "riscv64")] + fn create_interrupt_controller( + &mut self, + aia_conf: &AIAConfig, + ) -> Result>; + fn create_hypervisor_cpu(&self, vcpu_id: u8) -> Result>; diff --git a/machine_manager/src/config/ramfb.rs b/hypervisor/src/test/aarch64/mod.rs similarity index 55% rename from machine_manager/src/config/ramfb.rs rename to hypervisor/src/test/aarch64/mod.rs index 8473c1d..0b5317a 100644 --- a/machine_manager/src/config/ramfb.rs +++ b/hypervisor/src/test/aarch64/mod.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan @@ -10,15 +10,14 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use anyhow::Result; +use devices::{GICv3Access, GICv3ItsAccess}; -use crate::config::CmdParser; +#[derive(Default)] +pub struct TestGicv3 {} -pub fn parse_ramfb(cfg_args: &str) -> Result { - let mut cmd_parser = CmdParser::new("ramfb"); - cmd_parser.push("").push("install").push("id"); - cmd_parser.parse(cfg_args)?; +impl GICv3Access for TestGicv3 {} - let install = cmd_parser.get_value::("install")?.unwrap_or(false); - Ok(install) -} +#[derive(Default)] +pub struct TestGicv3Its {} + +impl GICv3ItsAccess for TestGicv3Its {} diff --git a/hypervisor/src/test/listener.rs b/hypervisor/src/test/listener.rs new file mode 100644 index 0000000..15a65dd --- /dev/null +++ b/hypervisor/src/test/listener.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use address_space::Listener; + +#[derive(Default, Clone)] +pub struct TestMemoryListener { + enabled: bool, +} + +impl Listener for TestMemoryListener { + /// Get default priority. + fn priority(&self) -> i32 { + 10_i32 + } + + /// Is this listener enabled to call. + fn enabled(&self) -> bool { + self.enabled + } + + /// Enable listener for address space. + fn enable(&mut self) { + self.enabled = true; + } + + /// Disable listener for address space. + fn disable(&mut self) { + self.enabled = false; + } +} diff --git a/hypervisor/src/test/mod.rs b/hypervisor/src/test/mod.rs new file mode 100644 index 0000000..8e4697a --- /dev/null +++ b/hypervisor/src/test/mod.rs @@ -0,0 +1,417 @@ +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +#[cfg(target_arch = "aarch64")] +mod aarch64; +mod listener; + +use std::collections::HashMap; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Barrier, Condvar, Mutex}; +use std::thread; +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use kvm_ioctls::DeviceFd; +use log::info; +use vmm_sys_util::eventfd::EventFd; + +#[cfg(target_arch = "aarch64")] +use self::aarch64::{TestGicv3, TestGicv3Its}; +use self::listener::TestMemoryListener; +use super::HypervisorOps; +use address_space::{AddressSpace, Listener}; +#[cfg(target_arch = "aarch64")] +use cpu::CPUFeatures; +use cpu::{ + ArchCPU, CPUBootConfig, CPUHypervisorOps, CPUThreadWorker, CpuError, CpuLifecycleState, + RegsIndex, CPU, +}; +use devices::{pci::MsiVector, IrqManager, LineIrqManager, MsiIrqManager, TriggerMode}; +#[cfg(target_arch = "aarch64")] +use devices::{GICVersion, GICv3, ICGICConfig, InterruptController, GIC_IRQ_INTERNAL}; +use machine_manager::machine::HypervisorType; +use migration::{MigrateMemSlot, MigrateOps}; +use util::test_helper::{IntxInfo, MsixMsg, TEST_INTX_LIST, TEST_MSIX_LIST}; + +pub struct TestHypervisor {} + +impl TestHypervisor { + pub fn new() -> Result { + Ok(TestHypervisor {}) + } + + fn create_memory_listener(&self) -> Arc> { + Arc::new(Mutex::new(TestMemoryListener::default())) + } +} + +impl HypervisorOps for TestHypervisor { + fn get_hypervisor_type(&self) -> HypervisorType { + HypervisorType::Test + } + + fn init_machine( + &self, + #[cfg(target_arch = "x86_64")] _sys_io: &Arc, + sys_mem: &Arc, + ) -> Result<()> { + sys_mem + .register_listener(self.create_memory_listener()) + .with_context(|| "Failed to register hypervisor listener for memory space.") + } + + #[cfg(target_arch = "aarch64")] + fn create_interrupt_controller( + &mut self, + gic_conf: &ICGICConfig, + ) -> Result> { + gic_conf.check_sanity()?; + + let create_gicv3 = || { + let gicv3 = Arc::new(GICv3::new( + Arc::new(TestGicv3::default()), + Arc::new(TestGicv3Its::default()), + gic_conf, + )?); + + Ok(Arc::new(InterruptController::new(gicv3))) + }; + + match &gic_conf.version { + Some(GICVersion::GICv3) => create_gicv3(), + Some(GICVersion::GICv2) => Err(anyhow!("MST doesn't support Gicv2.")), + // Try v3 by default if no version specified. + None => create_gicv3(), + } + } + + #[cfg(target_arch = "x86_64")] + fn create_interrupt_controller(&mut self) -> Result<()> { + Ok(()) + } + + fn create_hypervisor_cpu( + &self, + vcpu_id: u8, + ) -> Result> { + Ok(Arc::new(TestCpu::new(vcpu_id))) + } + + fn create_irq_manager(&mut self) -> Result { + let test_irq_manager = Arc::new(TestInterruptManager {}); + Ok(IrqManager { + line_irq_manager: Some(test_irq_manager.clone()), + msi_irq_manager: Some(test_irq_manager), + }) + } + + fn create_vfio_device(&self) -> Option { + None + } +} + +pub struct TestCpu { + #[allow(unused)] + id: u8, +} + +impl TestCpu { + pub fn new(vcpu_id: u8) -> Self { + Self { id: vcpu_id } + } +} + +impl CPUHypervisorOps for TestCpu { + fn get_hypervisor_type(&self) -> HypervisorType { + HypervisorType::Test + } + + fn init_pmu(&self) -> Result<()> { + Ok(()) + } + + fn vcpu_init(&self) -> Result<()> { + Ok(()) + } + + #[allow(unused)] + fn set_boot_config( + &self, + arch_cpu: Arc>, + boot_config: &CPUBootConfig, + #[cfg(target_arch = "aarch64")] _vcpu_config: &CPUFeatures, + ) -> Result<()> { + #[cfg(target_arch = "aarch64")] + { + arch_cpu.lock().unwrap().mpidr = self.id as u64; + arch_cpu.lock().unwrap().set_core_reg(boot_config); + } + Ok(()) + } + + fn get_one_reg(&self, _reg_id: u64) -> Result { + Err(anyhow!("MST does not support getting one reg.")) + } + + fn get_regs(&self, _arch_cpu: Arc>, _regs_index: RegsIndex) -> Result<()> { + Ok(()) + } + + fn set_regs(&self, _arch_cpu: Arc>, _regs_index: RegsIndex) -> Result<()> { + Ok(()) + } + + fn put_register(&self, _cpu: Arc) -> Result<()> { + Err(anyhow!("Test does not support putting register.")) + } + + fn reset_vcpu(&self, cpu: Arc) -> Result<()> { + cpu.arch_cpu.lock().unwrap().set(&cpu.boot_state()); + Ok(()) + } + + fn vcpu_exec( + &self, + cpu_thread_worker: CPUThreadWorker, + thread_barrier: Arc, + ) -> Result<()> { + cpu_thread_worker.init_local_thread_vcpu(); + cpu_thread_worker.thread_cpu.set_tid(None); + + // Wait for all vcpu to complete the running + // environment initialization. + thread_barrier.wait(); + + info!("Test vcpu{} start running", cpu_thread_worker.thread_cpu.id); + while let Ok(true) = cpu_thread_worker.ready_for_running() { + thread::sleep(Duration::from_millis(5)); + continue; + } + + // The vcpu thread is about to exit, marking the state + // of the CPU state as Stopped. + let (cpu_state, cvar) = &*cpu_thread_worker.thread_cpu.state; + *cpu_state.lock().unwrap() = CpuLifecycleState::Stopped; + cvar.notify_one(); + + Ok(()) + } + + fn set_hypervisor_exit(&self) -> Result<()> { + Ok(()) + } + + fn pause( + &self, + _task: Arc>>>, + _state: Arc<(Mutex, Condvar)>, + _pause_signal: Arc, + ) -> Result<()> { + Ok(()) + } + + fn resume( + &self, + _state: Arc<(Mutex, Condvar)>, + _pause_signal: Arc, + ) -> Result<()> { + Ok(()) + } + + fn destroy( + &self, + _task: Arc>>>, + state: Arc<(Mutex, Condvar)>, + ) -> Result<()> { + let (cpu_state, cvar) = &*state; + let mut locked_cpu_state = cpu_state.lock().unwrap(); + if *locked_cpu_state == CpuLifecycleState::Running { + *locked_cpu_state = CpuLifecycleState::Stopping; + } else if *locked_cpu_state == CpuLifecycleState::Stopped + || *locked_cpu_state == CpuLifecycleState::Paused + { + return Ok(()); + } + drop(locked_cpu_state); + + let mut locked_cpu_state = cpu_state.lock().unwrap(); + locked_cpu_state = cvar + .wait_timeout(locked_cpu_state, Duration::from_millis(10)) + .unwrap() + .0; + + if *locked_cpu_state == CpuLifecycleState::Stopped { + Ok(()) + } else { + Err(anyhow!(CpuError::DestroyVcpu(format!( + "VCPU still in {:?} state", + *locked_cpu_state + )))) + } + } +} + +impl MigrateOps for TestHypervisor { + fn get_mem_slots(&self) -> Arc>> { + Arc::new(Mutex::new(HashMap::new())) + } + + fn get_dirty_log(&self, _slot: u32, _mem_size: u64) -> Result> { + Err(anyhow!( + "Failed to get dirty log, mst doesn't support migration feature." + )) + } + + fn start_dirty_log(&self) -> Result<()> { + Err(anyhow!( + "Failed to start dirty log, mst doesn't support migration feature." + )) + } + + fn stop_dirty_log(&self) -> Result<()> { + Err(anyhow!( + "Failed to stop dirty log, mst doesn't support migration feature." + )) + } + + fn register_instance(&self) -> Result<()> { + Ok(()) + } +} + +struct TestInterruptManager {} + +impl TestInterruptManager { + #[cfg(target_arch = "x86_64")] + pub fn arch_map_irq(&self, gsi: u32) -> u32 { + gsi + } + + #[cfg(target_arch = "aarch64")] + pub fn arch_map_irq(&self, gsi: u32) -> u32 { + gsi + GIC_IRQ_INTERNAL + } + + pub fn add_msix_msg(addr: u64, data: u32) { + let new_msg = MsixMsg::new(addr, data); + let mut msix_list_lock = TEST_MSIX_LIST.lock().unwrap(); + + for msg in msix_list_lock.iter() { + if new_msg.addr == msg.addr && new_msg.data == msg.data { + return; + } + } + + msix_list_lock.push(new_msg); + } +} + +impl LineIrqManager for TestInterruptManager { + fn irqfd_enable(&self) -> bool { + false + } + + fn register_irqfd( + &self, + _irq_fd: Arc, + _irq: u32, + _trigger_mode: TriggerMode, + ) -> Result<()> { + Err(anyhow!( + "Failed to register irqfd, mst doesn't support irqfd feature." + )) + } + + fn unregister_irqfd(&self, _irq_fd: Arc, _irq: u32) -> Result<()> { + Err(anyhow!( + "Failed to unregister irqfd, mst doesn't support irqfd feature." + )) + } + + fn set_level_irq(&self, gsi: u32, level: bool) -> Result<()> { + let physical_irq = self.arch_map_irq(gsi); + let level: i8 = if level { 1 } else { 0 }; + + let mut intx_list_lock = TEST_INTX_LIST.lock().unwrap(); + + for intx in intx_list_lock.iter_mut() { + if intx.irq == physical_irq { + intx.level = level; + return Ok(()); + } + } + + let new_intx = IntxInfo::new(physical_irq, level); + intx_list_lock.push(new_intx); + Ok(()) + } + + fn set_edge_irq(&self, _gsi: u32) -> Result<()> { + Ok(()) + } + + fn write_irqfd(&self, _irq_fd: Arc) -> Result<()> { + Err(anyhow!( + "Failed to write irqfd, mst doesn't support irqfd feature." + )) + } +} + +impl MsiIrqManager for TestInterruptManager { + fn irqfd_enable(&self) -> bool { + false + } + + fn allocate_irq(&self, _vector: MsiVector) -> Result { + Err(anyhow!( + "Failed to allocate irq, mst doesn't support irq routing feature." + )) + } + + fn release_irq(&self, _irq: u32) -> Result<()> { + Err(anyhow!( + "Failed to release irq, mst doesn't support irq routing feature." + )) + } + + fn register_irqfd(&self, _irq_fd: Arc, _irq: u32) -> Result<()> { + Err(anyhow!( + "Failed to register msi irqfd, mst doesn't support irqfd feature." + )) + } + + fn unregister_irqfd(&self, _irq_fd: Arc, _irq: u32) -> Result<()> { + Err(anyhow!( + "Failed to unregister msi irqfd, mst doesn't support irqfd feature." + )) + } + + fn trigger( + &self, + _irq_fd: Option>, + vector: MsiVector, + _dev_id: u32, + ) -> Result<()> { + let data = vector.msg_data; + let mut addr: u64 = vector.msg_addr_hi as u64; + addr = (addr << 32) + vector.msg_addr_lo as u64; + TestInterruptManager::add_msix_msg(addr, data); + Ok(()) + } + + fn update_route_table(&self, _gsi: u32, _vector: MsiVector) -> Result<()> { + Err(anyhow!( + "Failed to update route table, mst doesn't support irq routing feature." + )) + } +} diff --git a/image/src/img.rs b/image/src/img.rs index 7f21220..e106b70 100644 --- a/image/src/img.rs +++ b/image/src/img.rs @@ -23,8 +23,8 @@ use crate::{cmdline::ArgsParse, BINARY_NAME}; use block_backend::{ qcow2::{header::QcowHeader, InternalSnapshotOps, Qcow2Driver, SyncAioInfo}, raw::RawDriver, - BlockDriverOps, BlockProperty, CheckResult, CreateOptions, FIX_ERRORS, FIX_LEAKS, NO_FIX, - SECTOR_SIZE, + BlockDriverOps, BlockProperty, CheckResult, CreateOptions, ImageInfo, FIX_ERRORS, FIX_LEAKS, + NO_FIX, SECTOR_SIZE, }; use machine_manager::config::{memory_unit_conversion, DiskFormat}; use util::{ @@ -169,6 +169,7 @@ pub(crate) fn image_create(args: Vec) -> Result<()> { .read(true) .write(true) .custom_flags(libc::O_CREAT | libc::O_TRUNC) + .mode(0o660) .open(path.clone())?; let aio = Aio::new(Arc::new(SyncAioInfo::complete_func), AioEngine::Off, None)?; @@ -189,6 +190,52 @@ pub(crate) fn image_create(args: Vec) -> Result<()> { Ok(()) } +pub(crate) fn image_info(args: Vec) -> Result<()> { + if args.len() < 1 { + bail!("Not enough arguments"); + } + let mut arg_parser = ArgsParse::create(vec!["h", "help"], vec![], vec![]); + arg_parser.parse(args)?; + + if arg_parser.opt_present("h") || arg_parser.opt_present("help") { + print_help(); + return Ok(()); + } + + // Parse the image path. + let len = arg_parser.free.len(); + let img_path = match len { + 0 => bail!("Image path is needed"), + 1 => arg_parser.free[0].clone(), + _ => { + let param = arg_parser.free[1].clone(); + bail!("Unexpected argument: {}", param); + } + }; + + let aio = Aio::new(Arc::new(SyncAioInfo::complete_func), AioEngine::Off, None)?; + let image_file = ImageFile::create(&img_path, false)?; + let detect_fmt = image_file.detect_img_format()?; + + let mut conf = BlockProperty::default(); + conf.format = detect_fmt; + let mut driver: Box> = match detect_fmt { + DiskFormat::Raw => Box::new(RawDriver::new(image_file.file.try_clone()?, aio, conf)), + DiskFormat::Qcow2 => { + let mut qocw2_driver = + Qcow2Driver::new(image_file.file.try_clone()?, aio, conf.clone())?; + qocw2_driver.load_metadata(conf)?; + Box::new(qocw2_driver) + } + }; + + let mut image_info = ImageInfo::default(); + image_info.path = img_path; + driver.query_image(&mut image_info)?; + print!("{}", image_info); + Ok(()) +} + pub(crate) fn image_check(args: Vec) -> Result<()> { let mut arg_parser = ArgsParse::create(vec!["no_print_error", "h", "help"], vec!["f", "r"], vec![]); @@ -473,6 +520,7 @@ Stratovirt disk image utility Command syntax: create [-f fmt] [-o options] filename [size] +info filename check [-r [leaks | all]] [-no_print_error] [-f fmt] filename resize [-f fmt] filename [+]size snapshot [-l | -a snapshot | -c snapshot | -d snapshot] filename @@ -759,6 +807,55 @@ mod test { assert!(remove_file(path).is_ok()); } + /// Test the function of query image. + /// TestStep: + /// 2. Query image info with different type. + /// Expect: + /// 1. Ihe invalid args will result in failure. + #[test] + fn test_args_parse_of_image_info() { + let path = "/tmp/test_args_parse_of_image_info.qcow2"; + let test_case = vec![ + ("img_path", true), + ("-f qcow2", false), + ("invalid_args", false), + ("img_path +1G", false), + ("-h", true), + ("--help", true), + ]; + + for case in test_case { + let cmd_str = case.0.replace("img_path", path); + let args: Vec = cmd_str + .split(' ') + .into_iter() + .map(|str| str.to_string()) + .collect(); + + // Query image info with type of qcow2. + assert!(image_create(vec![ + "-f".to_string(), + "qcow2".to_string(), + path.to_string(), + "+10M".to_string() + ]) + .is_ok()); + assert_eq!(image_info(args.clone()).is_ok(), case.1); + + // Query image info with type of raw. + assert!(image_create(vec![ + "-f".to_string(), + "raw".to_string(), + path.to_string(), + "+10M".to_string() + ]) + .is_ok()); + assert_eq!(image_info(args).is_ok(), case.1); + } + + assert!(remove_file(path).is_ok()); + } + /// Test the function of creating image. /// TestStep: /// 1. Create image with different cluster bits, image size and refcount bits. diff --git a/image/src/main.rs b/image/src/main.rs index 055ac21..b9c7aef 100644 --- a/image/src/main.rs +++ b/image/src/main.rs @@ -21,7 +21,7 @@ use std::{ use anyhow::{bail, Result}; use crate::img::{ - image_check, image_create, image_resize, image_snapshot, print_help, print_version, + image_check, image_create, image_info, image_resize, image_snapshot, print_help, print_version, }; const BINARY_NAME: &str = "stratovirt-img"; @@ -83,6 +83,7 @@ fn run(args: Vec) -> Result<()> { image_operation_matches!( opt.as_str(); ("create", image_create, cmd_args), + ("info", image_info, cmd_args), ("check", image_check, cmd_args), ("resize", image_resize, cmd_args), ("snapshot", image_snapshot, cmd_args); diff --git a/machine/src/aarch64/micro.rs b/machine/src/aarch64/micro.rs index 3e7cf38..40c8baf 100644 --- a/machine/src/aarch64/micro.rs +++ b/machine/src/aarch64/micro.rs @@ -20,13 +20,13 @@ use address_space::{AddressSpace, GuestAddress, Region}; use cpu::CPUTopology; use devices::{legacy::PL031, ICGICConfig, ICGICv2Config, ICGICv3Config, GIC_IRQ_MAX}; use hypervisor::kvm::aarch64::*; -use machine_manager::config::{MigrateMode, SerialConfig, VmConfig}; +use machine_manager::config::{SerialConfig, VmConfig}; use migration::{MigrationManager, MigrationStatus}; use util::{ device_tree::{self, CompileFDT, FdtBuilder}, seccomp::{BpfRule, SeccompCmpOpt}, }; -use virtio::VirtioMmioDevice; +use virtio::{VirtioDevice, VirtioMmioDevice}; #[repr(usize)] pub enum LayoutEntryType { @@ -160,12 +160,8 @@ impl MachineOps for LightMachine { vm_config.machine_config.nr_cpus, )?; - let migrate_info = locked_vm.get_migrate_info(); - let boot_config = if migrate_info.0 == MigrateMode::Unknown { - Some(locked_vm.load_boot_source(None, MEM_LAYOUT[LayoutEntryType::Mem as usize].0)?) - } else { - None - }; + let boot_config = + locked_vm.load_boot_source(None, MEM_LAYOUT[LayoutEntryType::Mem as usize].0)?; let cpu_config = locked_vm.load_cpu_features(vm_config)?; let hypervisor = locked_vm.base.hypervisor.clone(); @@ -190,22 +186,20 @@ impl MachineOps for LightMachine { locked_vm.add_devices(vm_config)?; trace::replaceable_info(&locked_vm.replaceable_info); - if let Some(boot_cfg) = boot_config { - let mut fdt_helper = FdtBuilder::new(); - locked_vm - .generate_fdt_node(&mut fdt_helper) - .with_context(|| MachineError::GenFdtErr)?; - let fdt_vec = fdt_helper.finish()?; - locked_vm - .base - .sys_mem - .write( - &mut fdt_vec.as_slice(), - GuestAddress(boot_cfg.fdt_addr), - fdt_vec.len() as u64, - ) - .with_context(|| MachineError::WrtFdtErr(boot_cfg.fdt_addr, fdt_vec.len()))?; - } + let mut fdt_helper = FdtBuilder::new(); + locked_vm + .generate_fdt_node(&mut fdt_helper) + .with_context(|| MachineError::GenFdtErr)?; + let fdt_vec = fdt_helper.finish()?; + locked_vm + .base + .sys_mem + .write( + &mut fdt_vec.as_slice(), + GuestAddress(boot_config.fdt_addr), + fdt_vec.len() as u64, + ) + .with_context(|| MachineError::WrtFdtErr(boot_config.fdt_addr, fdt_vec.len()))?; MigrationManager::register_vm_instance(vm.clone()); MigrationManager::register_migration_instance(locked_vm.base.migration_hypervisor.clone()); @@ -224,11 +218,12 @@ impl MachineOps for LightMachine { self.add_virtio_mmio_block(vm_config, cfg_args) } - fn realize_virtio_mmio_device( + fn add_virtio_mmio_device( &mut self, - dev: VirtioMmioDevice, + name: String, + device: Arc>, ) -> Result>> { - self.realize_virtio_mmio_device(dev) + self.add_virtio_mmio_device(name, device) } fn syscall_whitelist(&self) -> Vec { @@ -241,7 +236,6 @@ pub(crate) fn arch_ioctl_allow_list(bpf_rule: BpfRule) -> BpfRule { .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_ONE_REG() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_DEVICE_ATTR() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_REG_LIST() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_ONE_REG() as u32) } pub(crate) fn arch_syscall_whitelist() -> Vec { diff --git a/machine/src/aarch64/mod.rs b/machine/src/aarch64/mod.rs index ee107ad..3ffe949 100644 --- a/machine/src/aarch64/mod.rs +++ b/machine/src/aarch64/mod.rs @@ -11,7 +11,7 @@ // See the Mulan PSL v2 for more details. pub mod micro; +pub mod pci_host_root; pub mod standard; mod fdt; -mod pci_host_root; diff --git a/machine/src/aarch64/standard.rs b/machine/src/aarch64/standard.rs index 416298a..4b6eebd 100644 --- a/machine/src/aarch64/standard.rs +++ b/machine/src/aarch64/standard.rs @@ -19,6 +19,8 @@ use std::sync::RwLock; use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Context, Result}; +#[cfg(feature = "ramfb")] +use clap::Parser; use log::{error, info, warn}; use vmm_sys_util::eventfd::EventFd; @@ -45,25 +47,26 @@ use super::pci_host_root::PciHostRoot; use crate::standard_common::syscall::syscall_whitelist; use devices::acpi::ged::{acpi_dsdt_add_power_button, Ged, GedEvent}; use devices::acpi::power::PowerDev; -#[cfg(feature = "ramfb")] -use devices::legacy::Ramfb; use devices::legacy::{ FwCfgEntryType, FwCfgMem, FwCfgOps, LegacyError as DevErrorKind, PFlash, PL011, PL031, }; +#[cfg(feature = "ramfb")] +use devices::legacy::{Ramfb, RamfbConfig}; use devices::pci::{PciDevOps, PciHost, PciIntxState}; use devices::sysbus::SysBusDevType; use devices::{ICGICConfig, ICGICv3Config, GIC_IRQ_MAX}; use hypervisor::kvm::aarch64::*; use hypervisor::kvm::*; #[cfg(feature = "ramfb")] -use machine_manager::config::parse_ramfb; +use machine_manager::config::str_slip_to_clap; use machine_manager::config::ShutdownAction; #[cfg(feature = "gtk")] use machine_manager::config::UiContext; use machine_manager::config::{ - parse_incoming_uri, BootIndexInfo, MigrateMode, NumaNode, PFlashConfig, SerialConfig, VmConfig, + parse_incoming_uri, BootIndexInfo, DriveConfig, MigrateMode, NumaNode, SerialConfig, VmConfig, }; use machine_manager::event; +use machine_manager::event_loop::EventLoop; use machine_manager::machine::{ MachineExternalInterface, MachineInterface, MachineLifecycle, MachineTestInterface, MigrateInterface, VmState, @@ -78,7 +81,7 @@ use ui::ohui_srv::{ohui_init, OhUiServer}; use ui::vnc::vnc_init; use util::byte_code::ByteCode; use util::device_tree::{self, CompileFDT, FdtBuilder}; -use util::loop_context::EventLoopManager; +use util::loop_context::{create_new_eventfd, EventLoopManager}; use util::seccomp::{BpfRule, SeccompCmpOpt}; use util::set_termi_canon_mode; @@ -183,23 +186,23 @@ impl StdMachine { IRQ_MAP[IrqEntryType::Pcie as usize].0, ))), power_button: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("power_button".to_string()))?, ), shutdown_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("shutdown_req".to_string()))?, ), reset_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("reset_req".to_string()))?, ), pause_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("pause_req".to_string()))?, ), resume_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("resume_req".to_string()))?, ), dtb_vec: Vec::new(), @@ -255,7 +258,7 @@ impl StdMachine { Ok(()) } - pub fn handle_destroy_request(vm: &Arc>) -> Result<()> { + pub fn handle_destroy_request(vm: &Arc>) -> bool { let locked_vm = vm.lock().unwrap(); let vmstate = { let state = locked_vm.base.vm_state.deref().0.lock().unwrap(); @@ -267,11 +270,13 @@ impl StdMachine { if locked_vm.shutdown_req.write(1).is_err() { error!("Failed to send shutdown request.") } + return false; } + EventLoop::kick_all(); info!("vm destroy"); - Ok(()) + true } fn build_pptt_cores(&self, pptt: &mut AcpiTable, cluster_offset: u32, uid: &mut u32) { @@ -357,7 +362,7 @@ impl StdMachine { #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] fn add_ohui_server(&mut self, vm_config: &VmConfig) -> Result<()> { if let Some(dpy) = vm_config.display.as_ref() { - if !dpy.ohui_config.ohui { + if dpy.display_type != "ohui" { return Ok(()); } self.ohui_server = Some(Arc::new(OhUiServer::new(dpy.get_ui_path())?)); @@ -606,16 +611,8 @@ impl MachineOps for StdMachine { .with_context(|| MachineError::InitPCIeHostErr)?; let fwcfg = locked_vm.add_fwcfg_device(nr_cpus)?; - let migrate = locked_vm.get_migrate_info(); - let boot_config = - if migrate.0 == MigrateMode::Unknown { - Some(locked_vm.load_boot_source( - fwcfg.as_ref(), - MEM_LAYOUT[LayoutEntryType::Mem as usize].0, - )?) - } else { - None - }; + let boot_config = locked_vm + .load_boot_source(fwcfg.as_ref(), MEM_LAYOUT[LayoutEntryType::Mem as usize].0)?; let cpu_config = locked_vm.load_cpu_features(vm_config)?; let hypervisor = locked_vm.base.hypervisor.clone(); @@ -640,23 +637,21 @@ impl MachineOps for StdMachine { .add_devices(vm_config) .with_context(|| "Failed to add devices")?; - if let Some(boot_cfg) = boot_config { - let mut fdt_helper = FdtBuilder::new(); - locked_vm - .generate_fdt_node(&mut fdt_helper) - .with_context(|| MachineError::GenFdtErr)?; - let fdt_vec = fdt_helper.finish()?; - locked_vm.dtb_vec = fdt_vec.clone(); - locked_vm - .base - .sys_mem - .write( - &mut fdt_vec.as_slice(), - GuestAddress(boot_cfg.fdt_addr), - fdt_vec.len() as u64, - ) - .with_context(|| MachineError::WrtFdtErr(boot_cfg.fdt_addr, fdt_vec.len()))?; - } + let mut fdt_helper = FdtBuilder::new(); + locked_vm + .generate_fdt_node(&mut fdt_helper) + .with_context(|| MachineError::GenFdtErr)?; + let fdt_vec = fdt_helper.finish()?; + locked_vm.dtb_vec = fdt_vec.clone(); + locked_vm + .base + .sys_mem + .write( + &mut fdt_vec.as_slice(), + GuestAddress(boot_config.fdt_addr), + fdt_vec.len() as u64, + ) + .with_context(|| MachineError::WrtFdtErr(boot_config.fdt_addr, fdt_vec.len()))?; // If it is direct kernel boot mode, the ACPI can not be enabled. if let Some(fw_cfg) = fwcfg { @@ -695,16 +690,16 @@ impl MachineOps for StdMachine { Ok(()) } - fn add_pflash_device(&mut self, configs: &[PFlashConfig]) -> Result<()> { + fn add_pflash_device(&mut self, configs: &[DriveConfig]) -> Result<()> { let mut configs_vec = configs.to_vec(); - configs_vec.sort_by_key(|c| c.unit); + configs_vec.sort_by_key(|c| c.unit.unwrap()); let sector_len: u32 = 1024 * 256; let mut flash_base: u64 = MEM_LAYOUT[LayoutEntryType::Flash as usize].0; let flash_size: u64 = MEM_LAYOUT[LayoutEntryType::Flash as usize].1 / 2; for i in 0..=1 { let (fd, read_only) = if i < configs_vec.len() { let path = &configs_vec[i].path_on_host; - let read_only = configs_vec[i].read_only; + let read_only = configs_vec[i].readonly; let fd = self.fetch_drive_file(path)?; (Some(fd), read_only) } else { @@ -728,7 +723,7 @@ impl MachineOps for StdMachine { #[cfg(any(feature = "gtk", all(target_env = "ohos", feature = "ohui_srv")))] match vm_config.display { #[cfg(feature = "gtk")] - Some(ref ds_cfg) if ds_cfg.gtk => { + Some(ref ds_cfg) if ds_cfg.display_type == "gtk" => { let ui_context = UiContext { vm_name: vm_config.guest_name.clone(), power_button: Some(self.power_button.clone()), @@ -741,7 +736,7 @@ impl MachineOps for StdMachine { } // OHUI server init. #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] - Some(ref ds_cfg) if ds_cfg.ohui_config.ohui => { + Some(ref ds_cfg) if ds_cfg.display_type == "ohui" => { ohui_init(self.ohui_server.as_ref().unwrap().clone(), ds_cfg) .with_context(|| "Failed to init OH UI server!")?; } @@ -757,12 +752,12 @@ impl MachineOps for StdMachine { #[cfg(feature = "ramfb")] fn add_ramfb(&mut self, cfg_args: &str) -> Result<()> { - let install = parse_ramfb(cfg_args)?; + let config = RamfbConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let fwcfg_dev = self .get_fwcfg_dev() .with_context(|| "Ramfb device must be used UEFI to boot, please add pflash devices")?; let sys_mem = self.get_sys_mem(); - let mut ramfb = Ramfb::new(sys_mem.clone(), install); + let mut ramfb = Ramfb::new(sys_mem.clone(), config.install); ramfb.ramfb_state.setup(&fwcfg_dev)?; ramfb.realize(&mut self.base.sysbus) @@ -797,7 +792,6 @@ pub(crate) fn arch_syscall_whitelist() -> Vec { BpfRule::new(libc::SYS_epoll_pwait), BpfRule::new(libc::SYS_mkdirat), BpfRule::new(libc::SYS_unlinkat), - BpfRule::new(libc::SYS_clone), BpfRule::new(libc::SYS_rt_sigaction), #[cfg(target_env = "gnu")] BpfRule::new(libc::SYS_rseq), diff --git a/machine/src/error.rs b/machine/src/error.rs index c8fb8ee..f502b8f 100644 --- a/machine/src/error.rs +++ b/machine/src/error.rs @@ -94,10 +94,10 @@ pub enum MachineError { #[cfg(target_arch = "x86_64")] CrtPitErr, #[error("Failed to generate FDT.")] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] GenFdtErr, #[error("Failed to write FDT: addr={0}, size={1}")] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] WrtFdtErr(u64, usize), #[error("Failed to register event notifier.")] RegNotifierErr, diff --git a/machine/src/lib.rs b/machine/src/lib.rs index c1c0c22..e3ba141 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -13,6 +13,9 @@ #[cfg(target_arch = "aarch64")] pub mod aarch64; pub mod error; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; +#[cfg(not(target_arch = "riscv64"))] pub mod standard_common; #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -21,6 +24,7 @@ mod micro_common; pub use crate::error::MachineError; pub use micro_common::LightMachine; +#[cfg(not(target_arch = "riscv64"))] pub use standard_common::StdMachine; use std::collections::{BTreeMap, HashMap}; @@ -47,47 +51,46 @@ use cpu::CPUFeatures; use cpu::{ArchCPU, CPUBootConfig, CPUHypervisorOps, CPUInterface, CPUTopology, CpuTopology, CPU}; use devices::legacy::FwCfgOps; #[cfg(feature = "pvpanic")] -use devices::misc::pvpanic::PvPanicPci; +use devices::misc::pvpanic::{PvPanicPci, PvpanicDevConfig}; #[cfg(feature = "scream")] use devices::misc::scream::{Scream, ScreamConfig}; #[cfg(feature = "demo_device")] -use devices::pci::demo_device::DemoDev; -use devices::pci::{PciBus, PciDevOps, PciHost, RootPort}; +use devices::pci::demo_device::{DemoDev, DemoDevConfig}; +use devices::pci::{ + devices_register_pcidevops_type, register_pcidevops_type, PciBus, PciDevOps, PciHost, RootPort, + RootPortConfig, +}; use devices::smbios::smbios_table::{build_smbios_ep30, SmbiosTable}; use devices::smbios::{SMBIOS_ANCHOR_FILE, SMBIOS_TABLE_FILE}; -use devices::sysbus::{SysBus, SysBusDevOps, SysBusDevType}; +use devices::sysbus::{devices_register_sysbusdevops_type, SysBus, SysBusDevOps, SysBusDevType}; #[cfg(feature = "usb_camera")] use devices::usb::camera::{UsbCamera, UsbCameraConfig}; use devices::usb::keyboard::{UsbKeyboard, UsbKeyboardConfig}; +use devices::usb::storage::{UsbStorage, UsbStorageConfig}; use devices::usb::tablet::{UsbTablet, UsbTabletConfig}; +use devices::usb::uas::{UsbUas, UsbUasConfig}; #[cfg(feature = "usb_host")] use devices::usb::usbhost::{UsbHost, UsbHostConfig}; use devices::usb::xhci::xhci_pci::{XhciConfig, XhciPciDevice}; -use devices::usb::{storage::UsbStorage, UsbDevice}; -#[cfg(target_arch = "aarch64")] +use devices::usb::UsbDevice; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use devices::InterruptController; -use devices::ScsiDisk::{ScsiDevice, SCSI_TYPE_DISK, SCSI_TYPE_ROM}; +use devices::ScsiDisk::{ScsiDevConfig, ScsiDevice}; +#[cfg(not(target_arch = "riscv64"))] +use hypervisor::test::TestHypervisor; use hypervisor::{kvm::KvmHypervisor, HypervisorOps}; #[cfg(feature = "usb_camera")] use machine_manager::config::get_cameradev_by_id; -#[cfg(feature = "demo_device")] -use machine_manager::config::parse_demo_dev; -#[cfg(feature = "virtio_gpu")] -use machine_manager::config::parse_gpu; -#[cfg(feature = "pvpanic")] -use machine_manager::config::parse_pvpanic; -use machine_manager::config::parse_usb_storage; use machine_manager::config::{ - complete_numa_node, get_multi_function, get_pci_bdf, parse_blk, parse_device_id, - parse_device_type, parse_fs, parse_net, parse_numa_distance, parse_numa_mem, parse_rng_dev, - parse_root_port, parse_scsi_controller, parse_scsi_device, parse_vfio, parse_vhost_user_blk, - parse_virtio_serial, parse_virtserialport, parse_vsock, str_slip_to_clap, BootIndexInfo, - BootSource, DriveFile, Incoming, MachineMemConfig, MigrateMode, NumaConfig, NumaDistance, - NumaNode, NumaNodes, PFlashConfig, PciBdf, SerialConfig, VfioConfig, VmConfig, FAST_UNPLUG_ON, - MAX_VIRTIO_QUEUE, + complete_numa_node, get_chardev_socket_path, get_class_type, get_pci_bdf, + get_value_of_parameter, parse_numa_distance, parse_numa_mem, str_slip_to_clap, BootIndexInfo, + BootSource, ConfigCheck, DriveConfig, DriveFile, Incoming, MachineMemConfig, MigrateMode, + NetworkInterfaceConfig, NumaNode, NumaNodes, PciBdf, SerialConfig, VirtioSerialInfo, + VirtioSerialPortCfg, VmConfig, FAST_UNPLUG_ON, MAX_VIRTIO_QUEUE, }; use machine_manager::event_loop::EventLoop; -use machine_manager::machine::{MachineInterface, VmState}; +use machine_manager::machine::{HypervisorType, MachineInterface, VmState}; +use machine_manager::{check_arg_exist, check_arg_nonexist}; use migration::{MigrateOps, MigrationManager}; #[cfg(feature = "windows_emu_pid")] use ui::console::{get_run_stage, VmRunningStage}; @@ -96,18 +99,26 @@ use util::{ arg_parser, seccomp::{BpfRule, SeccompOpt, SyscallFilter}, }; -use vfio::{VfioDevice, VfioPciDevice, KVM_DEVICE_FD}; -#[cfg(feature = "virtio_gpu")] -use virtio::Gpu; +use vfio::{vfio_register_pcidevops_type, VfioConfig, VfioDevice, VfioPciDevice, KVM_DEVICE_FD}; #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] use virtio::VirtioDeviceQuirk; use virtio::{ - balloon_allow_list, find_port_by_nr, get_max_nr, vhost, Balloon, BalloonConfig, Block, - BlockState, Rng, RngState, - ScsiCntlr::{scsi_cntlr_create_scsi_bus, ScsiCntlr}, - Serial, SerialPort, VhostKern, VhostUser, VirtioDevice, VirtioMmioDevice, VirtioMmioState, - VirtioNetState, VirtioPciDevice, VirtioSerialState, VIRTIO_TYPE_CONSOLE, + balloon_allow_list, find_port_by_nr, get_max_nr, vhost, virtio_register_pcidevops_type, + virtio_register_sysbusdevops_type, Balloon, BalloonConfig, Block, BlockState, Rng, RngConfig, + RngState, + ScsiCntlr::{scsi_cntlr_create_scsi_bus, ScsiCntlr, ScsiCntlrConfig}, + Serial, SerialPort, VhostKern, VhostUser, VirtioBlkDevConfig, VirtioDevice, VirtioMmioDevice, + VirtioMmioState, VirtioNetState, VirtioPciDevice, VirtioSerialState, VIRTIO_TYPE_CONSOLE, }; +#[cfg(feature = "virtio_gpu")] +use virtio::{Gpu, GpuDevConfig}; + +#[cfg(feature = "windows_emu_pid")] +const WINDOWS_EMU_PID_DEFAULT_INTERVAL: u64 = 4000; +#[cfg(feature = "windows_emu_pid")] +const WINDOWS_EMU_PID_SHUTDOWN_INTERVAL: u64 = 1000; +#[cfg(feature = "windows_emu_pid")] +const WINDOWS_EMU_PID_POWERDOWN_INTERVAL: u64 = 30000; /// Machine structure include base members. pub struct MachineBase { @@ -116,7 +127,7 @@ pub struct MachineBase { /// `vCPU` devices. cpus: Vec>, /// Interrupt controller device. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: Option>, /// Memory address space. sys_mem: Arc, @@ -186,12 +197,26 @@ impl MachineBase { mmio_region, ); - let hypervisor = Arc::new(Mutex::new(KvmHypervisor::new()?)); + let hypervisor: Arc>; + let migration_hypervisor: Arc>; + match vm_config.machine_config.hypervisor { + HypervisorType::Kvm => { + let kvm_hypervisor = Arc::new(Mutex::new(KvmHypervisor::new()?)); + hypervisor = kvm_hypervisor.clone(); + migration_hypervisor = kvm_hypervisor; + } + #[cfg(not(target_arch = "riscv64"))] + HypervisorType::Test => { + let test_hypervisor = Arc::new(Mutex::new(TestHypervisor::new()?)); + hypervisor = test_hypervisor.clone(); + migration_hypervisor = test_hypervisor; + } + }; Ok(MachineBase { cpu_topo, cpus: Vec::new(), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: None, sys_mem, #[cfg(target_arch = "x86_64")] @@ -204,8 +229,8 @@ impl MachineBase { drive_files: Arc::new(Mutex::new(vm_config.init_drive_files()?)), fwcfg_dev: None, machine_ram, - hypervisor: hypervisor.clone(), - migration_hypervisor: hypervisor, + hypervisor, + migration_hypervisor, }) } @@ -269,13 +294,13 @@ macro_rules! create_device_add_matches { match $command { $( $($driver_name)|+ => { - $controller.$function_name($($arg),*)?; + $controller.$function_name($($arg),*).with_context(|| format!("add {} fail.", $command))?; }, )* $( #[cfg($($features)*)] $driver_name1 => { - $controller.$function_name1($($arg1),*)?; + $controller.$function_name1($($arg1),*).with_context(|| format!("add {} fail.", $command))?; }, )* _ => bail!("Unsupported device: {:?}", $command), @@ -318,7 +343,7 @@ pub trait MachineOps { #[cfg(target_arch = "x86_64")] fn load_boot_source(&self, fwcfg: Option<&Arc>>) -> Result; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_boot_source( &self, fwcfg: Option<&Arc>>, @@ -396,6 +421,7 @@ pub trait MachineOps { sys_mem: &Arc, nr_cpus: u8, ) -> Result<()> { + trace::trace_scope_start!(init_memory); let migrate_info = self.get_migrate_info(); if migrate_info.0 != MigrateMode::File { self.create_machine_ram(mem_config, nr_cpus)?; @@ -439,6 +465,8 @@ pub trait MachineOps { #[cfg(target_arch = "aarch64")] let arch_cpu = ArchCPU::new(u32::from(vcpu_id)); + #[cfg(target_arch = "riscv64")] + let arch_cpu = ArchCPU::new(u32::from(vcpu_id)); #[cfg(target_arch = "x86_64")] let arch_cpu = ArchCPU::new(u32::from(vcpu_id), u32::from(max_cpus)); @@ -465,7 +493,7 @@ pub trait MachineOps { nr_cpus: u8, #[cfg(target_arch = "x86_64")] max_cpus: u8, topology: &CPUTopology, - boot_cfg: &Option, + boot_cfg: &CPUBootConfig, #[cfg(target_arch = "aarch64")] vcpu_cfg: &CPUFeatures, ) -> Result>> where @@ -559,34 +587,31 @@ pub trait MachineOps { /// /// * `cfg_args` - Device configuration. fn add_virtio_vsock(&mut self, cfg_args: &str) -> Result<()> { - let device_cfg = parse_vsock(cfg_args)?; + let device_cfg = + VhostKern::VsockConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let sys_mem = self.get_sys_mem().clone(); let vsock = Arc::new(Mutex::new(VhostKern::Vsock::new(&device_cfg, &sys_mem))); - match parse_device_type(cfg_args)?.as_str() { + match device_cfg.classtype.as_str() { "vhost-vsock-device" => { - let device = VirtioMmioDevice::new(&sys_mem, vsock.clone()); + check_arg_nonexist!( + ("bus", device_cfg.bus), + ("addr", device_cfg.addr), + ("multifunction", device_cfg.multifunction) + ); + let device = self + .add_virtio_mmio_device(device_cfg.id.clone(), vsock.clone()) + .with_context(|| MachineError::RlzVirtioMmioErr)?; MigrationManager::register_device_instance( VirtioMmioState::descriptor(), - self.realize_virtio_mmio_device(device) - .with_context(|| MachineError::RlzVirtioMmioErr)?, + device, &device_cfg.id, ); } _ => { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let mut virtio_pci_device = VirtioPciDevice::new( - device_cfg.id.clone(), - devfn, - sys_mem, - vsock.clone(), - parent_bus, - multi_func, - ); - virtio_pci_device.enable_need_irqfd(); - virtio_pci_device - .realize() + check_arg_exist!(("bus", device_cfg.bus), ("addr", device_cfg.addr)); + let bdf = PciBdf::new(device_cfg.bus.clone().unwrap(), device_cfg.addr.unwrap()); + let multi_func = device_cfg.multifunction.unwrap_or_default(); + self.add_virtio_pci_device(&device_cfg.id, &bdf, vsock.clone(), multi_func, true) .with_context(|| "Failed to add virtio pci vsock device")?; } } @@ -600,9 +625,10 @@ pub trait MachineOps { Ok(()) } - fn realize_virtio_mmio_device( + fn add_virtio_mmio_device( &mut self, - _dev: VirtioMmioDevice, + _name: String, + _device: Arc>, ) -> Result>> { bail!("Virtio mmio devices not supported"); } @@ -663,36 +689,26 @@ pub trait MachineOps { if vm_config.dev_name.get("balloon").is_some() { bail!("Only one balloon device is supported for each vm."); } - let config = BalloonConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = BalloonConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; vm_config.dev_name.insert("balloon".to_string(), 1); let sys_mem = self.get_sys_mem(); let balloon = Arc::new(Mutex::new(Balloon::new(config.clone(), sys_mem.clone()))); Balloon::object_init(balloon.clone()); - match parse_device_type(cfg_args)?.as_str() { + match config.classtype.as_str() { "virtio-balloon-device" => { - if config.addr.is_some() || config.bus.is_some() || config.multifunction.is_some() { - bail!("virtio balloon device config is error!"); - } - let device = VirtioMmioDevice::new(sys_mem, balloon); - self.realize_virtio_mmio_device(device)?; + check_arg_nonexist!( + ("bus", config.bus), + ("addr", config.addr), + ("multifunction", config.multifunction) + ); + self.add_virtio_mmio_device(config.id.clone(), balloon)?; } _ => { - if config.addr.is_none() || config.bus.is_none() { - bail!("virtio balloon pci config is error!"); - } - let bdf = PciBdf { - bus: config.bus.unwrap(), - addr: config.addr.unwrap(), - }; + check_arg_exist!(("bus", config.bus), ("addr", config.addr)); + let bdf = PciBdf::new(config.bus.unwrap(), config.addr.unwrap()); let multi_func = config.multifunction.unwrap_or_default(); - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let sys_mem = self.get_sys_mem().clone(); - let virtio_pci_device = VirtioPciDevice::new( - config.id, devfn, sys_mem, balloon, parent_bus, multi_func, - ); - virtio_pci_device - .realize() + self.add_virtio_pci_device(&config.id, &bdf, balloon, multi_func, false) .with_context(|| "Failed to add virtio pci balloon device")?; } } @@ -707,34 +723,35 @@ pub trait MachineOps { /// * `vm_config` - VM configuration. /// * `cfg_args` - Device configuration args. fn add_virtio_serial(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let serial_cfg = parse_virtio_serial(vm_config, cfg_args)?; - let sys_mem = self.get_sys_mem().clone(); + if vm_config.virtio_serial.is_some() { + bail!("Only one virtio serial device is supported"); + } + let mut serial_cfg = + VirtioSerialInfo::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + serial_cfg.auto_max_ports(); let serial = Arc::new(Mutex::new(Serial::new(serial_cfg.clone()))); - match parse_device_type(cfg_args)?.as_str() { + match serial_cfg.classtype.as_str() { "virtio-serial-device" => { - let device = VirtioMmioDevice::new(&sys_mem, serial.clone()); + check_arg_nonexist!( + ("bus", serial_cfg.bus), + ("addr", serial_cfg.addr), + ("multifunction", serial_cfg.multifunction) + ); + let device = self + .add_virtio_mmio_device(serial_cfg.id.clone(), serial.clone()) + .with_context(|| MachineError::RlzVirtioMmioErr)?; MigrationManager::register_device_instance( VirtioMmioState::descriptor(), - self.realize_virtio_mmio_device(device) - .with_context(|| MachineError::RlzVirtioMmioErr)?, + device, &serial_cfg.id, ); } _ => { - let bdf = serial_cfg.pci_bdf.unwrap(); - let multi_func = serial_cfg.multifunction; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let virtio_pci_device = VirtioPciDevice::new( - serial_cfg.id.clone(), - devfn, - sys_mem, - serial.clone(), - parent_bus, - multi_func, - ); - virtio_pci_device - .realize() + check_arg_exist!(("bus", serial_cfg.bus), ("addr", serial_cfg.addr)); + let bdf = PciBdf::new(serial_cfg.bus.clone().unwrap(), serial_cfg.addr.unwrap()); + let multi_func = serial_cfg.multifunction.unwrap_or_default(); + self.add_virtio_pci_device(&serial_cfg.id, &bdf, serial.clone(), multi_func, false) .with_context(|| "Failed to add virtio pci serial device")?; } } @@ -745,6 +762,7 @@ pub trait MachineOps { &serial_cfg.id, ); + vm_config.virtio_serial = Some(serial_cfg); Ok(()) } @@ -761,7 +779,7 @@ pub trait MachineOps { .with_context(|| "No virtio serial device specified")?; let mut virtio_device = None; - if serial_cfg.pci_bdf.is_none() { + if serial_cfg.bus.is_none() { // Micro_vm. for dev in self.get_sys_bus().devices.iter() { let locked_busdev = dev.lock().unwrap(); @@ -798,24 +816,31 @@ pub trait MachineOps { let mut virtio_dev_h = virtio_dev.lock().unwrap(); let serial = virtio_dev_h.as_any_mut().downcast_mut::().unwrap(); - let is_console = matches!(parse_device_type(cfg_args)?.as_str(), "virtconsole"); + let mut serialport_cfg = + VirtioSerialPortCfg::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let free_port0 = find_port_by_nr(&serial.ports, 0).is_none(); // Note: port 0 is reserved for a virtconsole. let free_nr = get_max_nr(&serial.ports) + 1; - let serialport_cfg = - parse_virtserialport(vm_config, cfg_args, is_console, free_nr, free_port0)?; - if serialport_cfg.nr >= serial.max_nr_ports { + serialport_cfg.auto_nr(free_port0, free_nr, serial.max_nr_ports)?; + serialport_cfg.check()?; + if find_port_by_nr(&serial.ports, serialport_cfg.nr.unwrap()).is_some() { bail!( - "virtio serial port nr {} should be less than virtio serial's max_nr_ports {}", - serialport_cfg.nr, - serial.max_nr_ports + "Repetitive virtio serial port nr {}.", + serialport_cfg.nr.unwrap() ); } - if find_port_by_nr(&serial.ports, serialport_cfg.nr).is_some() { - bail!("Repetitive virtio serial port nr {}.", serialport_cfg.nr,); - } + let is_console = matches!(serialport_cfg.classtype.as_str(), "virtconsole"); + let chardev_cfg = vm_config + .chardev + .remove(&serialport_cfg.chardev) + .with_context(|| { + format!( + "Chardev {:?} not found or is in use", + &serialport_cfg.chardev + ) + })?; - let mut serial_port = SerialPort::new(serialport_cfg); + let mut serial_port = SerialPort::new(serialport_cfg, chardev_cfg); let port = Arc::new(Mutex::new(serial_port.clone())); serial_port.realize()?; if !is_console { @@ -833,36 +858,35 @@ pub trait MachineOps { /// * `vm_config` - VM configuration. /// * `cfg_args` - Device configuration arguments. fn add_virtio_rng(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let device_cfg = parse_rng_dev(vm_config, cfg_args)?; - let sys_mem = self.get_sys_mem(); - let rng_dev = Arc::new(Mutex::new(Rng::new(device_cfg.clone()))); + let rng_cfg = RngConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + rng_cfg.bytes_per_sec()?; + let rngobj_cfg = vm_config + .object + .rng_object + .remove(&rng_cfg.rng) + .with_context(|| "Object for rng-random device not found")?; + let rng_dev = Arc::new(Mutex::new(Rng::new(rng_cfg.clone(), rngobj_cfg))); - match parse_device_type(cfg_args)?.as_str() { + match rng_cfg.classtype.as_str() { "virtio-rng-device" => { - let device = VirtioMmioDevice::new(sys_mem, rng_dev.clone()); - self.realize_virtio_mmio_device(device) + check_arg_nonexist!( + ("bus", rng_cfg.bus), + ("addr", rng_cfg.addr), + ("multifunction", rng_cfg.multifunction) + ); + self.add_virtio_mmio_device(rng_cfg.id.clone(), rng_dev.clone()) .with_context(|| "Failed to add virtio mmio rng device")?; } _ => { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let sys_mem = self.get_sys_mem().clone(); - let vitio_pci_device = VirtioPciDevice::new( - device_cfg.id.clone(), - devfn, - sys_mem, - rng_dev.clone(), - parent_bus, - multi_func, - ); - vitio_pci_device - .realize() + check_arg_exist!(("bus", rng_cfg.bus), ("addr", rng_cfg.addr)); + let bdf = PciBdf::new(rng_cfg.bus.clone().unwrap(), rng_cfg.addr.unwrap()); + let multi_func = rng_cfg.multifunction.unwrap_or_default(); + self.add_virtio_pci_device(&rng_cfg.id, &bdf, rng_dev.clone(), multi_func, false) .with_context(|| "Failed to add pci rng device")?; } } - MigrationManager::register_device_instance(RngState::descriptor(), rng_dev, &device_cfg.id); + MigrationManager::register_device_instance(RngState::descriptor(), rng_dev, &rng_cfg.id); Ok(()) } @@ -877,32 +901,41 @@ pub trait MachineOps { /// * 'vm_config' - VM configuration. /// * 'cfg_args' - Device configuration arguments. fn add_virtio_fs(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let dev_cfg = parse_fs(vm_config, cfg_args)?; - let id_clone = dev_cfg.id.clone(); + let dev_cfg = + vhost::user::FsConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let char_dev = vm_config + .chardev + .remove(&dev_cfg.chardev) + .with_context(|| format!("Chardev {:?} not found or is in use", &dev_cfg.chardev))?; let sys_mem = self.get_sys_mem().clone(); if !vm_config.machine_config.mem_config.mem_share { bail!("When configuring the vhost-user-fs-device or vhost-user-fs-pci device, the memory must be shared."); } - match parse_device_type(cfg_args)?.as_str() { + let device = Arc::new(Mutex::new(vhost::user::Fs::new( + dev_cfg.clone(), + char_dev, + sys_mem, + ))); + match dev_cfg.classtype.as_str() { "vhost-user-fs-device" => { - let device = Arc::new(Mutex::new(vhost::user::Fs::new(dev_cfg, sys_mem.clone()))); - let virtio_mmio_device = VirtioMmioDevice::new(&sys_mem, device); - self.realize_virtio_mmio_device(virtio_mmio_device) + check_arg_nonexist!( + ("bus", dev_cfg.bus), + ("addr", dev_cfg.addr), + ("multifunction", dev_cfg.multifunction) + ); + self.add_virtio_mmio_device(dev_cfg.id.clone(), device) .with_context(|| "Failed to add vhost user fs device")?; } _ => { - let device = Arc::new(Mutex::new(vhost::user::Fs::new(dev_cfg, sys_mem.clone()))); - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - - let mut vitio_pci_device = - VirtioPciDevice::new(id_clone, devfn, sys_mem, device, parent_bus, multi_func); - vitio_pci_device.enable_need_irqfd(); - vitio_pci_device - .realize() + check_arg_exist!(("bus", dev_cfg.bus), ("addr", dev_cfg.addr)); + let bdf = PciBdf::new(dev_cfg.bus.clone().unwrap(), dev_cfg.addr.unwrap()); + let multi_func = dev_cfg.multifunction.unwrap_or_default(); + let root_bus = self.get_pci_host()?.lock().unwrap().root_bus.clone(); + let msi_irq_manager = root_bus.lock().unwrap().msi_irq_manager.clone(); + let need_irqfd = msi_irq_manager.as_ref().unwrap().irqfd_enable(); + self.add_virtio_pci_device(&dev_cfg.id, &bdf, device, multi_func, need_irqfd) .with_context(|| "Failed to add pci fs device")?; } } @@ -1059,11 +1092,10 @@ pub trait MachineOps { #[cfg(feature = "pvpanic")] fn add_pvpanic(&mut self, cfg_args: &str) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let device_cfg = parse_pvpanic(cfg_args)?; - + let config = PvpanicDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let bdf = PciBdf::new(config.bus.clone(), config.addr); let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let pcidev = PvPanicPci::new(&device_cfg, devfn, parent_bus); + let pcidev = PvPanicPci::new(&config, devfn, parent_bus); pcidev .realize() .with_context(|| "Failed to realize pvpanic device")?; @@ -1077,26 +1109,38 @@ pub trait MachineOps { cfg_args: &str, hotplug: bool, ) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let queues_auto = Some(VirtioPciDevice::virtio_pci_auto_queues_num( - 0, - vm_config.machine_config.nr_cpus, - MAX_VIRTIO_QUEUE, - )); - let device_cfg = parse_blk(vm_config, cfg_args, queues_auto)?; - if let Some(bootindex) = device_cfg.boot_index { + let mut device_cfg = + VirtioBlkDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + check_arg_exist!(("bus", device_cfg.bus), ("addr", device_cfg.addr)); + let bdf = PciBdf::new(device_cfg.bus.clone().unwrap(), device_cfg.addr.unwrap()); + let multi_func = device_cfg.multifunction.unwrap_or_default(); + if device_cfg.num_queues.is_none() { + let queues_auto = VirtioPciDevice::virtio_pci_auto_queues_num( + 0, + vm_config.machine_config.nr_cpus, + MAX_VIRTIO_QUEUE, + ); + device_cfg.num_queues = Some(queues_auto); + } + if let Some(bootindex) = device_cfg.bootindex { self.check_bootindex(bootindex) .with_context(|| "Fail to add virtio pci blk device for invalid bootindex")?; } + + let drive_cfg = vm_config + .drives + .remove(&device_cfg.drive) + .with_context(|| "No drive configured matched for blk device")?; + let device = Arc::new(Mutex::new(Block::new( device_cfg.clone(), + drive_cfg, self.get_drive_files(), ))); let pci_dev = self .add_virtio_pci_device(&device_cfg.id, &bdf, device.clone(), multi_func, false) .with_context(|| "Failed to add virtio pci device")?; - if let Some(bootindex) = device_cfg.boot_index { + if let Some(bootindex) = device_cfg.bootindex { // Eg: OpenFirmware device path(virtio-blk disk): // /pci@i0cf8/scsi@6[,3]/disk@0,0 // | | | | | @@ -1125,54 +1169,55 @@ pub trait MachineOps { cfg_args: &str, hotplug: bool, ) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let queues_auto = Some(VirtioPciDevice::virtio_pci_auto_queues_num( - 0, - vm_config.machine_config.nr_cpus, - MAX_VIRTIO_QUEUE, - )); - let device_cfg = parse_scsi_controller(cfg_args, queues_auto)?; + let mut device_cfg = + ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let bdf = PciBdf::new(device_cfg.bus.clone(), device_cfg.addr); + let multi_func = device_cfg.multifunction.unwrap_or_default(); + if device_cfg.num_queues.is_none() { + let queues_auto = VirtioPciDevice::virtio_pci_auto_queues_num( + 0, + vm_config.machine_config.nr_cpus, + MAX_VIRTIO_QUEUE, + ); + device_cfg.num_queues = Some(queues_auto as u32); + } let device = Arc::new(Mutex::new(ScsiCntlr::new(device_cfg.clone()))); let bus_name = format!("{}.0", device_cfg.id); scsi_cntlr_create_scsi_bus(&bus_name, &device)?; - let pci_dev = self - .add_virtio_pci_device(&device_cfg.id, &bdf, device.clone(), multi_func, false) + self.add_virtio_pci_device(&device_cfg.id, &bdf, device, multi_func, false) .with_context(|| "Failed to add virtio scsi controller")?; if !hotplug { self.reset_bus(&device_cfg.id)?; } - device.lock().unwrap().config.boot_prefix = pci_dev.lock().unwrap().get_dev_path(); Ok(()) } fn add_scsi_device(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let device_cfg = parse_scsi_device(vm_config, cfg_args)?; - let scsi_type = match parse_device_type(cfg_args)?.as_str() { - "scsi-hd" => SCSI_TYPE_DISK, - _ => SCSI_TYPE_ROM, - }; - if let Some(bootindex) = device_cfg.boot_index { + let device_cfg = ScsiDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let drive_arg = vm_config + .drives + .remove(&device_cfg.drive) + .with_context(|| "No drive configured matched for scsi device")?; + + if let Some(bootindex) = device_cfg.bootindex { self.check_bootindex(bootindex) .with_context(|| "Failed to add scsi device for invalid bootindex")?; } let device = Arc::new(Mutex::new(ScsiDevice::new( device_cfg.clone(), - scsi_type, + drive_arg, self.get_drive_files(), ))); + // Bus name `$parent_cntlr_name.0` is checked when parsing by clap. + let cntlr = device_cfg.bus.split('.').collect::>()[0].to_string(); let pci_dev = self - .get_pci_dev_by_id_and_type(vm_config, Some(&device_cfg.cntlr), "virtio-scsi-pci") - .with_context(|| { - format!( - "Can not find scsi controller from pci bus {}", - device_cfg.cntlr - ) - })?; + .get_pci_dev_by_id_and_type(vm_config, Some(&cntlr), "virtio-scsi-pci") + .with_context(|| format!("Can not find scsi controller from pci bus {}", cntlr))?; let locked_pcidev = pci_dev.lock().unwrap(); + let prefix = locked_pcidev.get_dev_path().unwrap(); let virtio_pcidev = locked_pcidev .as_any() .downcast_ref::() @@ -1197,7 +1242,7 @@ pub trait MachineOps { .insert((device_cfg.target, device_cfg.lun), device.clone()); device.lock().unwrap().parent_bus = Arc::downgrade(bus); - if let Some(bootindex) = device_cfg.boot_index { + if let Some(bootindex) = device_cfg.bootindex { // Eg: OpenFirmware device path(virtio-scsi disk): // /pci@i0cf8/scsi@7[,3]/channel@0/disk@2,3 // | | | | | | @@ -1205,7 +1250,6 @@ pub trait MachineOps { // | | | channel(unused, fixed 0). // | PCI slot,[function] holding SCSI controller. // PCI root as system bus port. - let prefix = cntlr.config.boot_prefix.as_ref().unwrap(); let dev_path = format! {"{}/channel@0/disk@{:x},{:x}", prefix, device_cfg.target, device_cfg.lun}; self.add_bootindex_devices(bootindex, &dev_path, &device_cfg.id); @@ -1219,35 +1263,53 @@ pub trait MachineOps { cfg_args: &str, hotplug: bool, ) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let device_cfg = parse_net(vm_config, cfg_args)?; + let net_cfg = + NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let netdev_cfg = vm_config + .netdevs + .remove(&net_cfg.netdev) + .with_context(|| format!("Netdev: {:?} not found for net device", &net_cfg.netdev))?; + check_arg_exist!(("bus", net_cfg.bus), ("addr", net_cfg.addr)); + let bdf = PciBdf::new(net_cfg.bus.clone().unwrap(), net_cfg.addr.unwrap()); + let multi_func = net_cfg.multifunction.unwrap_or_default(); + let mut need_irqfd = false; - let device: Arc> = if device_cfg.vhost_type.is_some() { + let device: Arc> = if netdev_cfg.vhost_type().is_some() { need_irqfd = true; - if device_cfg.vhost_type == Some(String::from("vhost-kernel")) { + if netdev_cfg.vhost_type().unwrap() == "vhost-kernel" { Arc::new(Mutex::new(VhostKern::Net::new( - &device_cfg, + &net_cfg, + netdev_cfg, self.get_sys_mem(), ))) } else { + let chardev = netdev_cfg.chardev.clone().with_context(|| { + format!("Chardev not configured for netdev {:?}", netdev_cfg.id) + })?; + let chardev_cfg = vm_config + .chardev + .remove(&chardev) + .with_context(|| format!("Chardev: {:?} not found for netdev", chardev))?; + let sock_path = get_chardev_socket_path(chardev_cfg)?; Arc::new(Mutex::new(VhostUser::Net::new( - &device_cfg, + &net_cfg, + netdev_cfg, + sock_path, self.get_sys_mem(), ))) } } else { - let device = Arc::new(Mutex::new(virtio::Net::new(device_cfg.clone()))); + let device = Arc::new(Mutex::new(virtio::Net::new(net_cfg.clone(), netdev_cfg))); MigrationManager::register_device_instance( VirtioNetState::descriptor(), device.clone(), - &device_cfg.id, + &net_cfg.id, ); device }; - self.add_virtio_pci_device(&device_cfg.id, &bdf, device, multi_func, need_irqfd)?; + self.add_virtio_pci_device(&net_cfg.id, &bdf, device, multi_func, need_irqfd)?; if !hotplug { - self.reset_bus(&device_cfg.id)?; + self.reset_bus(&net_cfg.id)?; } Ok(()) } @@ -1258,27 +1320,43 @@ pub trait MachineOps { cfg_args: &str, hotplug: bool, ) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let queues_auto = Some(VirtioPciDevice::virtio_pci_auto_queues_num( - 0, - vm_config.machine_config.nr_cpus, - MAX_VIRTIO_QUEUE, - )); - let device_cfg = parse_vhost_user_blk(vm_config, cfg_args, queues_auto)?; + let mut device_cfg = VhostUser::VhostUserBlkDevConfig::try_parse_from(str_slip_to_clap( + cfg_args, true, false, + ))?; + check_arg_exist!(("bus", device_cfg.bus), ("addr", device_cfg.addr)); + let bdf = PciBdf::new(device_cfg.bus.clone().unwrap(), device_cfg.addr.unwrap()); + if device_cfg.num_queues.is_none() { + let queues_auto = VirtioPciDevice::virtio_pci_auto_queues_num( + 0, + vm_config.machine_config.nr_cpus, + MAX_VIRTIO_QUEUE, + ); + device_cfg.num_queues = Some(queues_auto); + } + let chardev_cfg = vm_config + .chardev + .remove(&device_cfg.chardev) + .with_context(|| { + format!( + "Chardev: {:?} not found for vhost user blk", + &device_cfg.chardev + ) + })?; + let device: Arc> = Arc::new(Mutex::new(VhostUser::Block::new( &device_cfg, + chardev_cfg, self.get_sys_mem(), ))); let pci_dev = self - .add_virtio_pci_device(&device_cfg.id, &bdf, device.clone(), multi_func, true) + .add_virtio_pci_device(&device_cfg.id, &bdf, device.clone(), false, true) .with_context(|| { format!( "Failed to add virtio pci device, device id: {}", &device_cfg.id ) })?; - if let Some(bootindex) = device_cfg.boot_index { + if let Some(bootindex) = device_cfg.bootindex { if let Some(dev_path) = pci_dev.lock().unwrap().get_dev_path() { self.add_bootindex_devices(bootindex, &dev_path, &device_cfg.id); } @@ -1294,60 +1372,55 @@ pub trait MachineOps { vm_config: &mut VmConfig, cfg_args: &str, ) -> Result<()> { - let device_cfg = parse_vhost_user_blk(vm_config, cfg_args, None)?; + let device_cfg = VhostUser::VhostUserBlkDevConfig::try_parse_from(str_slip_to_clap( + cfg_args, true, false, + ))?; + check_arg_nonexist!(("bus", device_cfg.bus), ("addr", device_cfg.addr)); + let chardev_cfg = vm_config + .chardev + .remove(&device_cfg.chardev) + .with_context(|| { + format!( + "Chardev: {:?} not found for vhost user blk", + &device_cfg.chardev + ) + })?; let device: Arc> = Arc::new(Mutex::new(VhostUser::Block::new( &device_cfg, + chardev_cfg, self.get_sys_mem(), ))); - let virtio_mmio_device = VirtioMmioDevice::new(self.get_sys_mem(), device); - self.realize_virtio_mmio_device(virtio_mmio_device) + self.add_virtio_mmio_device(device_cfg.id.clone(), device) .with_context(|| "Failed to add vhost user block device")?; Ok(()) } - fn create_vfio_pci_device( - &mut self, - id: &str, - bdf: &PciBdf, - host: &str, - sysfsdev: &str, - multifunc: bool, - ) -> Result<()> { - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(bdf)?; - let path = if !host.is_empty() { - format!("/sys/bus/pci/devices/{}", host) + fn add_vfio_device(&mut self, cfg_args: &str, hotplug: bool) -> Result<()> { + let hypervisor = self.get_hypervisor(); + let locked_hypervisor = hypervisor.lock().unwrap(); + *KVM_DEVICE_FD.lock().unwrap() = locked_hypervisor.create_vfio_device(); + + let device_cfg = VfioConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let bdf = PciBdf::new(device_cfg.bus.clone(), device_cfg.addr); + let multi_func = device_cfg.multifunction.unwrap_or_default(); + let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; + let path = if device_cfg.host.is_some() { + format!("/sys/bus/pci/devices/{}", device_cfg.host.unwrap()) } else { - sysfsdev.to_string() + device_cfg.sysfsdev.unwrap() }; let device = VfioDevice::new(Path::new(&path), self.get_sys_mem()) .with_context(|| "Failed to create vfio device.")?; let vfio_pci = VfioPciDevice::new( device, devfn, - id.to_string(), + device_cfg.id.to_string(), parent_bus, - multifunc, + multi_func, self.get_sys_mem().clone(), ); VfioPciDevice::realize(vfio_pci).with_context(|| "Failed to realize vfio-pci device.")?; - Ok(()) - } - fn add_vfio_device(&mut self, cfg_args: &str, hotplug: bool) -> Result<()> { - let hypervisor = self.get_hypervisor(); - let locked_hypervisor = hypervisor.lock().unwrap(); - *KVM_DEVICE_FD.lock().unwrap() = locked_hypervisor.create_vfio_device(); - - let device_cfg: VfioConfig = parse_vfio(cfg_args)?; - let bdf = get_pci_bdf(cfg_args)?; - let multifunc = get_multi_function(cfg_args)?; - self.create_vfio_pci_device( - &device_cfg.id, - &bdf, - &device_cfg.host, - &device_cfg.sysfsdev, - multifunc, - )?; if !hotplug { self.reset_bus(&device_cfg.id)?; } @@ -1364,10 +1437,10 @@ pub trait MachineOps { #[cfg(feature = "virtio_gpu")] fn add_virtio_pci_gpu(&mut self, cfg_args: &str) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let multi_func = get_multi_function(cfg_args)?; - let device_cfg = parse_gpu(cfg_args)?; - let device = Arc::new(Mutex::new(Gpu::new(device_cfg.clone()))); + let config = GpuDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + config.check(); + let bdf = PciBdf::new(config.bus.clone(), config.addr); + let device = Arc::new(Mutex::new(Gpu::new(config.clone()))); #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] if device.lock().unwrap().device_quirk() == Some(VirtioDeviceQuirk::VirtioGpuEnableBar0) @@ -1377,7 +1450,7 @@ pub trait MachineOps { device.lock().unwrap().set_bar0_fb(self.get_ohui_fb()); } - self.add_virtio_pci_device(&device_cfg.id, &bdf, device, multi_func, false)?; + self.add_virtio_pci_device(&config.id, &bdf, device, false, false)?; Ok(()) } @@ -1394,21 +1467,15 @@ pub trait MachineOps { } fn add_pci_root_port(&mut self, cfg_args: &str) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - let device_cfg = parse_root_port(cfg_args)?; + let dev_cfg = RootPortConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let bdf = PciBdf::new(dev_cfg.bus.clone(), dev_cfg.addr); + let (_, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; let pci_host = self.get_pci_host()?; let bus = pci_host.lock().unwrap().root_bus.clone(); - if PciBus::find_bus_by_name(&bus, &device_cfg.id).is_some() { - bail!("ID {} already exists.", &device_cfg.id); + if PciBus::find_bus_by_name(&bus, &dev_cfg.id).is_some() { + bail!("ID {} already exists.", &dev_cfg.id); } - let rootport = RootPort::new( - device_cfg.id, - devfn, - device_cfg.port, - parent_bus, - device_cfg.multifunction, - ); + let rootport = RootPort::new(dev_cfg, parent_bus); rootport .realize() .with_context(|| "Failed to add pci root port")?; @@ -1425,17 +1492,15 @@ pub trait MachineOps { ) -> Result>> { let (devfn, parent_bus) = self.get_devfn_and_parent_bus(bdf)?; let sys_mem = self.get_sys_mem(); - let mut pcidev = VirtioPciDevice::new( + let pcidev = VirtioPciDevice::new( id.to_string(), devfn, sys_mem.clone(), device, parent_bus, multi_func, + need_irqfd, ); - if need_irqfd { - pcidev.enable_need_irqfd(); - } let clone_pcidev = Arc::new(Mutex::new(pcidev.clone())); pcidev .realize() @@ -1513,46 +1578,47 @@ pub trait MachineOps { for numa in vm_config.numa_nodes.iter() { match numa.0.as_str() { "node" => { - let numa_config: NumaConfig = parse_numa_mem(numa.1.as_str())?; - if numa_nodes.contains_key(&numa_config.numa_id) { - bail!("Numa node id is repeated {}", numa_config.numa_id); + let node_config = parse_numa_mem(numa.1.as_str())?; + if numa_nodes.contains_key(&node_config.numa_id) { + bail!("Numa node id is repeated {}", node_config.numa_id); } let mut numa_node = NumaNode { - cpus: numa_config.cpus, - mem_dev: numa_config.mem_dev.clone(), + cpus: node_config.cpus, + mem_dev: node_config.mem_dev.clone(), ..Default::default() }; numa_node.size = vm_config .object .mem_object - .remove(&numa_config.mem_dev) + .remove(&node_config.mem_dev) .map(|mem_conf| mem_conf.size) .with_context(|| { format!( "Object for memory-backend {} config not found", - numa_config.mem_dev + node_config.mem_dev ) })?; - numa_nodes.insert(numa_config.numa_id, numa_node); + numa_nodes.insert(node_config.numa_id, numa_node); } "dist" => { - let dist: (u32, NumaDistance) = parse_numa_distance(numa.1.as_str())?; - if !numa_nodes.contains_key(&dist.0) { - bail!("Numa node id is not found {}", dist.0); + let dist_config = parse_numa_distance(numa.1.as_str())?; + if !numa_nodes.contains_key(&dist_config.numa_id) { + bail!("Numa node id is not found {}", dist_config.numa_id); } - if !numa_nodes.contains_key(&dist.1.destination) { - bail!("Numa node id is not found {}", dist.1.destination); + if !numa_nodes.contains_key(&dist_config.destination) { + bail!("Numa node id is not found {}", dist_config.destination); } - if let Some(n) = numa_nodes.get_mut(&dist.0) { - if n.distances.contains_key(&dist.1.destination) { + if let Some(n) = numa_nodes.get_mut(&dist_config.numa_id) { + if n.distances.contains_key(&dist_config.destination) { bail!( "Numa destination info {} repeat settings", - dist.1.destination + dist_config.destination ); } - n.distances.insert(dist.1.destination, dist.1.distance); + n.distances + .insert(dist_config.destination, dist_config.distance); } } _ => { @@ -1577,7 +1643,7 @@ pub trait MachineOps { /// /// * `cfg_args` - XHCI Configuration. fn add_usb_xhci(&mut self, cfg_args: &str) -> Result<()> { - let device_cfg = XhciConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let device_cfg = XhciConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let bdf = PciBdf::new(device_cfg.bus.clone(), device_cfg.addr); let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; @@ -1601,7 +1667,7 @@ pub trait MachineOps { cfg_args: &str, token_id: Option>>, ) -> Result<()> { - let config = ScreamConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = ScreamConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let bdf = PciBdf { bus: config.bus.clone(), addr: config.addr, @@ -1722,16 +1788,18 @@ pub trait MachineOps { /// * `driver` - USB device class. /// * `cfg_args` - USB device Configuration. fn add_usb_device(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let usb_device = match parse_device_type(cfg_args)?.as_str() { + let usb_device = match get_class_type(cfg_args)?.as_str() { "usb-kbd" => { - let config = UsbKeyboardConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = + UsbKeyboardConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let keyboard = UsbKeyboard::new(config); keyboard .realize() .with_context(|| "Failed to realize usb keyboard device")? } "usb-tablet" => { - let config = UsbTabletConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = + UsbTabletConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let tablet = UsbTablet::new(config); tablet .realize() @@ -1739,7 +1807,8 @@ pub trait MachineOps { } #[cfg(feature = "usb_camera")] "usb-camera" => { - let config = UsbCameraConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = + UsbCameraConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let cameradev = get_cameradev_by_id(vm_config, config.cameradev.clone()) .with_context(|| { format!( @@ -1754,15 +1823,32 @@ pub trait MachineOps { .with_context(|| "Failed to realize usb camera device")? } "usb-storage" => { - let device_cfg = parse_usb_storage(vm_config, cfg_args)?; - let storage = UsbStorage::new(device_cfg, self.get_drive_files()); + let device_cfg = + UsbStorageConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let drive_cfg = vm_config + .drives + .remove(&device_cfg.drive) + .with_context(|| "No drive configured matched for usb storage device.")?; + let storage = UsbStorage::new(device_cfg, drive_cfg, self.get_drive_files())?; storage .realize() .with_context(|| "Failed to realize usb storage device")? } + "usb-uas" => { + let device_cfg = + UsbUasConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let drive_cfg = vm_config + .drives + .remove(&device_cfg.drive) + .with_context(|| "No drive configured matched for usb uas device.")?; + let uas = UsbUas::new(device_cfg, drive_cfg, self.get_drive_files()); + uas.realize() + .with_context(|| "Failed to realize usb uas device")? + } #[cfg(feature = "usb_host")] "usb-host" => { - let config = UsbHostConfig::try_parse_from(str_slip_to_clap(cfg_args))?; + let config = + UsbHostConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; let usbhost = UsbHost::new(config)?; usbhost .realize() @@ -1804,7 +1890,7 @@ pub trait MachineOps { for dev in &cloned_vm_config.devices { let cfg_args = dev.1.as_str(); // Check whether the device id exists to ensure device uniqueness. - let id = parse_device_id(cfg_args)?; + let id = get_value_of_parameter("id", cfg_args)?; self.check_device_id_existed(&id) .with_context(|| format!("Failed to check device id: config {}", cfg_args))?; #[cfg(feature = "scream")] @@ -1829,13 +1915,13 @@ pub trait MachineOps { ("vhost-user-blk-pci",add_vhost_user_blk_pci, vm_config, cfg_args, false), ("vhost-user-fs-pci" | "vhost-user-fs-device", add_virtio_fs, vm_config, cfg_args), ("nec-usb-xhci", add_usb_xhci, cfg_args), - ("usb-kbd" | "usb-storage" | "usb-tablet" | "usb-camera" | "usb-host", add_usb_device, vm_config, cfg_args); + ("usb-kbd" | "usb-storage" | "usb-uas" | "usb-tablet" | "usb-camera" | "usb-host", add_usb_device, vm_config, cfg_args); #[cfg(feature = "virtio_gpu")] ("virtio-gpu-pci", add_virtio_pci_gpu, cfg_args), #[cfg(feature = "ramfb")] ("ramfb", add_ramfb, cfg_args), #[cfg(feature = "demo_device")] - ("pcie-demo-dev", add_demo_dev, vm_config, cfg_args), + ("pcie-demo-dev", add_demo_dev, cfg_args), #[cfg(feature = "scream")] ("ivshmem-scream", add_ivshmem_scream, vm_config, cfg_args, token_id), #[cfg(feature = "pvpanic")] @@ -1850,7 +1936,7 @@ pub trait MachineOps { None } - fn add_pflash_device(&mut self, _configs: &[PFlashConfig]) -> Result<()> { + fn add_pflash_device(&mut self, _configs: &[DriveConfig]) -> Result<()> { bail!("Pflash device is not supported!"); } @@ -1863,22 +1949,22 @@ pub trait MachineOps { } #[cfg(feature = "demo_device")] - fn add_demo_dev(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { - let bdf = get_pci_bdf(cfg_args)?; - let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; - - let demo_cfg = parse_demo_dev(vm_config, cfg_args.to_string()) + fn add_demo_dev(&mut self, cfg_args: &str) -> Result<()> { + let config = DemoDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false)) .with_context(|| "failed to parse cmdline for demo dev.")?; - + let bdf = PciBdf::new(config.bus.clone(), config.addr); + let (devfn, parent_bus) = self.get_devfn_and_parent_bus(&bdf)?; let sys_mem = self.get_sys_mem().clone(); - let demo_dev = DemoDev::new(demo_cfg, devfn, sys_mem, parent_bus); + let demo_dev = DemoDev::new(config, devfn, sys_mem, parent_bus); demo_dev.realize() } + #[cfg(not(target_arch = "riscv64"))] /// Return the syscall whitelist for seccomp. fn syscall_whitelist(&self) -> Vec; + #[cfg(not(target_arch = "riscv64"))] /// Register seccomp rules in syscall whitelist to seccomp. fn register_seccomp(&self, balloon_enable: bool) -> Result<()> { let mut seccomp_filter = SyscallFilter::new(SeccompOpt::Trap); @@ -2246,18 +2332,22 @@ fn check_windows_emu_pid( powerdown_req: Arc, shutdown_req: Arc, ) { - let mut check_delay = Duration::from_millis(4000); + let mut check_delay = Duration::from_millis(WINDOWS_EMU_PID_DEFAULT_INTERVAL); if !Path::new(&pid_path).exists() { - log::info!("Detect windows emu exited, let VM exits now"); + log::info!("Detect emulator exited, let VM exits now"); if get_run_stage() == VmRunningStage::Os { + // Wait 30s for windows normal exit. + check_delay = Duration::from_millis(WINDOWS_EMU_PID_POWERDOWN_INTERVAL); if let Err(e) = powerdown_req.write(1) { log::error!("Failed to send powerdown request after emu exits: {:?}", e); } - } else if let Err(e) = shutdown_req.write(1) { - log::error!("Failed to send shutdown request after emu exits: {:?}", e); + } else { + // Wait 1s for windows shutdown. + check_delay = Duration::from_millis(WINDOWS_EMU_PID_SHUTDOWN_INTERVAL); + if let Err(e) = shutdown_req.write(1) { + log::error!("Failed to send shutdown request after emu exits: {:?}", e); + } } - // Continue checking to prevent exit failed. - check_delay = Duration::from_millis(1000); } let check_emu_alive = Box::new(move || { @@ -2271,3 +2361,31 @@ fn check_windows_emu_pid( .unwrap() .timer_add(check_emu_alive, check_delay); } + +fn machine_register_pcidevops_type() -> Result<()> { + #[cfg(target_arch = "x86_64")] + { + register_pcidevops_type::()?; + register_pcidevops_type::()?; + } + #[cfg(target_arch = "aarch64")] + { + register_pcidevops_type::()?; + } + + Ok(()) +} + +pub fn type_init() -> Result<()> { + // Register all sysbus devices type. + virtio_register_sysbusdevops_type()?; + devices_register_sysbusdevops_type()?; + + // Register all pci devices type. + machine_register_pcidevops_type()?; + vfio_register_pcidevops_type()?; + virtio_register_pcidevops_type()?; + devices_register_pcidevops_type()?; + + Ok(()) +} diff --git a/machine/src/micro_common/mod.rs b/machine/src/micro_common/mod.rs index b97f046..7fb530e 100644 --- a/machine/src/micro_common/mod.rs +++ b/machine/src/micro_common/mod.rs @@ -27,7 +27,9 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` +#[cfg(not(target_arch = "riscv64"))] pub mod syscall; use std::fmt; @@ -38,20 +40,22 @@ use std::sync::{Arc, Mutex}; use std::vec::Vec; use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use log::{error, info}; #[cfg(target_arch = "aarch64")] use crate::aarch64::micro::{LayoutEntryType, MEM_LAYOUT}; +#[cfg(target_arch = "riscv64")] +use crate::riscv64::micro::{LayoutEntryType, MEM_LAYOUT}; #[cfg(target_arch = "x86_64")] use crate::x86_64::micro::{LayoutEntryType, MEM_LAYOUT}; use crate::{MachineBase, MachineError, MachineOps}; use cpu::CpuLifecycleState; use devices::sysbus::{IRQ_BASE, IRQ_MAX}; use machine_manager::config::{ - parse_blk, parse_incoming_uri, parse_net, BlkDevConfig, ConfigCheck, DiskFormat, MigrateMode, - NetworkInterfaceConfig, VmConfig, DEFAULT_VIRTQUEUE_SIZE, + get_chardev_socket_path, parse_incoming_uri, str_slip_to_clap, ConfigCheck, DriveConfig, + MigrateMode, NetDevcfg, NetworkInterfaceConfig, VmConfig, }; -use machine_manager::event; use machine_manager::event_loop::EventLoop; use machine_manager::machine::{ DeviceInterface, MachineAddressInterface, MachineExternalInterface, MachineInterface, @@ -60,9 +64,10 @@ use machine_manager::machine::{ use machine_manager::qmp::{ qmp_channel::QmpChannel, qmp_response::Response, qmp_schema, qmp_schema::UpdateRegionArgument, }; +use machine_manager::{check_arg_nonexist, event}; use migration::MigrationManager; -use util::aio::WriteZeroesState; use util::{loop_context::EventLoopManager, num_ops::str_to_num, set_termi_canon_mode}; +use virtio::device::block::VirtioBlkDevConfig; use virtio::{ create_tap, qmp_balloon, qmp_query_balloon, Block, BlockState, Net, VhostKern, VhostUser, VirtioDevice, VirtioMmioDevice, VirtioMmioState, VirtioNetState, @@ -78,8 +83,9 @@ const MMIO_REPLACEABLE_NET_NR: usize = 2; struct MmioReplaceableConfig { // Device id. id: String, - // The dev_config of the related backend device. - dev_config: Arc, + // The config of the related backend device. + // Eg: Drive config of virtio mmio block. Netdev config of virtio mmio net. + back_config: Arc, } // The device information of replaceable device. @@ -155,69 +161,64 @@ impl LightMachine { } pub(crate) fn create_replaceable_devices(&mut self) -> Result<()> { - let mut rpl_devs: Vec = Vec::new(); for id in 0..MMIO_REPLACEABLE_BLK_NR { let block = Arc::new(Mutex::new(Block::new( - BlkDevConfig::default(), + VirtioBlkDevConfig::default(), + DriveConfig::default(), self.get_drive_files(), ))); - let virtio_mmio = VirtioMmioDevice::new(&self.base.sys_mem, block.clone()); - rpl_devs.push(virtio_mmio); - MigrationManager::register_device_instance( BlockState::descriptor(), - block, + block.clone(), + &id.to_string(), + ); + + let blk_mmio = self.add_virtio_mmio_device(id.to_string(), block.clone())?; + let info = MmioReplaceableDevInfo { + device: block, + id: id.to_string(), + used: false, + }; + self.replaceable_info.devices.lock().unwrap().push(info); + MigrationManager::register_transport_instance( + VirtioMmioState::descriptor(), + blk_mmio, &id.to_string(), ); } for id in 0..MMIO_REPLACEABLE_NET_NR { - let net = Arc::new(Mutex::new(Net::new(NetworkInterfaceConfig::default()))); - let virtio_mmio = VirtioMmioDevice::new(&self.base.sys_mem, net.clone()); - rpl_devs.push(virtio_mmio); - + let total_id = id + MMIO_REPLACEABLE_BLK_NR; + let net = Arc::new(Mutex::new(Net::new( + NetworkInterfaceConfig::default(), + NetDevcfg::default(), + ))); MigrationManager::register_device_instance( VirtioNetState::descriptor(), - net, - &id.to_string(), + net.clone(), + &total_id.to_string(), ); - } - - let mut region_base = self.base.sysbus.min_free_base; - let region_size = MEM_LAYOUT[LayoutEntryType::Mmio as usize].1; - for (id, dev) in rpl_devs.into_iter().enumerate() { - self.replaceable_info - .devices - .lock() - .unwrap() - .push(MmioReplaceableDevInfo { - device: dev.device.clone(), - id: id.to_string(), - used: false, - }); + let net_mmio = self.add_virtio_mmio_device(total_id.to_string(), net.clone())?; + let info = MmioReplaceableDevInfo { + device: net, + id: total_id.to_string(), + used: false, + }; + self.replaceable_info.devices.lock().unwrap().push(info); MigrationManager::register_transport_instance( VirtioMmioState::descriptor(), - VirtioMmioDevice::realize( - dev, - &mut self.base.sysbus, - region_base, - MEM_LAYOUT[LayoutEntryType::Mmio as usize].1, - #[cfg(target_arch = "x86_64")] - &self.base.boot_source, - ) - .with_context(|| MachineError::RlzVirtioMmioErr)?, - &id.to_string(), + net_mmio, + &total_id.to_string(), ); - region_base += region_size; } - self.base.sysbus.min_free_base = region_base; + Ok(()) } pub(crate) fn fill_replaceable_device( &mut self, id: &str, - dev_config: Arc, + dev_config: Vec>, index: usize, ) -> Result<()> { let mut replaceable_devices = self.replaceable_info.devices.lock().unwrap(); @@ -232,14 +233,14 @@ impl LightMachine { .device .lock() .unwrap() - .update_config(Some(dev_config.clone())) + .update_config(dev_config.clone()) .with_context(|| MachineError::UpdCfgErr(id.to_string()))?; } - self.add_replaceable_config(id, dev_config) + self.add_replaceable_config(id, dev_config[0].clone()) } - fn add_replaceable_config(&self, id: &str, dev_config: Arc) -> Result<()> { + fn add_replaceable_config(&self, id: &str, back_config: Arc) -> Result<()> { let mut configs_lock = self.replaceable_info.configs.lock().unwrap(); let limit = MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR; if configs_lock.len() >= limit { @@ -254,7 +255,7 @@ impl LightMachine { let config = MmioReplaceableConfig { id: id.to_string(), - dev_config, + back_config, }; trace::mmio_replaceable_config(&config); @@ -262,21 +263,28 @@ impl LightMachine { Ok(()) } - fn add_replaceable_device(&self, id: &str, driver: &str, slot: usize) -> Result<()> { + fn add_replaceable_device( + &self, + args: Box, + slot: usize, + ) -> Result<()> { + let id = args.id; + let driver = args.driver; + // Find the configuration by id. let configs_lock = self.replaceable_info.configs.lock().unwrap(); - let mut dev_config = None; + let mut configs = Vec::new(); for config in configs_lock.iter() { if config.id == id { - dev_config = Some(config.dev_config.clone()); + configs.push(config.back_config.clone()); } } - if dev_config.is_none() { + if configs.is_empty() { bail!("Failed to find device configuration."); } // Sanity check for config, driver and slot. - let cfg_any = dev_config.as_ref().unwrap().as_any(); + let cfg_any = configs[0].as_any(); let index = if driver.contains("net") { if slot >= MMIO_REPLACEABLE_NET_NR { return Err(anyhow!(MachineError::RplDevLmtErr( @@ -284,9 +292,18 @@ impl LightMachine { MMIO_REPLACEABLE_NET_NR ))); } - if cfg_any.downcast_ref::().is_none() { + if cfg_any.downcast_ref::().is_none() { return Err(anyhow!(MachineError::DevTypeErr("net".to_string()))); } + let net_config = NetworkInterfaceConfig { + classtype: driver, + id: id.clone(), + netdev: args.chardev.with_context(|| "No chardev set")?, + mac: args.mac, + iothread: args.iothread, + ..Default::default() + }; + configs.push(Arc::new(net_config)); slot + MMIO_REPLACEABLE_BLK_NR } else if driver.contains("blk") { if slot >= MMIO_REPLACEABLE_BLK_NR { @@ -295,9 +312,19 @@ impl LightMachine { MMIO_REPLACEABLE_BLK_NR ))); } - if cfg_any.downcast_ref::().is_none() { + if cfg_any.downcast_ref::().is_none() { return Err(anyhow!(MachineError::DevTypeErr("blk".to_string()))); } + let dev_config = VirtioBlkDevConfig { + classtype: driver, + id: id.clone(), + drive: args.drive.with_context(|| "No drive set")?, + bootindex: args.boot_index, + iothread: args.iothread, + serial: args.serial_num, + ..Default::default() + }; + configs.push(Arc::new(dev_config)); slot } else { bail!("Unsupported replaceable device type."); @@ -316,7 +343,7 @@ impl LightMachine { .device .lock() .unwrap() - .update_config(dev_config) + .update_config(configs) .with_context(|| MachineError::UpdCfgErr(id.to_string()))?; } Ok(()) @@ -328,8 +355,10 @@ impl LightMachine { let mut configs_lock = self.replaceable_info.configs.lock().unwrap(); for (index, config) in configs_lock.iter().enumerate() { if config.id == id { - if let Some(blkconf) = config.dev_config.as_any().downcast_ref::() { - self.unregister_drive_file(&blkconf.path_on_host)?; + if let Some(drive_config) = + config.back_config.as_any().downcast_ref::() + { + self.unregister_drive_file(&drive_config.path_on_host)?; } configs_lock.remove(index); is_exist = true; @@ -347,7 +376,7 @@ impl LightMachine { .device .lock() .unwrap() - .update_config(None) + .update_config(Vec::new()) .with_context(|| MachineError::UpdCfgErr(id.to_string()))?; } } @@ -363,22 +392,42 @@ impl LightMachine { vm_config: &mut VmConfig, cfg_args: &str, ) -> Result<()> { - let device_cfg = parse_net(vm_config, cfg_args)?; - if device_cfg.vhost_type.is_some() { - let device = if device_cfg.vhost_type == Some(String::from("vhost-kernel")) { + let net_cfg = + NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + check_arg_nonexist!( + ("bus", net_cfg.bus), + ("addr", net_cfg.addr), + ("multifunction", net_cfg.multifunction) + ); + let netdev_cfg = vm_config + .netdevs + .remove(&net_cfg.netdev) + .with_context(|| format!("Netdev: {:?} not found for net device", &net_cfg.netdev))?; + if netdev_cfg.vhost_type().is_some() { + if netdev_cfg.vhost_type().unwrap() == "vhost-kernel" { let net = Arc::new(Mutex::new(VhostKern::Net::new( - &device_cfg, + &net_cfg, + netdev_cfg, &self.base.sys_mem, ))); - VirtioMmioDevice::new(&self.base.sys_mem, net) + self.add_virtio_mmio_device(net_cfg.id.clone(), net)?; } else { + let chardev = netdev_cfg.chardev.clone().with_context(|| { + format!("Chardev not configured for netdev {:?}", netdev_cfg.id) + })?; + let chardev_cfg = vm_config + .chardev + .remove(&chardev) + .with_context(|| format!("Chardev: {:?} not found for netdev", chardev))?; + let sock_path = get_chardev_socket_path(chardev_cfg)?; let net = Arc::new(Mutex::new(VhostUser::Net::new( - &device_cfg, + &net_cfg, + netdev_cfg, + sock_path, &self.base.sys_mem, ))); - VirtioMmioDevice::new(&self.base.sys_mem, net) + self.add_virtio_mmio_device(net_cfg.id.clone(), net)?; }; - self.realize_virtio_mmio_device(device)?; } else { let index = MMIO_REPLACEABLE_BLK_NR + self.replaceable_info.net_count; if index >= MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR { @@ -387,7 +436,9 @@ impl LightMachine { MMIO_REPLACEABLE_NET_NR ); } - self.fill_replaceable_device(&device_cfg.id, Arc::new(device_cfg.clone()), index)?; + let configs: Vec> = + vec![Arc::new(netdev_cfg), Arc::new(net_cfg.clone())]; + self.fill_replaceable_device(&net_cfg.id, configs, index)?; self.replaceable_info.net_count += 1; } Ok(()) @@ -398,7 +449,17 @@ impl LightMachine { vm_config: &mut VmConfig, cfg_args: &str, ) -> Result<()> { - let device_cfg = parse_blk(vm_config, cfg_args, None)?; + let device_cfg = + VirtioBlkDevConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + check_arg_nonexist!( + ("bus", device_cfg.bus), + ("addr", device_cfg.addr), + ("multifunction", device_cfg.multifunction) + ); + let drive_cfg = vm_config + .drives + .remove(&device_cfg.drive) + .with_context(|| "No drive configured matched for blk device")?; if self.replaceable_info.block_count >= MMIO_REPLACEABLE_BLK_NR { bail!( "A maximum of {} block replaceable devices are supported.", @@ -406,15 +467,21 @@ impl LightMachine { ); } let index = self.replaceable_info.block_count; - self.fill_replaceable_device(&device_cfg.id, Arc::new(device_cfg.clone()), index)?; + let configs: Vec> = + vec![Arc::new(drive_cfg), Arc::new(device_cfg.clone())]; + self.fill_replaceable_device(&device_cfg.id, configs, index)?; self.replaceable_info.block_count += 1; Ok(()) } - pub(crate) fn realize_virtio_mmio_device( + pub(crate) fn add_virtio_mmio_device( &mut self, - dev: VirtioMmioDevice, + name: String, + device: Arc>, ) -> Result>> { + let sys_mem = self.get_sys_mem().clone(); + let dev = VirtioMmioDevice::new(&sys_mem, name, device); + let region_base = self.base.sysbus.min_free_base; let region_size = MEM_LAYOUT[LayoutEntryType::Mmio as usize].1; let realized_virtio_mmio_device = VirtioMmioDevice::realize( @@ -461,7 +528,7 @@ impl MachineLifecycle for LightMachine { } info!("vm destroy"); - EventLoop::get_ctx(None).unwrap().kick(); + EventLoop::kick_all(); true } @@ -583,6 +650,8 @@ impl DeviceInterface for LightMachine { let cpu_type = String::from("host-x86-cpu"); #[cfg(target_arch = "aarch64")] let cpu_type = String::from("host-aarch64-cpu"); + #[cfg(target_arch = "riscv64")] + let cpu_type = String::from("host-riscv64-cpu"); for cpu_index in 0..self.base.cpu_topo.max_cpus { if self.base.cpu_topo.get_mask(cpu_index as usize) == 0 { @@ -668,7 +737,7 @@ impl DeviceInterface for LightMachine { fn device_add(&mut self, args: Box) -> Response { // get slot of bus by addr or lun let mut slot = 0; - if let Some(addr) = args.addr { + if let Some(addr) = args.addr.clone() { if let Ok(num) = str_to_num::(&addr) { slot = num; } else { @@ -684,7 +753,7 @@ impl DeviceInterface for LightMachine { slot = lun + 1; } - match self.add_replaceable_device(&args.id, &args.driver, slot) { + match self.add_replaceable_device(args.clone(), slot) { Ok(()) => Response::create_empty_response(), Err(ref e) => { error!("{:?}", e); @@ -719,32 +788,22 @@ impl DeviceInterface for LightMachine { } fn blockdev_add(&self, args: Box) -> Response { - let read_only = args.read_only.unwrap_or(false); + let readonly = args.read_only.unwrap_or(false); let mut direct = true; if args.cache.is_some() && !args.cache.unwrap().direct.unwrap_or(true) { direct = false; } - let config = BlkDevConfig { + let config = DriveConfig { id: args.node_name.clone(), + drive_type: "none".to_string(), path_on_host: args.file.filename.clone(), - read_only, + readonly, direct, - serial_num: None, - iothread: None, - iops: None, - queues: 1, - boot_index: None, - chardev: None, - socket_path: None, aio: args.file.aio, - queue_size: DEFAULT_VIRTQUEUE_SIZE, - discard: false, - write_zeroes: WriteZeroesState::Off, - format: DiskFormat::Raw, - l2_cache_size: None, - refcount_cache_size: None, + ..Default::default() }; + if let Err(e) = config.check() { error!("{:?}", e); return Response::create_error_response( @@ -753,7 +812,7 @@ impl DeviceInterface for LightMachine { ); } // Register drive backend file for hotplugged drive. - if let Err(e) = self.register_drive_file(&config.id, &args.file.filename, read_only, direct) + if let Err(e) = self.register_drive_file(&config.id, &args.file.filename, readonly, direct) { error!("{:?}", e); return Response::create_error_response( @@ -783,18 +842,9 @@ impl DeviceInterface for LightMachine { } fn netdev_add(&mut self, args: Box) -> Response { - let mut config = NetworkInterfaceConfig { + let mut netdev_cfg = NetDevcfg { id: args.id.clone(), - host_dev_name: "".to_string(), - mac: None, - tap_fds: None, - vhost_type: None, - vhost_fds: None, - iothread: None, - queues: 2, - mq: false, - socket_path: None, - queue_size: DEFAULT_VIRTQUEUE_SIZE, + ..Default::default() }; if let Some(fds) = args.fds { @@ -806,7 +856,7 @@ impl DeviceInterface for LightMachine { }; if let Some(fd_num) = QmpChannel::get_fd(&netdev_fd) { - config.tap_fds = Some(vec![fd_num]); + netdev_cfg.tap_fds = Some(vec![fd_num]); } else { // try to convert string to RawFd let fd_num = match netdev_fd.parse::() { @@ -824,10 +874,10 @@ impl DeviceInterface for LightMachine { ); } }; - config.tap_fds = Some(vec![fd_num]); + netdev_cfg.tap_fds = Some(vec![fd_num]); } } else if let Some(if_name) = args.if_name { - config.host_dev_name = if_name.clone(); + netdev_cfg.ifname = if_name.clone(); if create_tap(None, Some(&if_name), 1).is_err() { return Response::create_error_response( qmp_schema::QmpErrorClass::GenericError( @@ -838,7 +888,7 @@ impl DeviceInterface for LightMachine { } } - match self.add_replaceable_config(&args.id, Arc::new(config)) { + match self.add_replaceable_config(&args.id, Arc::new(netdev_cfg)) { Ok(()) => Response::create_empty_response(), Err(ref e) => { error!("{:?}", e); diff --git a/machine/src/micro_common/syscall.rs b/machine/src/micro_common/syscall.rs index ca8327f..f3acec1 100644 --- a/machine/src/micro_common/syscall.rs +++ b/machine/src/micro_common/syscall.rs @@ -159,14 +159,7 @@ fn ioctl_allow_list() -> BpfRule { .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_API_VERSION() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_MP_STATE() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_MP_STATE() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_VCPU_EVENTS() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_VCPU_EVENTS() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_USER_MEMORY_REGION); - #[cfg(target_arch = "x86_64")] - let bpf_rule = bpf_rule - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_PIT2() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_CLOCK() as u32) - .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_IRQCHIP() as u32); + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_VCPU_EVENTS() as u32); arch_ioctl_allow_list(bpf_rule) } diff --git a/machine/src/riscv64/fdt.rs b/machine/src/riscv64/fdt.rs new file mode 100644 index 0000000..05d00a3 --- /dev/null +++ b/machine/src/riscv64/fdt.rs @@ -0,0 +1,217 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use anyhow::Result; + +use crate::MachineBase; +use devices::sysbus::{SysBusDevType, SysRes}; +use util::device_tree::{self, FdtBuilder}; + +/// Function that helps to generate arm pmu in device-tree. +/// +/// # Arguments +/// +/// * `fdt` - Flatted device-tree blob where node will be filled into. + +/// Function that helps to generate serial node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of serial device. +/// * `fdt` - Flatted device-tree blob where serial node will be filled into. +fn generate_serial_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { + let node = format!("serial@{:x}", res.region_base); + let serial_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("compatible", "ns16550a")?; + fdt.set_property_array_u64("reg", &[res.region_base, res.region_size])?; + fdt.set_property_u32("clock-frequency", 3686400)?; + fdt.set_property_u32("interrupt-parent", device_tree::AIA_APLIC_PHANDLE)?; + let mut cells: Vec = Vec::new(); + cells.push(res.irq as u32); + cells.push(0x4); + fdt.set_property_array_u32("interrupts", &cells)?; + fdt.end_node(serial_node_dep) +} + +/// Function that helps to generate RTC node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of RTC device. +/// * `fdt` - Flatted device-tree blob where RTC node will be filled into. +/// Function that helps to generate Virtio-Mmio device's node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of Virtio-Mmio device. +/// * `fdt` - Flatted device-tree blob where node will be filled into. +fn generate_virtio_devices_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { + let node = format!("virtio_mmio@{:x}", res.region_base); + let virtio_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("compatible", "virtio,mmio")?; + fdt.set_property_u32("interrupt-parent", device_tree::AIA_APLIC_PHANDLE)?; + fdt.set_property_array_u64("reg", &[res.region_base, res.region_size])?; + let mut cells: Vec = Vec::new(); + cells.push(res.irq as u32); + cells.push(0x4); + fdt.set_property_array_u32("interrupts", &cells)?; + fdt.end_node(virtio_node_dep) +} + +/// Function that helps to generate fw-cfg node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of fw-cfg device. +/// * `fdt` - Flatted device-tree blob where fw-cfg node will be filled into. +// fn generate_fwcfg_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { +// TODO +// } + +/// Function that helps to generate flash node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of fw-cfg device. +/// * `flash` - Flatted device-tree blob where fw-cfg node will be filled into. +// fn generate_flash_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { +// TODO +// } + +/// Trait that helps to generate all nodes in device-tree. +#[allow(clippy::upper_case_acronyms)] +trait CompileFDTHelper { + /// Function that helps to generate cpu nodes. + fn generate_cpu_nodes(&self, fdt: &mut FdtBuilder) -> Result<()>; + /// Function that helps to generate Virtio-mmio devices' nodes. + fn generate_devices_node(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +impl CompileFDTHelper for MachineBase { + fn generate_cpu_nodes(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "cpus"; + + let cpus = &self.cpus; + let cpus_node_dep = fdt.begin_node(node)?; + fdt.set_property_u32("#address-cells", 0x01)?; + fdt.set_property_u32("#size-cells", 0x0)?; + let frequency = cpus[0].arch().lock().unwrap().timer_regs().frequency; + fdt.set_property_u32("timebase-frequency", frequency as u32)?; + + let nr_vcpus = cpus.len(); + for cpu_index in 0..nr_vcpus { + let node = format!("cpu@{:x}", cpu_index); + let cpu_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", cpu_index as u32 + device_tree::PHANDLE_CPU)?; + fdt.set_property_string("device_type", "cpu")?; + fdt.set_property_string("compatible", "riscv")?; + + let xlen = self.cpus[cpu_index] + .arch() + .lock() + .unwrap() + .get_xlen() + .to_string(); + let mut isa = format!("rv{}", xlen); + let valid_isa_order = String::from("IEMAFDQCLBJTPVNSUHKORWXYZG"); + for char in valid_isa_order.chars() { + let index = char as u32 - 'A' as u32; + let cpu_isa = self.cpus[cpu_index] + .arch() + .lock() + .unwrap() + .config_regs() + .isa; + if (cpu_isa & (1 << index) as u64) > 0 { + let tmp = char::from('a' as u8 + index as u8); + isa = format!("{}{}", isa, tmp); + } + } + isa = format!("{}_{}", isa, "ssaia_smaia"); + + fdt.set_property_string("riscv,isa", &isa)?; + + fdt.set_property_u32("reg", cpu_index as u32)?; + + let node = "interrupt-controller"; + let interrupt_controller = fdt.begin_node(node)?; + fdt.set_property_u32("#interrupt-cells", 1)?; + fdt.set_property_array_u32("interrupt-controller", &Vec::new())?; + fdt.set_property_string("compatible", "riscv,cpu-intc")?; + fdt.set_property_u32( + "phandle", + cpu_index as u32 + device_tree::INTC_PHANDLE_START, + )?; + fdt.end_node(interrupt_controller)?; + + fdt.end_node(cpu_node_dep)?; + } + + let cpu_map_node_dep = fdt.begin_node("cpu-map")?; + for cluster in 0..self.cpu_topo.clusters { + let cluster_name = format!("cluster{}", cluster); + let cluster_node_dep = fdt.begin_node(&cluster_name)?; + for core in 0..self.cpu_topo.cores { + let core_name = format!("core{}", core); + let core_node_dep = fdt.begin_node(&core_name)?; + let vcpuid = self.cpu_topo.cores * cluster + core; + fdt.set_property_u32("cpu", u32::from(vcpuid) + device_tree::PHANDLE_CPU)?; + fdt.end_node(core_node_dep)?; + } + fdt.end_node(cluster_node_dep)?; + } + fdt.end_node(cpu_map_node_dep)?; + + fdt.end_node(cpus_node_dep)?; + + Ok(()) + } + + fn generate_devices_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "soc"; + let smb_node_dep = fdt.begin_node(node)?; + fdt.set_property_string("compatible", "simple-bus")?; + fdt.set_property_u32("#address-cells", 0x02)?; + fdt.set_property_u32("#size-cells", 0x2)?; + fdt.set_property("ranges", &Vec::new())?; + + for dev in self.sysbus.devices.iter() { + let locked_dev = dev.lock().unwrap(); + match locked_dev.sysbusdev_base().dev_type { + SysBusDevType::Serial => { + generate_serial_device_node(fdt, &locked_dev.sysbusdev_base().res)? + } + SysBusDevType::VirtioMmio => { + generate_virtio_devices_node(fdt, &locked_dev.sysbusdev_base().res)? + } + _ => (), + } + } + + use util::device_tree::CompileFDT; + self.irq_chip.as_ref().unwrap().generate_fdt_node(fdt)?; + + fdt.end_node(smb_node_dep)?; + Ok(()) + } +} + +impl device_tree::CompileFDT for MachineBase { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + fdt.set_property_string("compatible", "linux,dummy-virt")?; + fdt.set_property_u32("#address-cells", 0x2)?; + fdt.set_property_u32("#size-cells", 0x2)?; + + self.generate_cpu_nodes(fdt)?; + self.generate_devices_node(fdt) + } +} diff --git a/machine/src/riscv64/micro.rs b/machine/src/riscv64/micro.rs new file mode 100644 index 0000000..d0d059b --- /dev/null +++ b/machine/src/riscv64/micro.rs @@ -0,0 +1,278 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::sync::{Arc, Mutex}; + +use anyhow::{bail, Context, Result}; +use devices::legacy::Serial; +use log::info; + +use crate::{LightMachine, MachineOps}; +use crate::{MachineBase, MachineError}; +use address_space::{AddressSpace, GuestAddress, Region}; +use cpu::CPUTopology; +// use devices::{legacy::PL031, ICGICConfig, ICGICv2Config, ICGICv3Config, GIC_IRQ_MAX}; +use devices::AIAConfig as InterruptControllerConfig; +use hypervisor::kvm::riscv64::*; +use machine_manager::config::{SerialConfig, VmConfig}; +use migration::{MigrationManager, MigrationStatus}; +use util::{ + device_tree::{self, CompileFDT, FdtBuilder}, + seccomp::{BpfRule, SeccompCmpOpt}, +}; +use virtio::{VirtioDevice, VirtioMmioDevice}; + +/// The type of memory layout entry on riscv64 +#[repr(usize)] +pub enum LayoutEntryType { + AIA, + Uart, + Mmio, + Mem, +} +/// Layout of riscv64 +pub const MEM_LAYOUT: &[(u64, u64)] = &[ + (0x0c00_0000, 0x0400_0000), // AIA + (0x1000_0000, 0x0000_1000), // Uart + (0x1000_1000, 0x0000_1000), // Mmio + (0x8000_0000, 0x80_0000_0000), // Mem +]; + +impl MachineOps for LightMachine { + fn machine_base(&self) -> &MachineBase { + &self.base + } + + fn machine_base_mut(&mut self) -> &mut MachineBase { + &mut self.base + } + + fn init_machine_ram(&self, sys_mem: &Arc, mem_size: u64) -> Result<()> { + let vm_ram = self.get_vm_ram(); + let layout_size = MEM_LAYOUT[LayoutEntryType::Mem as usize].1; + let ram = Region::init_alias_region( + vm_ram.clone(), + 0, + std::cmp::min(layout_size, mem_size), + "pc_ram", + ); + sys_mem + .root() + .add_subregion(ram, MEM_LAYOUT[LayoutEntryType::Mem as usize].0) + } + + fn init_interrupt_controller(&mut self, vcpu_count: u64) -> Result<()> { + let aia_conf = InterruptControllerConfig { + vcpu_count: vcpu_count as u32, + max_irq: 33, + region_range: ( + MEM_LAYOUT[LayoutEntryType::AIA as usize].0, + MEM_LAYOUT[LayoutEntryType::AIA as usize].1, + ), + }; + + let hypervisor = self.get_hypervisor(); + let mut locked_hypervisor = hypervisor.lock().unwrap(); + self.base.irq_chip = Some(locked_hypervisor.create_interrupt_controller(&aia_conf)?); + self.base.irq_chip.as_ref().unwrap().realize()?; + + let irq_manager = locked_hypervisor.create_irq_manager()?; + self.base.sysbus.irq_manager = irq_manager.line_irq_manager; + Ok(()) + } + + // fn add_rtc_device(&mut self) -> Result<()> { + // TODO + // } + + fn add_serial_device(&mut self, config: &SerialConfig) -> Result<()> { + let region_base: u64 = MEM_LAYOUT[LayoutEntryType::Uart as usize].0; + let region_size: u64 = MEM_LAYOUT[LayoutEntryType::Uart as usize].1; + + let serial = Serial::new(config.clone()); + serial + .realize( + &mut self.base.sysbus, + region_base, + region_size, + &self.base.boot_source, + ) + .with_context(|| "Failed to realize Serial") + } + + fn realize(vm: &Arc>, vm_config: &mut VmConfig) -> Result<()> { + let mut locked_vm = vm.lock().unwrap(); + + trace::sysbus(&locked_vm.base.sysbus); + trace::vm_state(&locked_vm.base.vm_state); + + let topology = CPUTopology::new().set_topology(( + vm_config.machine_config.nr_threads, + vm_config.machine_config.nr_cores, + vm_config.machine_config.nr_dies, + )); + trace::cpu_topo(&topology); + locked_vm.base.numa_nodes = locked_vm.add_numa_nodes(vm_config)?; + let locked_hypervisor = locked_vm.base.hypervisor.lock().unwrap(); + locked_hypervisor.init_machine(&locked_vm.base.sys_mem)?; + drop(locked_hypervisor); + locked_vm.init_memory( + &vm_config.machine_config.mem_config, + &locked_vm.base.sys_mem, + vm_config.machine_config.nr_cpus, + )?; + + let boot_config = + locked_vm.load_boot_source(None, MEM_LAYOUT[LayoutEntryType::Mem as usize].0)?; + #[cfg(target_arch = "aarch64")] + let cpu_config = locked_vm.load_cpu_features(vm_config)?; + + let hypervisor = locked_vm.base.hypervisor.clone(); + // vCPUs init,and apply CPU features (for aarch64) + locked_vm.base.cpus.extend(::init_vcpu( + vm.clone(), + hypervisor, + vm_config.machine_config.nr_cpus, + &topology, + &boot_config, + )?); + + locked_vm.init_interrupt_controller(u64::from(vm_config.machine_config.nr_cpus))?; + + #[cfg(target_arch = "aarch64")] + locked_vm.cpu_post_init(&cpu_config)?; + + // Add mmio devices + locked_vm + .create_replaceable_devices() + .with_context(|| "Failed to create replaceable devices.")?; + locked_vm.add_devices(vm_config)?; + trace::replaceable_info(&locked_vm.replaceable_info); + + let mut fdt_helper = FdtBuilder::new(); + locked_vm + .generate_fdt_node(&mut fdt_helper) + .with_context(|| MachineError::GenFdtErr)?; + let fdt_vec = fdt_helper.finish()?; + + locked_vm + .base + .sys_mem + .write( + &mut fdt_vec.as_slice(), + GuestAddress(boot_config.fdt_addr), + fdt_vec.len() as u64, + ) + .with_context(|| MachineError::WrtFdtErr(boot_config.fdt_addr, fdt_vec.len()))?; + + MigrationManager::register_vm_instance(vm.clone()); + MigrationManager::register_migration_instance(locked_vm.base.migration_hypervisor.clone()); + if let Err(e) = MigrationManager::set_status(MigrationStatus::Setup) { + bail!("Failed to set migration status {}", e); + } + + Ok(()) + } + + fn add_virtio_mmio_net(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { + self.add_virtio_mmio_net(vm_config, cfg_args) + } + + fn add_virtio_mmio_block(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { + self.add_virtio_mmio_block(vm_config, cfg_args) + } + + fn add_virtio_mmio_device( + &mut self, + name: String, + device: Arc>, + ) -> Result>> { + self.add_virtio_mmio_device(name, device) + } + + #[cfg(not(target_arch = "riscv64"))] + fn syscall_whitelist(&self) -> Vec { + syscall_whitelist() + } +} + +pub(crate) fn arch_ioctl_allow_list(bpf_rule: BpfRule) -> BpfRule { + bpf_rule + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_ONE_REG() as u32) + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_DEVICE_ATTR() as u32) + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_REG_LIST() as u32) +} + +pub(crate) fn arch_syscall_whitelist() -> Vec { + vec![ + BpfRule::new(libc::SYS_epoll_pwait), + BpfRule::new(libc::SYS_newfstatat), + BpfRule::new(libc::SYS_unlinkat), + BpfRule::new(libc::SYS_mkdirat), + ] +} + +/// Trait that helps to generate all nodes in device-tree. +#[allow(clippy::upper_case_acronyms)] +trait CompileFDTHelper { + /// Function that helps to generate memory nodes. + fn generate_memory_node(&self, fdt: &mut FdtBuilder) -> Result<()>; + /// Function that helps to generate the chosen node. + fn generate_chosen_node(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +impl CompileFDTHelper for LightMachine { + fn generate_memory_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let mem_base = MEM_LAYOUT[LayoutEntryType::Mem as usize].0; + let mem_size = self.base.sys_mem.memory_end_address().raw_value() + - MEM_LAYOUT[LayoutEntryType::Mem as usize].0; + let node = format!("memory@{:x}", mem_base); + let memory_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("device_type", "memory")?; + fdt.set_property_array_u64("reg", &[mem_base, mem_size])?; + fdt.end_node(memory_node_dep) + } + + fn generate_chosen_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "chosen"; + let boot_source = self.base.boot_source.lock().unwrap(); + + let chosen_node_dep = fdt.begin_node(node)?; + let cmdline = &boot_source.kernel_cmdline.to_string(); + fdt.set_property_string("bootargs", cmdline.as_str())?; + + let serial_property_string = format!( + "/soc/serial@{:x}", + MEM_LAYOUT[LayoutEntryType::Uart as usize].0 + ); + fdt.set_property_string("stdout-path", &serial_property_string)?; + + match &boot_source.initrd { + Some(initrd) => { + fdt.set_property_u64("linux,initrd-start", initrd.initrd_addr)?; + fdt.set_property_u64("linux,initrd-end", initrd.initrd_addr + initrd.initrd_size)?; + } + None => {} + } + fdt.end_node(chosen_node_dep) + } +} + +impl device_tree::CompileFDT for LightMachine { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node_dep = fdt.begin_node("")?; + self.base.generate_fdt_node(fdt)?; + self.generate_memory_node(fdt)?; + self.generate_chosen_node(fdt)?; + fdt.end_node(node_dep) + } +} diff --git a/machine/src/riscv64/mod.rs b/machine/src/riscv64/mod.rs new file mode 100644 index 0000000..2f40818 --- /dev/null +++ b/machine/src/riscv64/mod.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2024 Institute of Software, CAS. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +mod fdt; +pub mod micro; diff --git a/machine/src/standard_common/mod.rs b/machine/src/standard_common/mod.rs index 1ab0be0..f18350a 100644 --- a/machine/src/standard_common/mod.rs +++ b/machine/src/standard_common/mod.rs @@ -79,7 +79,9 @@ use ui::input::{input_button, input_move_abs, input_point_sync, key_event, Axis} use ui::vnc::qmp_query_vnc; use util::aio::{AioEngine, WriteZeroesState}; use util::byte_code::ByteCode; -use util::loop_context::{read_fd, EventNotifier, NotifierCallback, NotifierOperation}; +use util::loop_context::{ + create_new_eventfd, read_fd, EventNotifier, NotifierCallback, NotifierOperation, +}; use virtio::{qmp_balloon, qmp_query_balloon}; const MAX_REGION_SIZE: u64 = 65536; @@ -341,7 +343,7 @@ pub(crate) trait StdMachineOps: AcpiBuilder + MachineOps { let shutdown_req_fd = shutdown_req.as_raw_fd(); let shutdown_req_handler: Rc = Rc::new(move |_, _| { let _ret = shutdown_req.read(); - if StdMachine::handle_destroy_request(&clone_vm).is_ok() { + if StdMachine::handle_destroy_request(&clone_vm) { Some(gen_delete_notifiers(&[shutdown_req_fd])) } else { None @@ -882,11 +884,11 @@ impl StdMachine { power_button: Arc, shutdown_req: Arc, ) { - let emu_pid = vm_config.windows_emu_pid.as_ref(); + let emu_pid = vm_config.emulator_pid.as_ref(); if emu_pid.is_none() { return; } - log::info!("Watching on windows emu lifetime"); + log::info!("Watching on emulator lifetime"); crate::check_windows_emu_pid( "/proc/".to_owned() + emu_pid.unwrap(), power_button, @@ -1272,7 +1274,7 @@ impl DeviceInterface for StdMachine { if let Err(e) = self.register_drive_file( &config.id, &args.file.filename, - config.read_only, + config.readonly, config.direct, ) { error!("{:?}", e); @@ -1549,7 +1551,7 @@ impl DeviceInterface for StdMachine { region = Region::init_io_region(args.size, dummy_dev_ops, "UpdateRegionTest"); if args.ioeventfd.is_some() && args.ioeventfd.unwrap() { let ioeventfds = vec![RegionIoEventFd { - fd: Arc::new(EventFd::new(libc::EFD_NONBLOCK).unwrap()), + fd: Arc::new(create_new_eventfd().unwrap()), addr_range: AddressRange::from(( 0, args.ioeventfd_size.unwrap_or_default(), @@ -1672,12 +1674,7 @@ impl DeviceInterface for StdMachine { None, ); } - let drive_cfg = match self - .get_vm_config() - .lock() - .unwrap() - .add_block_drive(cmd_args[2]) - { + let drive_cfg = match self.get_vm_config().lock().unwrap().add_drive(cmd_args[2]) { Ok(cfg) => cfg, Err(ref e) => { return Response::create_error_response( @@ -1689,7 +1686,7 @@ impl DeviceInterface for StdMachine { if let Err(e) = self.register_drive_file( &drive_cfg.id, &drive_cfg.path_on_host, - drive_cfg.read_only, + drive_cfg.readonly, drive_cfg.direct, ) { error!("{:?}", e); @@ -1915,8 +1912,10 @@ impl DeviceInterface for StdMachine { fn parse_blockdev(args: &BlockDevAddArgument) -> Result { let mut config = DriveConfig { id: args.node_name.clone(), + drive_type: "none".to_string(), + unit: None, path_on_host: args.file.filename.clone(), - read_only: args.read_only.unwrap_or(false), + readonly: args.read_only.unwrap_or(false), direct: true, iops: args.iops, aio: args.file.aio, diff --git a/machine/src/standard_common/syscall.rs b/machine/src/standard_common/syscall.rs index 0cfac2e..d665f31 100644 --- a/machine/src/standard_common/syscall.rs +++ b/machine/src/standard_common/syscall.rs @@ -104,6 +104,7 @@ pub fn syscall_whitelist() -> Vec { BpfRule::new(libc::SYS_accept4), BpfRule::new(libc::SYS_lseek), futex_rule(), + BpfRule::new(libc::SYS_clone), BpfRule::new(libc::SYS_exit), BpfRule::new(libc::SYS_exit_group), BpfRule::new(libc::SYS_rt_sigreturn), diff --git a/machine/src/x86_64/micro.rs b/machine/src/x86_64/micro.rs index 77ea440..e5d17ce 100644 --- a/machine/src/x86_64/micro.rs +++ b/machine/src/x86_64/micro.rs @@ -22,10 +22,10 @@ use cpu::{CPUBootConfig, CPUTopology}; use devices::legacy::FwCfgOps; use hypervisor::kvm::x86_64::*; use hypervisor::kvm::*; -use machine_manager::config::{MigrateMode, SerialConfig, VmConfig}; +use machine_manager::config::{SerialConfig, VmConfig}; use migration::{MigrationManager, MigrationStatus}; use util::seccomp::{BpfRule, SeccompCmpOpt}; -use virtio::VirtioMmioDevice; +use virtio::{VirtioDevice, VirtioMmioDevice}; #[repr(usize)] pub enum LayoutEntryType { @@ -174,12 +174,7 @@ impl MachineOps for LightMachine { locked_vm.add_devices(vm_config)?; trace::replaceable_info(&locked_vm.replaceable_info); - let migrate_info = locked_vm.get_migrate_info(); - let boot_config = if migrate_info.0 == MigrateMode::Unknown { - Some(locked_vm.load_boot_source(None)?) - } else { - None - }; + let boot_config = locked_vm.load_boot_source(None)?; let hypervisor = locked_vm.base.hypervisor.clone(); locked_vm.base.cpus.extend(::init_vcpu( vm.clone(), @@ -209,11 +204,12 @@ impl MachineOps for LightMachine { self.add_virtio_mmio_block(vm_config, cfg_args) } - fn realize_virtio_mmio_device( + fn add_virtio_mmio_device( &mut self, - dev: VirtioMmioDevice, + name: String, + device: Arc>, ) -> Result>> { - self.realize_virtio_mmio_device(dev) + self.add_virtio_mmio_device(name, device) } fn syscall_whitelist(&self) -> Vec { @@ -243,6 +239,7 @@ pub(crate) fn arch_ioctl_allow_list(bpf_rule: BpfRule) -> BpfRule { .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_LAPIC() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_MSRS() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_MSRS() as u32) + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_VCPU_EVENTS() as u32) .add_constraint(SeccompCmpOpt::Eq, 1, KVM_SET_CPUID2() as u32) } diff --git a/machine/src/x86_64/mod.rs b/machine/src/x86_64/mod.rs index 47b4ecb..b3227f9 100644 --- a/machine/src/x86_64/mod.rs +++ b/machine/src/x86_64/mod.rs @@ -11,7 +11,6 @@ // See the Mulan PSL v2 for more details. pub mod ich9_lpc; +pub mod mch; pub mod micro; pub mod standard; - -mod mch; diff --git a/machine/src/x86_64/standard.rs b/machine/src/x86_64/standard.rs index 3aac836..8dca2db 100644 --- a/machine/src/x86_64/standard.rs +++ b/machine/src/x86_64/standard.rs @@ -45,9 +45,10 @@ use hypervisor::kvm::*; #[cfg(feature = "gtk")] use machine_manager::config::UiContext; use machine_manager::config::{ - parse_incoming_uri, BootIndexInfo, MigrateMode, NumaNode, PFlashConfig, SerialConfig, VmConfig, + parse_incoming_uri, BootIndexInfo, DriveConfig, MigrateMode, NumaNode, SerialConfig, VmConfig, }; use machine_manager::event; +use machine_manager::event_loop::EventLoop; use machine_manager::machine::{ MachineExternalInterface, MachineInterface, MachineLifecycle, MachineTestInterface, MigrateInterface, VmState, @@ -60,7 +61,10 @@ use ui::gtk::gtk_display_init; use ui::vnc::vnc_init; use util::seccomp::SeccompCmpOpt; use util::{ - byte_code::ByteCode, loop_context::EventLoopManager, seccomp::BpfRule, set_termi_canon_mode, + byte_code::ByteCode, + loop_context::{create_new_eventfd, EventLoopManager}, + seccomp::BpfRule, + set_termi_canon_mode, }; pub(crate) const VENDOR_ID_INTEL: u16 = 0x8086; @@ -155,20 +159,20 @@ impl StdMachine { IRQ_MAP[IrqEntryType::Pcie as usize].0, ))), reset_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("reset request".to_string()))?, ), shutdown_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK).with_context(|| { + create_new_eventfd().with_context(|| { MachineError::InitEventFdErr("shutdown request".to_string()) })?, ), power_button: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("power button".to_string()))?, ), cpu_resize_req: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("cpu resize".to_string()))?, ), boot_order_list: Arc::new(Mutex::new(Vec::new())), @@ -206,7 +210,7 @@ impl StdMachine { Ok(()) } - pub fn handle_destroy_request(vm: &Arc>) -> Result<()> { + pub fn handle_destroy_request(vm: &Arc>) -> bool { let locked_vm = vm.lock().unwrap(); let vmstate = { let state = locked_vm.base.vm_state.deref().0.lock().unwrap(); @@ -218,11 +222,13 @@ impl StdMachine { if locked_vm.shutdown_req.write(1).is_err() { error!("Failed to send shutdown request.") } + return false; } + EventLoop::kick_all(); info!("vm destroy"); - Ok(()) + true } fn init_ich9_lpc(&self, vm: Arc>) -> Result<()> { @@ -262,7 +268,7 @@ impl StdMachine { let region_size: u64 = MEM_LAYOUT[LayoutEntryType::CpuController as usize].1; let cpu_config = CpuConfig::new(boot_config, cpu_topology); let hotplug_cpu_req = Arc::new( - EventFd::new(libc::EFD_NONBLOCK) + create_new_eventfd() .with_context(|| MachineError::InitEventFdErr("hotplug cpu".to_string()))?, ); @@ -371,7 +377,7 @@ impl StdMachineOps for StdMachine { hypervisor, self.base.cpu_topo.max_cpus, )?; - vcpu.realize(&Some(boot_cfg), topology).with_context(|| { + vcpu.realize(boot_cfg, topology).with_context(|| { format!( "Failed to realize arch cpu register/features for CPU {}", vcpu_id @@ -559,12 +565,7 @@ impl MachineOps for StdMachine { locked_vm.add_devices(vm_config)?; let fwcfg = locked_vm.add_fwcfg_device(nr_cpus, max_cpus)?; - let migrate = locked_vm.get_migrate_info(); - let boot_config = if migrate.0 == MigrateMode::Unknown { - Some(locked_vm.load_boot_source(fwcfg.as_ref())?) - } else { - None - }; + let boot_config = locked_vm.load_boot_source(fwcfg.as_ref())?; let topology = CPUTopology::new().set_topology(( vm_config.machine_config.nr_threads, vm_config.machine_config.nr_cores, @@ -580,9 +581,7 @@ impl MachineOps for StdMachine { &boot_config, )?); - if migrate.0 == MigrateMode::Unknown { - locked_vm.init_cpu_controller(boot_config.unwrap(), topology, vm.clone())?; - } + locked_vm.init_cpu_controller(boot_config, topology, vm.clone())?; if let Some(fw_cfg) = fwcfg { locked_vm @@ -635,9 +634,9 @@ impl MachineOps for StdMachine { Ok(()) } - fn add_pflash_device(&mut self, configs: &[PFlashConfig]) -> Result<()> { + fn add_pflash_device(&mut self, configs: &[DriveConfig]) -> Result<()> { let mut configs_vec = configs.to_vec(); - configs_vec.sort_by_key(|c| c.unit); + configs_vec.sort_by_key(|c| c.unit.unwrap()); // The two PFlash devices locates below 4GB, this variable represents the end address // of current PFlash device. let mut flash_end: u64 = MEM_LAYOUT[LayoutEntryType::MemAbove4g as usize].0; @@ -645,7 +644,7 @@ impl MachineOps for StdMachine { let mut fd = self.fetch_drive_file(&config.path_on_host)?; let pfl_size = fd.metadata().unwrap().len(); - if config.unit == 0 { + if config.unit.unwrap() == 0 { // According to the Linux/x86 boot protocol, the memory region of // 0x000000 - 0x100000 (1 MiB) is for BIOS usage. And the top 128 // KiB is for BIOS code which is stored in the first PFlash. @@ -681,7 +680,7 @@ impl MachineOps for StdMachine { sector_len, 4_u32, 1_u32, - config.read_only, + config.readonly, ) .with_context(|| MachineError::InitPflashErr)?; PFlash::realize( @@ -704,7 +703,7 @@ impl MachineOps for StdMachine { // GTK display init. #[cfg(feature = "gtk")] match vm_config.display { - Some(ref ds_cfg) if ds_cfg.gtk => { + Some(ref ds_cfg) if ds_cfg.display_type == "gtk" => { let ui_context = UiContext { vm_name: vm_config.guest_name.clone(), power_button: None, @@ -769,8 +768,6 @@ pub(crate) fn arch_syscall_whitelist() -> Vec { BpfRule::new(libc::SYS_mkdir), BpfRule::new(libc::SYS_unlink), BpfRule::new(libc::SYS_readlink), - #[cfg(target_env = "musl")] - BpfRule::new(libc::SYS_clone), #[cfg(target_env = "gnu")] BpfRule::new(libc::SYS_clone3), #[cfg(target_env = "gnu")] diff --git a/machine_manager/src/cmdline.rs b/machine_manager/src/cmdline.rs index 5b0d275..619a7c2 100644 --- a/machine_manager/src/cmdline.rs +++ b/machine_manager/src/cmdline.rs @@ -11,9 +11,10 @@ // See the Mulan PSL v2 for more details. use anyhow::{bail, Context, Result}; +use clap::{ArgAction, Parser}; use crate::{ - config::{parse_trace_options, ChardevType, CmdParser, MachineType, VmConfig}, + config::{add_trace, str_slip_to_clap, ChardevType, MachineType, SocketType, VmConfig}, qmp::qmp_socket::QmpSocketPath, temp_cleaner::TempCleaner, }; @@ -509,7 +510,7 @@ pub fn create_args_parser<'a>() -> ArgParser<'a> { .multiple(false) .long("windows_emu_pid") .value_name("pid") - .help("watch on the external windows emu pid") + .help("watch on the external emulator pid") .takes_value(true), ); @@ -587,7 +588,7 @@ pub fn create_vmconfig(args: &ArgMatches) -> Result { add_args_to_config_multi!((args.values_of("cameradev")), vm_cfg, add_camera_backend); add_args_to_config_multi!((args.values_of("smbios")), vm_cfg, add_smbios); if let Some(opt) = args.value_of("trace") { - parse_trace_options(&opt)?; + add_trace(&opt)?; } // Check the mini-set for Vm to start is ok @@ -599,6 +600,28 @@ pub fn create_vmconfig(args: &ArgMatches) -> Result { Ok(vm_cfg) } +#[derive(Parser)] +#[command(no_binary_name(true))] +struct QmpConfig { + #[arg(long, alias = "classtype")] + uri: String, + #[arg(long, action = ArgAction::SetTrue, required = true)] + server: bool, + #[arg(long, action = ArgAction::SetTrue, required = true)] + nowait: bool, +} + +#[derive(Parser)] +#[command(no_binary_name(true))] +struct MonConfig { + #[arg(long, default_value = "")] + id: String, + #[arg(long, value_parser = ["control"])] + mode: String, + #[arg(long)] + chardev: String, +} + /// This function is to parse qmp socket path and type. /// /// # Arguments @@ -613,75 +636,34 @@ pub fn check_api_channel( vm_config: &mut VmConfig, ) -> Result> { let mut sock_paths = Vec::new(); - if let Some(qmp_config) = args.value_of("qmp") { - let mut cmd_parser = CmdParser::new("qmp"); - cmd_parser.push("").push("server").push("nowait"); - - cmd_parser.parse(&qmp_config)?; - if let Some(uri) = cmd_parser.get_value::("")? { - let sock_path = - QmpSocketPath::new(uri).with_context(|| "Failed to parse qmp socket path")?; - sock_paths.push(sock_path); - } else { - bail!("No uri found for qmp"); - } - if cmd_parser.get_value::("server")?.is_none() { - bail!("Argument \'server\' is needed for qmp"); - } - if cmd_parser.get_value::("nowait")?.is_none() { - bail!("Argument \'nowait\' is needed for qmp"); - } + if let Some(qmp_args) = args.value_of("qmp") { + let qmp_cfg = QmpConfig::try_parse_from(str_slip_to_clap(&qmp_args, true, false))?; + let sock_path = + QmpSocketPath::new(qmp_cfg.uri).with_context(|| "Failed to parse qmp socket path")?; + sock_paths.push(sock_path); } - if let Some(mon_config) = args.value_of("mon") { - let mut cmd_parser = CmdParser::new("monitor"); - cmd_parser.push("id").push("mode").push("chardev"); - cmd_parser.parse(&mon_config)?; - - let chardev = cmd_parser - .get_value::("chardev")? - .with_context(|| "Argument \'chardev\' is missing for \'mon\'")?; - - if let Some(mode) = cmd_parser.get_value::("mode")? { - if mode != *"control" { - bail!("Invalid \'mode\' parameter: {:?} for monitor", &mode); + if let Some(mon_args) = args.value_of("mon") { + let mon_cfg = MonConfig::try_parse_from(str_slip_to_clap(&mon_args, false, false))?; + let cfg = vm_config + .chardev + .remove(&mon_cfg.chardev) + .with_context(|| format!("No chardev found: {}", &mon_cfg.chardev))?; + let socket = cfg + .classtype + .socket_type() + .with_context(|| "Only chardev of unix-socket type can be used for monitor")?; + if let ChardevType::Socket { server, nowait, .. } = cfg.classtype { + if !server || !nowait { + bail!( + "Argument \'server\' and \'nowait\' are both required for chardev \'{}\'", + cfg.id() + ); } - } else { - bail!("Argument \'mode\' of \'mon\' should be set to \'control\'."); } - - if let Some(cfg) = vm_config.chardev.remove(&chardev) { - if let ChardevType::UnixSocket { - path, - server, - nowait, - } = cfg.backend - { - if !server || !nowait { - bail!( - "Argument \'server\' and \'nowait\' are both required for chardev \'{}\'", - path - ); - } - sock_paths.push(QmpSocketPath::Unix { path }); - } else if let ChardevType::TcpSocket { - host, - port, - server, - nowait, - } = cfg.backend - { - if !server || !nowait { - bail!( - "Argument \'server\' and \'nowait\' are both required for chardev \'{}:{}\'", - host, port - ); - } - sock_paths.push(QmpSocketPath::Tcp { host, port }); - } else { - bail!("Only chardev of unix-socket type can be used for monitor"); - } - } else { - bail!("No chardev found: {}", &chardev); + if let SocketType::Tcp { host, port } = socket { + sock_paths.push(QmpSocketPath::Tcp { host, port }); + } else if let SocketType::Unix { path } = socket { + sock_paths.push(QmpSocketPath::Unix { path }); } } diff --git a/machine_manager/src/config/camera.rs b/machine_manager/src/config/camera.rs index 90872b4..43af312 100644 --- a/machine_manager/src/config/camera.rs +++ b/machine_manager/src/config/camera.rs @@ -22,8 +22,10 @@ use crate::{ }; #[derive(Parser, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[command(name = "camera device")] +#[command(no_binary_name(true))] pub struct CameraDevConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] pub id: String, #[arg(long)] @@ -38,7 +40,6 @@ pub enum CamBackendType { V4l2, #[cfg(all(target_env = "ohos", feature = "usb_camera_oh"))] OhCamera, - #[cfg(not(target_env = "ohos"))] Demo, } @@ -51,7 +52,6 @@ impl FromStr for CamBackendType { "v4l2" => Ok(CamBackendType::V4l2), #[cfg(all(target_env = "ohos", feature = "usb_camera_oh"))] "ohcamera" => Ok(CamBackendType::OhCamera), - #[cfg(not(target_env = "ohos"))] "demo" => Ok(CamBackendType::Demo), _ => Err(anyhow!("Unknown camera backend type")), } @@ -61,7 +61,7 @@ impl FromStr for CamBackendType { impl VmConfig { pub fn add_camera_backend(&mut self, camera_config: &str) -> Result<()> { let cfg = format!("cameradev,backend={}", camera_config); - let config = CameraDevConfig::try_parse_from(str_slip_to_clap(&cfg))?; + let config = CameraDevConfig::try_parse_from(str_slip_to_clap(&cfg, true, false))?; self.add_cameradev_with_config(config) } @@ -105,6 +105,7 @@ impl VmConfig { pub fn get_cameradev_config(args: qmp_schema::CameraDevAddArgument) -> Result { let path = args.path.with_context(|| "cameradev config path is null")?; let config = CameraDevConfig { + classtype: "cameradev".to_string(), id: args.id, path, backend: CamBackendType::from_str(&args.driver)?, diff --git a/machine_manager/src/config/chardev.rs b/machine_manager/src/config/chardev.rs index 943de72..0ea4d49 100644 --- a/machine_manager/src/config/chardev.rs +++ b/machine_manager/src/config/chardev.rs @@ -14,248 +14,168 @@ use std::net::IpAddr; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser, Subcommand}; use log::error; use serde::{Deserialize, Serialize}; -use super::{error::ConfigError, get_pci_bdf, pci_args_check, PciBdf}; -use crate::config::{ - check_arg_too_long, CmdParser, ConfigCheck, ExBool, VmConfig, MAX_PATH_LENGTH, -}; +use super::{error::ConfigError, str_slip_to_clap}; +use super::{get_pci_df, parse_bool}; +use crate::config::{valid_id, valid_path, valid_socket_path, ConfigCheck, VmConfig}; use crate::qmp::qmp_schema; -const MAX_GUEST_CID: u64 = 4_294_967_295; -const MIN_GUEST_CID: u64 = 3; - /// Default value of max ports for virtio-serial. const DEFAULT_SERIAL_PORTS_NUMBER: u32 = 31; -/// Character device options. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum ChardevType { - Stdio, - Pty, - UnixSocket { - path: String, - server: bool, - nowait: bool, - }, - TcpSocket { - host: String, - port: u16, - server: bool, - nowait: bool, - }, - File(String), -} - /// Config structure for virtio-serial-port. -#[derive(Debug, Clone)] -pub struct VirtioSerialPort { +#[derive(Parser, Debug, Clone)] +#[command(no_binary_name(true))] +pub struct VirtioSerialPortCfg { + #[arg(long, value_parser = ["virtconsole", "virtserialport"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] pub id: String, - pub chardev: ChardevConfig, - pub nr: u32, - pub is_console: bool, + #[arg(long)] + pub chardev: String, + #[arg(long)] + pub nr: Option, } -impl ConfigCheck for VirtioSerialPort { +impl ConfigCheck for VirtioSerialPortCfg { fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "chardev id") - } -} - -/// Config structure for character device. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChardevConfig { - pub id: String, - pub backend: ChardevType, -} - -impl ConfigCheck for ChardevConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "chardev id")?; - match &self.backend { - ChardevType::UnixSocket { path, .. } => { - if path.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "unix-socket path".to_string(), - MAX_PATH_LENGTH - ))); - } - Ok(()) - } - ChardevType::TcpSocket { host, port, .. } => { - if *port == 0u16 { - return Err(anyhow!(ConfigError::InvalidParam( - "port".to_string(), - "tcp-socket".to_string() - ))); - } - let ip_address = IpAddr::from_str(host); - if ip_address.is_err() { - return Err(anyhow!(ConfigError::InvalidParam( - "host".to_string(), - "tcp-socket".to_string() - ))); - } - Ok(()) - } - ChardevType::File(path) => { - if path.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "file path".to_string(), - MAX_PATH_LENGTH - ))); - } - Ok(()) - } - _ => Ok(()), + if self.classtype != "virtconsole" && self.nr.unwrap() == 0 { + bail!("Port number 0 on virtio-serial devices reserved for virtconsole device."); } + + Ok(()) } } -fn check_chardev_fields( - dev_type: &str, - cmd_parser: &CmdParser, - supported_fields: &[&str], -) -> Result<()> { - for (field, value) in &cmd_parser.params { - let supported_field = supported_fields.contains(&field.as_str()); - if !supported_field && value.is_some() { +impl VirtioSerialPortCfg { + /// If nr is not set in command line. Configure incremental maximum value for virtconsole. + /// Configure incremental maximum value(except 0) for virtserialport. + pub fn auto_nr(&mut self, free_port0: bool, free_nr: u32, max_nr_ports: u32) -> Result<()> { + let free_console_nr = if free_port0 { 0 } else { free_nr }; + let auto_nr = match self.classtype.as_str() { + "virtconsole" => free_console_nr, + "virtserialport" => free_nr, + _ => bail!("Invalid classtype."), + }; + let nr = self.nr.unwrap_or(auto_nr); + if nr >= max_nr_ports { bail!( - "Chardev of type {} does not support \'{}\' argument", - dev_type, - field + "virtio serial port nr {} should be less than virtio serial's max_nr_ports {}", + nr, + max_nr_ports ); } - } - Ok(()) -} - -fn parse_stdio_chardev(chardev_id: String, cmd_parser: CmdParser) -> Result { - let supported_fields = ["", "id"]; - check_chardev_fields("stdio", &cmd_parser, &supported_fields)?; - Ok(ChardevConfig { - id: chardev_id, - backend: ChardevType::Stdio, - }) -} -fn parse_pty_chardev(chardev_id: String, cmd_parser: CmdParser) -> Result { - let supported_fields = ["", "id"]; - check_chardev_fields("pty", &cmd_parser, &supported_fields)?; - Ok(ChardevConfig { - id: chardev_id, - backend: ChardevType::Pty, - }) + self.nr = Some(nr); + Ok(()) + } } -fn parse_file_chardev(chardev_id: String, cmd_parser: CmdParser) -> Result { - let supported_fields = ["", "id", "path"]; - check_chardev_fields("file", &cmd_parser, &supported_fields)?; - - let path = cmd_parser - .get_value::("path")? - .with_context(|| ConfigError::FieldIsMissing("path".to_string(), "chardev".to_string()))?; - - let default_value = path.clone(); - let file_path = std::fs::canonicalize(path).map_or(default_value, |canonical_path| { - String::from(canonical_path.to_str().unwrap()) - }); - - Ok(ChardevConfig { - id: chardev_id, - backend: ChardevType::File(file_path), - }) +/// Config structure for character device. +#[derive(Parser, Debug, Clone, Serialize, Deserialize)] +#[command(no_binary_name(true))] +pub struct ChardevConfig { + #[command(subcommand)] + pub classtype: ChardevType, } -fn parse_socket_chardev(chardev_id: String, cmd_parser: CmdParser) -> Result { - let mut server_enabled = false; - let server = cmd_parser.get_value::("server")?; - if let Some(server) = server { - if server.ne("") { - bail!("No parameter needed for server"); +impl ChardevConfig { + pub fn id(&self) -> String { + match &self.classtype { + ChardevType::Stdio { id } => id, + ChardevType::Pty { id } => id, + ChardevType::Socket { id, .. } => id, + ChardevType::File { id, .. } => id, } - server_enabled = true; + .clone() } +} - let mut nowait_enabled = false; - let nowait = cmd_parser.get_value::("nowait")?; - if let Some(nowait) = nowait { - if nowait.ne("") { - bail!("No parameter needed for nowait"); +impl ConfigCheck for ChardevConfig { + fn check(&self) -> Result<()> { + if let ChardevType::Socket { .. } = self.classtype { + self.classtype.socket_type()?; } - nowait_enabled = true; - } - - let path = cmd_parser.get_value::("path")?; - if let Some(path) = path { - let supported_fields = ["", "id", "path", "server", "nowait"]; - check_chardev_fields("unix-socket", &cmd_parser, &supported_fields)?; - - let default_value = path.clone(); - let socket_path = std::fs::canonicalize(path).map_or(default_value, |canonical_path| { - String::from(canonical_path.to_str().unwrap()) - }); - - return Ok(ChardevConfig { - id: chardev_id, - backend: ChardevType::UnixSocket { - path: socket_path, - server: server_enabled, - nowait: nowait_enabled, - }, - }); - } - let port = cmd_parser.get_value::("port")?; - if let Some(port) = port { - let supported_fields = ["", "id", "host", "port", "server", "nowait"]; - check_chardev_fields("tcp-socket", &cmd_parser, &supported_fields)?; - - let host = cmd_parser.get_value::("host")?; - return Ok(ChardevConfig { - id: chardev_id, - backend: ChardevType::TcpSocket { - host: host.unwrap_or_else(|| String::from("0.0.0.0")), - port, - server: server_enabled, - nowait: nowait_enabled, - }, - }); + Ok(()) } +} - Err(anyhow!(ConfigError::InvalidParam( - "backend".to_string(), - "chardev".to_string() - ))) +/// Character device options. +#[derive(Subcommand, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ChardevType { + Stdio { + #[arg(long, value_parser = valid_id)] + id: String, + }, + Pty { + #[arg(long, value_parser = valid_id)] + id: String, + }, + // Unix Socket: use `path`. + // Tcp Socket: use `host` and `port`. + #[clap(group = clap::ArgGroup::new("unix-socket").args(&["host", "port"]).requires("port").multiple(true).conflicts_with("tcp-socket"))] + #[clap(group = clap::ArgGroup::new("tcp-socket").arg("path").conflicts_with("unix-socket"))] + Socket { + #[arg(long, value_parser = valid_id)] + id: String, + #[arg(long, value_parser = valid_socket_path)] + path: Option, + #[arg(long, value_parser = valid_host, default_value = "0.0.0.0")] + host: String, + #[arg(long, value_parser = clap::value_parser!(u16).range(1..))] + port: Option, + #[arg(long, action = ArgAction::SetTrue)] + server: bool, + #[arg(long, action = ArgAction::SetTrue)] + nowait: bool, + }, + File { + #[arg(long, value_parser = valid_id)] + id: String, + #[arg(long, value_parser = valid_path)] + path: String, + }, } -pub fn parse_chardev(chardev_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("chardev"); - for field in ["", "id", "path", "host", "port", "server", "nowait"] { - cmd_parser.push(field); +impl ChardevType { + pub fn socket_type(&self) -> Result { + if let ChardevType::Socket { + path, host, port, .. + } = self + { + if path.is_some() && port.is_none() { + return Ok(SocketType::Unix { + path: path.clone().unwrap(), + }); + } else if port.is_some() && path.is_none() { + return Ok(SocketType::Tcp { + host: host.clone(), + port: (*port).unwrap(), + }); + } + } + bail!("Not socket type or invalid socket type"); } +} - cmd_parser.parse(chardev_config)?; - - let chardev_id = cmd_parser - .get_value::("id")? - .with_context(|| ConfigError::FieldIsMissing("id".to_string(), "chardev".to_string()))?; - - let backend = cmd_parser - .get_value::("")? - .with_context(|| ConfigError::InvalidParam("backend".to_string(), "chardev".to_string()))?; - - match backend.as_str() { - "stdio" => parse_stdio_chardev(chardev_id, cmd_parser), - "pty" => parse_pty_chardev(chardev_id, cmd_parser), - "file" => parse_file_chardev(chardev_id, cmd_parser), - "socket" => parse_socket_chardev(chardev_id, cmd_parser), - _ => Err(anyhow!(ConfigError::InvalidParam( - backend, - "chardev".to_string() - ))), +pub enum SocketType { + Unix { path: String }, + Tcp { host: String, port: u16 }, +} + +fn valid_host(host: &str) -> Result { + let ip_address = IpAddr::from_str(host); + if ip_address.is_err() { + return Err(anyhow!(ConfigError::InvalidParam( + "host".to_string(), + "tcp-socket".to_string() + ))); } + Ok(host.to_string()) } /// Get chardev config from qmp arguments. @@ -291,9 +211,11 @@ pub fn get_chardev_config(args: qmp_schema::CharDevAddArgument) -> Result Result Result { - if let Some(char_dev) = vm_config.chardev.remove(chardev) { - match char_dev.backend.clone() { - ChardevType::UnixSocket { - path, - server, - nowait, - } => { - if server || nowait { - bail!( - "Argument \'server\' or \'nowait\' is not need for chardev \'{}\'", - path - ); - } - Ok(path) - } - _ => { - bail!( - "Chardev {:?} backend should be unix-socket type.", - &char_dev.id - ); - } +pub fn get_chardev_socket_path(chardev: ChardevConfig) -> Result { + let id = chardev.id(); + if let ChardevType::Socket { + path, + server, + nowait, + .. + } = chardev.classtype + { + path.clone() + .with_context(|| format!("Chardev {:?} backend should be unix-socket type.", id))?; + if server || nowait { + bail!( + "Argument \'server\' or \'nowait\' is not need for chardev \'{}\'", + path.unwrap() + ); } - } else { - bail!("Chardev: {:?} not found for character device", &chardev); + return Ok(path.unwrap()); } -} - -pub fn parse_virtserialport( - vm_config: &mut VmConfig, - config_args: &str, - is_console: bool, - free_nr: u32, - free_port0: bool, -) -> Result { - let mut cmd_parser = CmdParser::new("virtserialport"); - cmd_parser.push("").push("id").push("chardev").push("nr"); - cmd_parser.parse(config_args)?; - - let chardev_name = cmd_parser - .get_value::("chardev")? - .with_context(|| { - ConfigError::FieldIsMissing("chardev".to_string(), "virtserialport".to_string()) - })?; - let id = cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "virtserialport".to_string()) - })?; - - let nr = cmd_parser - .get_value::("nr")? - .unwrap_or(if is_console && free_port0 { 0 } else { free_nr }); - - if nr == 0 && !is_console { - bail!("Port number 0 on virtio-serial devices reserved for virtconsole device."); - } - - if let Some(chardev) = vm_config.chardev.remove(&chardev_name) { - let port_cfg = VirtioSerialPort { - id, - chardev, - nr, - is_console, - }; - port_cfg.check()?; - return Ok(port_cfg); - } - bail!("Chardev {:?} not found or is in use", &chardev_name); + bail!("Chardev {:?} backend should be unix-socket type.", id); } impl VmConfig { /// Add chardev config to `VmConfig`. pub fn add_chardev(&mut self, chardev_config: &str) -> Result<()> { - let chardev = parse_chardev(chardev_config)?; + let chardev = ChardevConfig::try_parse_from(str_slip_to_clap(chardev_config, true, true))?; chardev.check()?; - let chardev_id = chardev.id.clone(); - if self.chardev.get(&chardev_id).is_none() { - self.chardev.insert(chardev_id, chardev); - } else { - bail!("Chardev {:?} has been added", &chardev_id); - } + self.add_chardev_with_config(chardev)?; Ok(()) } @@ -395,16 +264,11 @@ impl VmConfig { /// /// * `conf` - The chardev config to be added to the vm. pub fn add_chardev_with_config(&mut self, conf: ChardevConfig) -> Result<()> { - if let Err(e) = conf.check() { - bail!("Chardev config checking failed, {}", e.to_string()); - } - - let chardev_id = conf.id.clone(); - if self.chardev.get(&chardev_id).is_none() { - self.chardev.insert(chardev_id, conf); - } else { + let chardev_id = conf.id(); + if self.chardev.get(&chardev_id).is_some() { bail!("Chardev {:?} has been added", chardev_id); } + self.chardev.insert(chardev_id, conf); Ok(()) } @@ -414,11 +278,9 @@ impl VmConfig { /// /// * `id` - The chardev id which is used to delete chardev config. pub fn del_chardev_by_id(&mut self, id: &str) -> Result<()> { - if self.chardev.get(id).is_some() { - self.chardev.remove(id); - } else { - bail!("Chardev {} not found", id); - } + self.chardev + .remove(id) + .with_context(|| format!("Chardev {} not found", id))?; Ok(()) } } @@ -458,189 +320,74 @@ impl VmConfig { } } -/// Config structure for virtio-vsock. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct VsockConfig { - pub id: String, - pub guest_cid: u64, - pub vhost_fd: Option, -} - -impl ConfigCheck for VsockConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "vsock id")?; - - if self.guest_cid < MIN_GUEST_CID || self.guest_cid >= MAX_GUEST_CID { - return Err(anyhow!(ConfigError::IllegalValue( - "Vsock guest-cid".to_string(), - MIN_GUEST_CID, - true, - MAX_GUEST_CID, - false, - ))); - } - - Ok(()) - } -} - -pub fn parse_vsock(vsock_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("vhost-vsock"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("multifunction") - .push("guest-cid") - .push("vhostfd"); - cmd_parser.parse(vsock_config)?; - pci_args_check(&cmd_parser)?; - let id = cmd_parser - .get_value::("id")? - .with_context(|| ConfigError::FieldIsMissing("id".to_string(), "vsock".to_string()))?; - - let guest_cid = cmd_parser.get_value::("guest-cid")?.with_context(|| { - ConfigError::FieldIsMissing("guest-cid".to_string(), "vsock".to_string()) - })?; - - let vhost_fd = cmd_parser.get_value::("vhostfd")?; - let vsock = VsockConfig { - id, - guest_cid, - vhost_fd, - }; - Ok(vsock) -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct VirtioSerialInfo { + #[arg(long, value_parser = ["virtio-serial-pci", "virtio-serial-device"])] + pub classtype: String, + #[arg(long, default_value = "", value_parser = valid_id)] pub id: String, - pub pci_bdf: Option, - pub multifunction: bool, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: Option, + #[arg(long, default_value = "31", value_parser = clap::value_parser!(u32).range(1..=DEFAULT_SERIAL_PORTS_NUMBER as i64))] pub max_ports: u32, } -impl ConfigCheck for VirtioSerialInfo { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "virtio-serial id")?; - - if self.max_ports < 1 || self.max_ports > DEFAULT_SERIAL_PORTS_NUMBER { - return Err(anyhow!(ConfigError::IllegalValue( - "Virtio-serial max_ports".to_string(), - 1, - true, - DEFAULT_SERIAL_PORTS_NUMBER as u64, - true - ))); - } - - Ok(()) - } -} - -pub fn parse_virtio_serial( - vm_config: &mut VmConfig, - serial_config: &str, -) -> Result { - let mut cmd_parser = CmdParser::new("virtio-serial"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("multifunction") - .push("max_ports"); - cmd_parser.parse(serial_config)?; - pci_args_check(&cmd_parser)?; - - if vm_config.virtio_serial.is_some() { - bail!("Only one virtio serial device is supported"); - } - - let id = cmd_parser.get_value::("id")?.unwrap_or_default(); - let multifunction = cmd_parser - .get_value::("multifunction")? - .map_or(false, |switch| switch.into()); - let max_ports = cmd_parser - .get_value::("max_ports")? - .unwrap_or(DEFAULT_SERIAL_PORTS_NUMBER); - let virtio_serial = if serial_config.contains("-pci") { - let pci_bdf = get_pci_bdf(serial_config)?; - VirtioSerialInfo { - id, - pci_bdf: Some(pci_bdf), - multifunction, - max_ports, - } - } else { - VirtioSerialInfo { - id, - pci_bdf: None, - multifunction, +impl VirtioSerialInfo { + pub fn auto_max_ports(&mut self) { + if self.classtype == "virtio-serial-device" { // Micro_vm does not support multi-ports in virtio-serial-device. - max_ports: 1, + self.max_ports = 1; } - }; - virtio_serial.check()?; - vm_config.virtio_serial = Some(virtio_serial.clone()); - - Ok(virtio_serial) + } } #[cfg(test)] mod tests { use super::*; - use crate::config::parse_virtio_serial; fn test_mmio_console_config_cmdline_parser(chardev_cfg: &str, expected_chardev: ChardevType) { let mut vm_config = VmConfig::default(); - assert!(parse_virtio_serial(&mut vm_config, "virtio-serial-device").is_ok()); + let serial_cmd = "virtio-serial-device"; + let mut serial_cfg = + VirtioSerialInfo::try_parse_from(str_slip_to_clap(serial_cmd, true, false)).unwrap(); + serial_cfg.auto_max_ports(); + vm_config.virtio_serial = Some(serial_cfg.clone()); assert!(vm_config.add_chardev(chardev_cfg).is_ok()); - let virt_console = parse_virtserialport( - &mut vm_config, - "virtconsole,chardev=test_console,id=console1,nr=1", - true, - 0, - true, - ); - assert!(virt_console.is_ok()); - - let console_cfg = virt_console.unwrap(); - assert_eq!(console_cfg.id, "console1"); - assert_eq!(console_cfg.chardev.backend, expected_chardev); + let port_cmd = "virtconsole,chardev=test_console,id=console1,nr=0"; + let mut port_cfg = + VirtioSerialPortCfg::try_parse_from(str_slip_to_clap(port_cmd, true, false)).unwrap(); + assert!(port_cfg.auto_nr(true, 0, serial_cfg.max_ports).is_ok()); + let chardev = vm_config.chardev.remove(&port_cfg.chardev).unwrap(); + assert_eq!(port_cfg.id, "console1"); + assert_eq!(port_cfg.nr.unwrap(), 0); + assert_eq!(chardev.classtype, expected_chardev); + + // Error: VirtioSerialPortCfg.nr >= VirtioSerialInfo.max_nr_ports. + let port_cmd = "virtconsole,chardev=test_console,id=console1,nr=1"; + let mut port_cfg = + VirtioSerialPortCfg::try_parse_from(str_slip_to_clap(port_cmd, true, false)).unwrap(); + assert!(port_cfg.auto_nr(true, 0, serial_cfg.max_ports).is_err()); let mut vm_config = VmConfig::default(); - assert!( - parse_virtio_serial(&mut vm_config, "virtio-serial-device,bus=pcie.0,addr=0x1") - .is_err() - ); assert!(vm_config .add_chardev("sock,id=test_console,path=/path/to/socket") .is_err()); - - let mut vm_config = VmConfig::default(); - assert!(parse_virtio_serial(&mut vm_config, "virtio-serial-device").is_ok()); - assert!(vm_config - .add_chardev("socket,id=test_console,path=/path/to/socket,server,nowait") - .is_ok()); - let virt_console = parse_virtserialport( - &mut vm_config, - "virtconsole,chardev=test_console1,id=console1,nr=1", - true, - 0, - true, - ); - // test_console1 does not exist. - assert!(virt_console.is_err()); } #[test] fn test_mmio_console_config_cmdline_parser_1() { let chardev_cfg = "socket,id=test_console,path=/path/to/socket,server,nowait"; - let expected_chardev = ChardevType::UnixSocket { - path: "/path/to/socket".to_string(), + let expected_chardev = ChardevType::Socket { + id: "test_console".to_string(), + path: Some("/path/to/socket".to_string()), + host: "0.0.0.0".to_string(), + port: None, server: true, nowait: true, }; @@ -650,9 +397,11 @@ mod tests { #[test] fn test_mmio_console_config_cmdline_parser_2() { let chardev_cfg = "socket,id=test_console,host=127.0.0.1,port=9090,server,nowait"; - let expected_chardev = ChardevType::TcpSocket { + let expected_chardev = ChardevType::Socket { + id: "test_console".to_string(), + path: None, host: "127.0.0.1".to_string(), - port: 9090, + port: Some(9090), server: true, nowait: true, }; @@ -661,41 +410,34 @@ mod tests { fn test_pci_console_config_cmdline_parser(chardev_cfg: &str, expected_chardev: ChardevType) { let mut vm_config = VmConfig::default(); - let virtio_arg = "virtio-serial-pci,bus=pcie.0,addr=0x1.0x2"; - assert!(parse_virtio_serial(&mut vm_config, virtio_arg).is_ok()); + let serial_cmd = "virtio-serial-pci,bus=pcie.0,addr=0x1.0x2,multifunction=on"; + let mut serial_cfg = + VirtioSerialInfo::try_parse_from(str_slip_to_clap(serial_cmd, true, false)).unwrap(); + serial_cfg.auto_max_ports(); + vm_config.virtio_serial = Some(serial_cfg.clone()); assert!(vm_config.add_chardev(chardev_cfg).is_ok()); - let virt_console = parse_virtserialport( - &mut vm_config, - "virtconsole,chardev=test_console,id=console1,nr=1", - true, - 0, - true, - ); - assert!(virt_console.is_ok()); - let console_cfg = virt_console.unwrap(); - + let console_cmd = "virtconsole,chardev=test_console,id=console1,nr=1"; + let mut console_cfg = + VirtioSerialPortCfg::try_parse_from(str_slip_to_clap(console_cmd, true, false)) + .unwrap(); + assert!(console_cfg.auto_nr(true, 0, serial_cfg.max_ports).is_ok()); + let chardev = vm_config.chardev.remove(&console_cfg.chardev).unwrap(); assert_eq!(console_cfg.id, "console1"); let serial_info = vm_config.virtio_serial.clone().unwrap(); - assert!(serial_info.pci_bdf.is_some()); - let bdf = serial_info.pci_bdf.unwrap(); - assert_eq!(bdf.bus, "pcie.0"); - assert_eq!(bdf.addr, (1, 2)); - assert_eq!(console_cfg.chardev.backend, expected_chardev); - - let mut vm_config = VmConfig::default(); - assert!(parse_virtio_serial( - &mut vm_config, - "virtio-serial-pci,bus=pcie.0,addr=0x1.0x2,multifunction=on" - ) - .is_ok()); + assert_eq!(serial_info.bus.unwrap(), "pcie.0"); + assert_eq!(serial_info.addr.unwrap(), (1, 2)); + assert_eq!(chardev.classtype, expected_chardev); } #[test] fn test_pci_console_config_cmdline_parser_1() { let chardev_cfg = "socket,id=test_console,path=/path/to/socket,server,nowait"; - let expected_chardev = ChardevType::UnixSocket { - path: "/path/to/socket".to_string(), + let expected_chardev = ChardevType::Socket { + id: "test_console".to_string(), + path: Some("/path/to/socket".to_string()), + host: "0.0.0.0".to_string(), + port: None, server: true, nowait: true, }; @@ -705,36 +447,17 @@ mod tests { #[test] fn test_pci_console_config_cmdline_parser_2() { let chardev_cfg = "socket,id=test_console,host=127.0.0.1,port=9090,server,nowait"; - let expected_chardev = ChardevType::TcpSocket { + let expected_chardev = ChardevType::Socket { + id: "test_console".to_string(), + path: None, host: "127.0.0.1".to_string(), - port: 9090, + port: Some(9090), server: true, nowait: true, }; test_pci_console_config_cmdline_parser(chardev_cfg, expected_chardev) } - #[test] - fn test_vsock_config_cmdline_parser() { - let vsock_cfg_op = parse_vsock("vhost-vsock-device,id=test_vsock,guest-cid=3"); - assert!(vsock_cfg_op.is_ok()); - - let vsock_config = vsock_cfg_op.unwrap(); - assert_eq!(vsock_config.id, "test_vsock"); - assert_eq!(vsock_config.guest_cid, 3); - assert_eq!(vsock_config.vhost_fd, None); - assert!(vsock_config.check().is_ok()); - - let vsock_cfg_op = parse_vsock("vhost-vsock-device,id=test_vsock,guest-cid=3,vhostfd=4"); - assert!(vsock_cfg_op.is_ok()); - - let vsock_config = vsock_cfg_op.unwrap(); - assert_eq!(vsock_config.id, "test_vsock"); - assert_eq!(vsock_config.guest_cid, 3); - assert_eq!(vsock_config.vhost_fd, Some(4)); - assert!(vsock_config.check().is_ok()); - } - #[test] fn test_chardev_config_cmdline_parser() { let check_argument = |arg: String, expect: ChardevType| { @@ -744,17 +467,30 @@ mod tests { let device_id = "test_id"; if let Some(char_dev) = vm_config.chardev.remove(device_id) { - assert_eq!(char_dev.backend, expect); + assert_eq!(char_dev.classtype, expect); } else { assert!(false); } }; - check_argument("stdio,id=test_id".to_string(), ChardevType::Stdio); - check_argument("pty,id=test_id".to_string(), ChardevType::Pty); + check_argument( + "stdio,id=test_id".to_string(), + ChardevType::Stdio { + id: "test_id".to_string(), + }, + ); + check_argument( + "pty,id=test_id".to_string(), + ChardevType::Pty { + id: "test_id".to_string(), + }, + ); check_argument( "file,id=test_id,path=/some/file".to_string(), - ChardevType::File("/some/file".to_string()), + ChardevType::File { + id: "test_id".to_string(), + path: "/some/file".to_string(), + }, ); let extra_params = [ @@ -767,17 +503,22 @@ mod tests { for (param, server_state, nowait_state) in extra_params { check_argument( format!("{}{}", "socket,id=test_id,path=/path/to/socket", param), - ChardevType::UnixSocket { - path: "/path/to/socket".to_string(), + ChardevType::Socket { + id: "test_id".to_string(), + path: Some("/path/to/socket".to_string()), + host: "0.0.0.0".to_string(), + port: None, server: server_state, nowait: nowait_state, }, ); check_argument( format!("{}{}", "socket,id=test_id,port=9090", param), - ChardevType::TcpSocket { + ChardevType::Socket { + id: "test_id".to_string(), + path: None, host: "0.0.0.0".to_string(), - port: 9090, + port: Some(9090), server: server_state, nowait: nowait_state, }, @@ -787,9 +528,11 @@ mod tests { "{}{}", "socket,id=test_id,host=172.56.16.12,port=7070", param ), - ChardevType::TcpSocket { + ChardevType::Socket { + id: "test_id".to_string(), + path: None, host: "172.56.16.12".to_string(), - port: 7070, + port: Some(7070), server: server_state, nowait: nowait_state, }, diff --git a/machine_manager/src/config/demo_dev.rs b/machine_manager/src/config/demo_dev.rs deleted file mode 100644 index 10d2199..0000000 --- a/machine_manager/src/config/demo_dev.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{bail, Result}; - -use super::{pci_args_check, CmdParser, VmConfig}; - -/// Config struct for `demo_dev`. -/// Contains demo_dev device's attr. -#[derive(Debug, Clone)] -pub struct DemoDevConfig { - pub id: String, - // Different device implementations can be configured based on this parameter - pub device_type: String, - pub bar_num: u8, - // Every bar has the same size just for simplification. - pub bar_size: u64, -} - -impl DemoDevConfig { - pub fn new() -> Self { - Self { - id: "".to_string(), - device_type: "".to_string(), - bar_num: 0, - bar_size: 0, - } - } -} - -impl Default for DemoDevConfig { - fn default() -> Self { - Self::new() - } -} - -pub fn parse_demo_dev(_vm_config: &mut VmConfig, args_str: String) -> Result { - let mut cmd_parser = CmdParser::new("demo-dev"); - cmd_parser - .push("") - .push("id") - .push("addr") - .push("device_type") - .push("bus") - .push("bar_num") - .push("bar_size"); - cmd_parser.parse(&args_str)?; - - pci_args_check(&cmd_parser)?; - - let mut demo_dev_cfg = DemoDevConfig::new(); - - if let Some(id) = cmd_parser.get_value::("id")? { - demo_dev_cfg.id = id; - } else { - bail!("No id configured for demo device"); - } - - if let Some(device_type) = cmd_parser.get_value::("device_type")? { - demo_dev_cfg.device_type = device_type; - } - - if let Some(bar_num) = cmd_parser.get_value::("bar_num")? { - demo_dev_cfg.bar_num = bar_num; - } - - // todo: support parsing hex num "0x**". It just supports decimal number now. - if let Some(bar_size) = cmd_parser.get_value::("bar_size")? { - demo_dev_cfg.bar_size = bar_size; - } - - Ok(demo_dev_cfg) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_parse_demo_dev() { - let mut vm_config = VmConfig::default(); - let config_line = "-device pcie-demo-dev,bus=pcie.0,addr=4.0,id=test_0,device_type=demo-gpu,bar_num=3,bar_size=4096"; - let demo_cfg = parse_demo_dev(&mut vm_config, config_line.to_string()).unwrap(); - assert_eq!(demo_cfg.id, "test_0".to_string()); - assert_eq!(demo_cfg.device_type, "demo-gpu".to_string()); - assert_eq!(demo_cfg.bar_num, 3); - assert_eq!(demo_cfg.bar_size, 4096); - } -} diff --git a/machine_manager/src/config/devices.rs b/machine_manager/src/config/devices.rs index cf42739..e355b88 100644 --- a/machine_manager/src/config/devices.rs +++ b/machine_manager/src/config/devices.rs @@ -13,7 +13,7 @@ use anyhow::{Context, Result}; use regex::Regex; -use super::{CmdParser, VmConfig}; +use super::{get_class_type, VmConfig}; use crate::qmp::qmp_schema; impl VmConfig { @@ -117,7 +117,7 @@ impl VmConfig { } pub fn add_device(&mut self, device_config: &str) -> Result<()> { - let device_type = parse_device_type(device_config)?; + let device_type = get_class_type(device_config).with_context(|| "Missing driver field.")?; self.devices.push((device_type, device_config.to_string())); Ok(()) @@ -135,44 +135,3 @@ impl VmConfig { } } } - -pub fn parse_device_type(device_config: &str) -> Result { - let mut cmd_params = CmdParser::new("device"); - cmd_params.push(""); - cmd_params.get_parameters(device_config)?; - cmd_params - .get_value::("")? - .with_context(|| "Missing driver field.") -} - -pub fn parse_device_id(device_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("device"); - cmd_parser.push("id"); - - cmd_parser.get_parameters(device_config)?; - if let Some(id) = cmd_parser.get_value::("id")? { - Ok(id) - } else { - Ok(String::new()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_device_id() { - let test_conf = "virtio-blk-device,drive=rootfs,id=blkid"; - let ret = parse_device_id(test_conf); - assert!(ret.is_ok()); - let id = ret.unwrap(); - assert_eq!("blkid", id); - - let test_conf = "virtio-blk-device,drive=rootfs"; - let ret = parse_device_id(test_conf); - assert!(ret.is_ok()); - let id = ret.unwrap(); - assert_eq!("", id); - } -} diff --git a/machine_manager/src/config/display.rs b/machine_manager/src/config/display.rs index 8f1f2e0..d078285 100644 --- a/machine_manager/src/config/display.rs +++ b/machine_manager/src/config/display.rs @@ -13,17 +13,15 @@ #[cfg(feature = "gtk")] use std::sync::Arc; +use anyhow::Result; #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] -use anyhow::Context; -use anyhow::{bail, Result}; +use anyhow::{bail, Context}; +use clap::{ArgAction, Parser}; use serde::{Deserialize, Serialize}; #[cfg(feature = "gtk")] use vmm_sys_util::eventfd::EventFd; -use crate::config::{CmdParser, ExBool, VmConfig}; - -#[cfg(all(target_env = "ohos", feature = "ohui_srv"))] -static DEFAULT_UI_PATH: &str = "/tmp/"; +use crate::config::{parse_bool, str_slip_to_clap, VmConfig}; /// Event fd related to power button in gtk. #[cfg(feature = "gtk")] @@ -41,88 +39,59 @@ pub struct UiContext { } #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct OhuiConfig { - /// Use OHUI. - pub ohui: bool, - /// Create the OHUI thread. - pub iothread: Option, - /// Confirm related files' path. - pub path: String, +fn get_sock_path(p: &str) -> Result { + let path = std::fs::canonicalize(p) + .with_context(|| format!("Failed to get real directory path: {:?}", p))?; + if !path.exists() { + bail!( + "The defined directory {:?} path doesn't exist", + path.as_os_str() + ); + } + if !path.is_dir() { + bail!( + "The defined socks-path {:?} is not directory", + path.as_os_str() + ); + } + + Ok(path.to_str().unwrap().to_string()) } /// GTK and OHUI related configuration. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] + pub struct DisplayConfig { - /// Create the GTK thread. - pub gtk: bool, + #[arg(long, alias = "classtype", value_parser = ["gtk", "ohui"])] + pub display_type: String, /// App name if configured. + #[arg(long)] pub app_name: Option, /// Keep the window fill the desktop. + #[arg(long, default_value = "off", action = ArgAction::Append, value_parser = parse_bool)] pub full_screen: bool, - /// Used for OHUI + /// Create the OHUI thread. #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] - pub ohui_config: OhuiConfig, + #[arg(long)] + pub iothread: Option, + /// Confirm related files' path. Default ui path is "/tmp". + #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] + #[arg(long, alias = "socks-path", default_value = "/tmp/", value_parser = get_sock_path)] + pub path: String, } #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] impl DisplayConfig { pub fn get_ui_path(&self) -> String { - self.ohui_config.path.clone() + self.path.clone() } } impl VmConfig { pub fn add_display(&mut self, vm_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("display"); - cmd_parser.push("").push("full-screen").push("app-name"); - #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] - cmd_parser.push("iothread").push("socks-path"); - cmd_parser.parse(vm_config)?; - let mut display_config = DisplayConfig::default(); - if let Some(str) = cmd_parser.get_value::("")? { - match str.as_str() { - "gtk" => display_config.gtk = true, - #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] - "ohui" => display_config.ohui_config.ohui = true, - _ => bail!("Unsupported device: {}", str), - } - } - if let Some(name) = cmd_parser.get_value::("app-name")? { - display_config.app_name = Some(name); - } - if let Some(default) = cmd_parser.get_value::("full-screen")? { - display_config.full_screen = default.into(); - } - - #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] - if display_config.ohui_config.ohui { - if let Some(iothread) = cmd_parser.get_value::("iothread")? { - display_config.ohui_config.iothread = Some(iothread); - } - - if let Some(path) = cmd_parser.get_value::("socks-path")? { - let path = std::fs::canonicalize(path.clone()).with_context(|| { - format!("Failed to get real directory path: {:?}", path.clone()) - })?; - if !path.exists() { - bail!( - "The defined directory {:?} path doesn't exist", - path.as_os_str() - ); - } - if !path.is_dir() { - bail!( - "The defined socks-path {:?} is not directory", - path.as_os_str() - ); - } - display_config.ohui_config.path = path.to_str().unwrap().to_string(); - } else { - display_config.ohui_config.path = DEFAULT_UI_PATH.to_string(); - } - } - + let display_config = + DisplayConfig::try_parse_from(str_slip_to_clap(vm_config, true, false))?; self.display = Some(display_config); Ok(()) } @@ -141,28 +110,28 @@ mod tests { let config_line = "gtk"; assert!(vm_config.add_display(config_line).is_ok()); let display_config = vm_config.display.unwrap(); - assert_eq!(display_config.gtk, true); + assert_eq!(display_config.display_type, "gtk"); assert_eq!(display_config.full_screen, false); let mut vm_config = VmConfig::default(); let config_line = "gtk,full-screen=on"; assert!(vm_config.add_display(config_line).is_ok()); let display_config = vm_config.display.unwrap(); - assert_eq!(display_config.gtk, true); + assert_eq!(display_config.display_type, "gtk"); assert_eq!(display_config.full_screen, true); let mut vm_config = VmConfig::default(); let config_line = "gtk,full-screen=off"; assert!(vm_config.add_display(config_line).is_ok()); let display_config = vm_config.display.unwrap(); - assert_eq!(display_config.gtk, true); + assert_eq!(display_config.display_type, "gtk"); assert_eq!(display_config.full_screen, false); let mut vm_config = VmConfig::default(); let config_line = "gtk,app-name=desktopappengine"; assert!(vm_config.add_display(config_line).is_ok()); let display_config = vm_config.display.unwrap(); - assert_eq!(display_config.gtk, true); + assert_eq!(display_config.display_type, "gtk"); assert_eq!(display_config.full_screen, false); assert_eq!( display_config.app_name, diff --git a/machine_manager/src/config/drive.rs b/machine_manager/src/config/drive.rs index e88826a..e8e1a60 100644 --- a/machine_manager/src/config/drive.rs +++ b/machine_manager/src/config/drive.rs @@ -16,25 +16,17 @@ use std::path::Path; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser}; use log::error; use serde::{Deserialize, Serialize}; -use super::{error::ConfigError, pci_args_check, M}; -use crate::config::{ - check_arg_too_long, get_chardev_socket_path, memory_unit_conversion, CmdParser, ConfigCheck, - ExBool, VmConfig, DEFAULT_VIRTQUEUE_SIZE, MAX_PATH_LENGTH, MAX_STRING_LENGTH, MAX_VIRTIO_QUEUE, -}; +use super::{error::ConfigError, parse_size, valid_id, valid_path}; +use crate::config::{parse_bool, str_slip_to_clap, ConfigCheck, VmConfig, MAX_STRING_LENGTH}; use util::aio::{aio_probe, AioEngine, WriteZeroesState}; -const MAX_SERIAL_NUM: usize = 20; const MAX_IOPS: u64 = 1_000_000; const MAX_UNIT_ID: usize = 2; -// Seg_max = queue_size - 2. So, size of each virtqueue for virtio-blk should be larger than 2. -const MIN_QUEUE_SIZE_BLK: u16 = 2; -// Max size of each virtqueue for virtio-blk. -const MAX_QUEUE_SIZE_BLK: u16 = 1024; - // L2 Cache max size is 32M. pub const MAX_L2_CACHE_SIZE: u64 = 32 * (1 << 20); // Refcount table cache max size is 32M. @@ -60,29 +52,6 @@ pub struct DriveFile { pub buf_align: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct BlkDevConfig { - pub id: String, - pub path_on_host: String, - pub read_only: bool, - pub direct: bool, - pub serial_num: Option, - pub iothread: Option, - pub iops: Option, - pub queues: u16, - pub boot_index: Option, - pub chardev: Option, - pub socket_path: Option, - pub aio: AioEngine, - pub queue_size: u16, - pub discard: bool, - pub write_zeroes: WriteZeroesState, - pub format: DiskFormat, - pub l2_cache_size: Option, - pub refcount_cache_size: Option, -} - #[derive(Debug, Clone)] pub struct BootIndexInfo { pub boot_index: u8, @@ -90,33 +59,9 @@ pub struct BootIndexInfo { pub dev_path: String, } -impl Default for BlkDevConfig { - fn default() -> Self { - BlkDevConfig { - id: "".to_string(), - path_on_host: "".to_string(), - read_only: false, - direct: true, - serial_num: None, - iothread: None, - iops: None, - queues: 1, - boot_index: None, - chardev: None, - socket_path: None, - aio: AioEngine::Native, - queue_size: DEFAULT_VIRTQUEUE_SIZE, - discard: false, - write_zeroes: WriteZeroesState::Off, - format: DiskFormat::Raw, - l2_cache_size: None, - refcount_cache_size: None, - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum DiskFormat { + #[default] Raw, Qcow2, } @@ -142,44 +87,73 @@ impl ToString for DiskFormat { } } -/// Config struct for `drive`. -/// Contains block device's attr. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] +fn valid_l2_cache_size(s: &str) -> Result { + let size = parse_size(s)?; + if size > MAX_L2_CACHE_SIZE { + return Err(anyhow!(ConfigError::IllegalValue( + "l2-cache-size".to_string(), + 0, + true, + MAX_L2_CACHE_SIZE, + true + ))); + } + Ok(size) +} + +fn valid_refcount_cache_size(s: &str) -> Result { + let size = parse_size(s)?; + if size > MAX_REFTABLE_CACHE_SIZE { + return Err(anyhow!(ConfigError::IllegalValue( + "refcount-cache-size".to_string(), + 0, + true, + MAX_REFTABLE_CACHE_SIZE, + true + ))); + } + Ok(size) +} + +/// Config struct for `drive`, including `block drive` and `pflash drive`. +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct DriveConfig { + #[arg(long, default_value = "")] pub id: String, + #[arg(long, alias = "if", default_value = "none", value_parser = ["none", "pflash"])] + pub drive_type: String, + #[arg(long, value_parser = clap::value_parser!(u8).range(..MAX_UNIT_ID as i64))] + pub unit: Option, + #[arg(long, alias = "file", value_parser = valid_path)] pub path_on_host: String, - pub read_only: bool, + #[arg(long, default_value = "off", value_parser = parse_bool, action = ArgAction::Append)] + pub readonly: bool, + #[arg(long, default_value = "true", value_parser = parse_bool, action = ArgAction::Append)] pub direct: bool, + #[arg(long, alias = "throttling.iops-total", value_parser = clap::value_parser!(u64).range(..=MAX_IOPS as u64))] pub iops: Option, + #[arg( + long, + default_value = "native", + default_value_if("direct", "false", "off"), + default_value_if("direct", "off", "off") + )] pub aio: AioEngine, + #[arg(long, default_value = "disk", value_parser = ["disk", "cdrom"])] pub media: String, + #[arg(long, default_value = "ignore", value_parser = parse_bool, action = ArgAction::Append)] pub discard: bool, + #[arg(long, alias = "detect-zeroes", default_value = "off")] pub write_zeroes: WriteZeroesState, + #[arg(long, default_value = "raw")] pub format: DiskFormat, + #[arg(long, value_parser = valid_l2_cache_size)] pub l2_cache_size: Option, + #[arg(long, value_parser = valid_refcount_cache_size)] pub refcount_cache_size: Option, } -impl Default for DriveConfig { - fn default() -> Self { - DriveConfig { - id: "".to_string(), - path_on_host: "".to_string(), - read_only: false, - direct: true, - iops: None, - aio: AioEngine::Native, - media: "disk".to_string(), - discard: false, - write_zeroes: WriteZeroesState::Off, - format: DiskFormat::Raw, - l2_cache_size: None, - refcount_cache_size: None, - } - } -} - impl DriveConfig { /// Check whether the drive file path on the host is valid. pub fn check_path(&self) -> Result<()> { @@ -222,383 +196,90 @@ impl DriveConfig { impl ConfigCheck for DriveConfig { fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "Drive id")?; + if self.drive_type == "pflash" { + self.unit.with_context(|| { + ConfigError::FieldIsMissing("unit".to_string(), "pflash".to_string()) + })?; + if self.format.to_string() != "raw" { + bail!("Only \'raw\' type of pflash is supported"); + } + } else { + if self.id.is_empty() { + return Err(anyhow!(ConfigError::FieldIsMissing( + "id".to_string(), + "blk".to_string() + ))); + } + valid_id(&self.id)?; + valid_path(&self.path_on_host)?; + if self.iops > Some(MAX_IOPS) { + return Err(anyhow!(ConfigError::IllegalValue( + "iops of block device".to_string(), + 0, + true, + MAX_IOPS, + true, + ))); + } + if self.l2_cache_size > Some(MAX_L2_CACHE_SIZE) { + return Err(anyhow!(ConfigError::IllegalValue( + "l2-cache-size".to_string(), + 0, + true, + MAX_L2_CACHE_SIZE, + true + ))); + } + if self.refcount_cache_size > Some(MAX_REFTABLE_CACHE_SIZE) { + return Err(anyhow!(ConfigError::IllegalValue( + "refcount-cache-size".to_string(), + 0, + true, + MAX_REFTABLE_CACHE_SIZE, + true + ))); + } - if self.path_on_host.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "Drive device path".to_string(), - MAX_PATH_LENGTH, - ))); - } - if self.iops > Some(MAX_IOPS) { - return Err(anyhow!(ConfigError::IllegalValue( - "iops of block device".to_string(), - 0, - true, - MAX_IOPS, - true, - ))); - } - if self.aio != AioEngine::Off { - if self.aio == AioEngine::Native && !self.direct { + if self.aio != AioEngine::Off { + if self.aio == AioEngine::Native && !self.direct { + return Err(anyhow!(ConfigError::InvalidParam( + "aio".to_string(), + "native aio type should be used with \"direct\" on".to_string(), + ))); + } + aio_probe(self.aio)?; + } else if self.direct { return Err(anyhow!(ConfigError::InvalidParam( "aio".to_string(), - "native aio type should be used with \"direct\" on".to_string(), + "low performance expected when use sync io with \"direct\" on".to_string(), ))); } - aio_probe(self.aio)?; - } else if self.direct { - return Err(anyhow!(ConfigError::InvalidParam( - "aio".to_string(), - "low performance expected when use sync io with \"direct\" on".to_string(), - ))); - } - - if !["disk", "cdrom"].contains(&self.media.as_str()) { - return Err(anyhow!(ConfigError::InvalidParam( - "media".to_string(), - "media should be \"disk\" or \"cdrom\"".to_string(), - ))); - } - - if self.l2_cache_size > Some(MAX_L2_CACHE_SIZE) { - return Err(anyhow!(ConfigError::IllegalValue( - "l2-cache-size".to_string(), - 0, - true, - MAX_L2_CACHE_SIZE, - true - ))); - } - if self.refcount_cache_size > Some(MAX_REFTABLE_CACHE_SIZE) { - return Err(anyhow!(ConfigError::IllegalValue( - "refcount-cache-size".to_string(), - 0, - true, - MAX_REFTABLE_CACHE_SIZE, - true - ))); } - Ok(()) - } -} - -impl ConfigCheck for BlkDevConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "drive device id")?; - if self.serial_num.is_some() && self.serial_num.as_ref().unwrap().len() > MAX_SERIAL_NUM { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "drive serial number".to_string(), - MAX_SERIAL_NUM, - ))); - } - - if self.iothread.is_some() && self.iothread.as_ref().unwrap().len() > MAX_STRING_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "iothread name".to_string(), - MAX_STRING_LENGTH, - ))); - } - - if self.queues < 1 || self.queues > MAX_VIRTIO_QUEUE as u16 { - return Err(anyhow!(ConfigError::IllegalValue( - "number queues of block device".to_string(), - 1, - true, - MAX_VIRTIO_QUEUE as u64, - true, - ))); - } - - if self.queue_size <= MIN_QUEUE_SIZE_BLK || self.queue_size > MAX_QUEUE_SIZE_BLK { - return Err(anyhow!(ConfigError::IllegalValue( - "queue size of block device".to_string(), - MIN_QUEUE_SIZE_BLK as u64, - false, - MAX_QUEUE_SIZE_BLK as u64, - true - ))); - } - - if self.queue_size & (self.queue_size - 1) != 0 { - bail!("Queue size should be power of 2!"); - } - - let fake_drive = DriveConfig { - path_on_host: self.path_on_host.clone(), - direct: self.direct, - iops: self.iops, - aio: self.aio, - ..Default::default() - }; - fake_drive.check()?; #[cfg(not(test))] - if self.chardev.is_none() { - fake_drive.check_path()?; - } - - Ok(()) - } -} - -fn parse_drive(cmd_parser: CmdParser) -> Result { - let mut drive = DriveConfig::default(); - if let Some(fmt) = cmd_parser.get_value::("format")? { - drive.format = fmt; - } - - drive.id = cmd_parser - .get_value::("id")? - .with_context(|| ConfigError::FieldIsMissing("id".to_string(), "blk".to_string()))?; - drive.path_on_host = cmd_parser - .get_value::("file")? - .with_context(|| ConfigError::FieldIsMissing("file".to_string(), "blk".to_string()))?; - - if let Some(read_only) = cmd_parser.get_value::("readonly")? { - drive.read_only = read_only.into(); - } - if let Some(direct) = cmd_parser.get_value::("direct")? { - drive.direct = direct.into(); - } - drive.iops = cmd_parser.get_value::("throttling.iops-total")?; - drive.aio = cmd_parser.get_value::("aio")?.unwrap_or({ - if drive.direct { - AioEngine::Native - } else { - AioEngine::Off - } - }); - drive.media = cmd_parser - .get_value::("media")? - .unwrap_or_else(|| "disk".to_string()); - if let Some(discard) = cmd_parser.get_value::("discard")? { - drive.discard = discard.into(); - } - drive.write_zeroes = cmd_parser - .get_value::("detect-zeroes")? - .unwrap_or(WriteZeroesState::Off); - - if let Some(l2_cache) = cmd_parser.get_value::("l2-cache-size")? { - let sz = memory_unit_conversion(&l2_cache, M) - .with_context(|| format!("Invalid l2 cache size: {}", l2_cache))?; - drive.l2_cache_size = Some(sz); - } - if let Some(rc_cache) = cmd_parser.get_value::("refcount-cache-size")? { - let sz = memory_unit_conversion(&rc_cache, M) - .with_context(|| format!("Invalid refcount cache size: {}", rc_cache))?; - drive.refcount_cache_size = Some(sz); - } - - drive.check()?; - #[cfg(not(test))] - drive.check_path()?; - Ok(drive) -} - -pub fn parse_blk( - vm_config: &mut VmConfig, - drive_config: &str, - queues_auto: Option, -) -> Result { - let mut cmd_parser = CmdParser::new("virtio-blk"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("multifunction") - .push("drive") - .push("bootindex") - .push("serial") - .push("iothread") - .push("num-queues") - .push("queue-size"); - - cmd_parser.parse(drive_config)?; - - pci_args_check(&cmd_parser)?; - - let mut blkdevcfg = BlkDevConfig::default(); - if let Some(boot_index) = cmd_parser.get_value::("bootindex")? { - blkdevcfg.boot_index = Some(boot_index); - } - - let blkdrive = cmd_parser - .get_value::("drive")? - .with_context(|| ConfigError::FieldIsMissing("drive".to_string(), "blk".to_string()))?; - - if let Some(iothread) = cmd_parser.get_value::("iothread")? { - blkdevcfg.iothread = Some(iothread); - } - - if let Some(serial) = cmd_parser.get_value::("serial")? { - blkdevcfg.serial_num = Some(serial); - } - - blkdevcfg.id = cmd_parser - .get_value::("id")? - .with_context(|| "No id configured for blk device")?; - - if let Some(queues) = cmd_parser.get_value::("num-queues")? { - blkdevcfg.queues = queues; - } else if let Some(queues) = queues_auto { - blkdevcfg.queues = queues; - } - - if let Some(queue_size) = cmd_parser.get_value::("queue-size")? { - blkdevcfg.queue_size = queue_size; - } - - let drive_arg = &vm_config - .drives - .remove(&blkdrive) - .with_context(|| "No drive configured matched for blk device")?; - blkdevcfg.path_on_host = drive_arg.path_on_host.clone(); - blkdevcfg.read_only = drive_arg.read_only; - blkdevcfg.direct = drive_arg.direct; - blkdevcfg.iops = drive_arg.iops; - blkdevcfg.aio = drive_arg.aio; - blkdevcfg.discard = drive_arg.discard; - blkdevcfg.write_zeroes = drive_arg.write_zeroes; - blkdevcfg.format = drive_arg.format; - blkdevcfg.l2_cache_size = drive_arg.l2_cache_size; - blkdevcfg.refcount_cache_size = drive_arg.refcount_cache_size; - blkdevcfg.check()?; - Ok(blkdevcfg) -} - -pub fn parse_vhost_user_blk( - vm_config: &mut VmConfig, - drive_config: &str, - queues_auto: Option, -) -> Result { - let mut cmd_parser = CmdParser::new("vhost-user-blk-pci"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("num-queues") - .push("chardev") - .push("queue-size") - .push("bootindex"); - - cmd_parser.parse(drive_config)?; - - pci_args_check(&cmd_parser)?; - - let mut blkdevcfg = BlkDevConfig::default(); - - if let Some(boot_index) = cmd_parser.get_value::("bootindex")? { - blkdevcfg.boot_index = Some(boot_index); - } - - blkdevcfg.chardev = cmd_parser - .get_value::("chardev")? - .map(Some) - .with_context(|| { - ConfigError::FieldIsMissing("chardev".to_string(), "vhost-user-blk-pci".to_string()) - })?; - - blkdevcfg.id = cmd_parser - .get_value::("id")? - .with_context(|| "No id configured for blk device")?; - - if let Some(queues) = cmd_parser.get_value::("num-queues")? { - blkdevcfg.queues = queues; - } else if let Some(queues) = queues_auto { - blkdevcfg.queues = queues; - } + self.check_path()?; - if let Some(size) = cmd_parser.get_value::("queue-size")? { - blkdevcfg.queue_size = size; - } - - if let Some(chardev) = &blkdevcfg.chardev { - blkdevcfg.socket_path = Some(get_chardev_socket_path(chardev, vm_config)?); - } - blkdevcfg.check()?; - Ok(blkdevcfg) -} - -/// Config struct for `pflash`. -/// Contains pflash device's attr. -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -#[serde(deny_unknown_fields)] -pub struct PFlashConfig { - pub path_on_host: String, - pub read_only: bool, - pub unit: usize, -} - -impl ConfigCheck for PFlashConfig { - fn check(&self) -> Result<()> { - if self.path_on_host.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "drive device path".to_string(), - MAX_PATH_LENGTH, - ))); - } - - if self.unit >= MAX_UNIT_ID { - return Err(anyhow!(ConfigError::UnitIdError( - "PFlash unit id".to_string(), - self.unit, - MAX_UNIT_ID - 1 - ))); - } Ok(()) } } impl VmConfig { - /// Add '-drive ...' drive config to `VmConfig`. - pub fn add_drive(&mut self, drive_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("drive"); - cmd_parser.push("if"); - - cmd_parser.get_parameters(drive_config)?; - let drive_type = cmd_parser - .get_value::("if")? - .unwrap_or_else(|| "none".to_string()); - match drive_type.as_str() { + /// Add '-drive ...' drive config to `VmConfig`, including `block drive` and `pflash drive`. + pub fn add_drive(&mut self, drive_config: &str) -> Result { + let drive_cfg = DriveConfig::try_parse_from(str_slip_to_clap(drive_config, false, false))?; + drive_cfg.check()?; + match drive_cfg.drive_type.as_str() { "none" => { - self.add_block_drive(drive_config)?; + self.add_drive_with_config(drive_cfg.clone())?; } "pflash" => { - self.add_pflash(drive_config)?; + self.add_flashdev(drive_cfg.clone())?; } _ => { - bail!("Unknow 'if' argument: {:?}", drive_type.as_str()); + bail!("Unknow 'if' argument: {:?}", &drive_cfg.drive_type); } } - Ok(()) - } - - /// Add block drive config to vm and return the added drive config. - pub fn add_block_drive(&mut self, block_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("drive"); - cmd_parser - .push("file") - .push("id") - .push("readonly") - .push("direct") - .push("format") - .push("if") - .push("throttling.iops-total") - .push("aio") - .push("media") - .push("discard") - .push("detect-zeroes") - .push("format") - .push("l2-cache-size") - .push("refcount-cache-size"); - - cmd_parser.parse(block_config)?; - let drive_cfg = parse_drive(cmd_parser)?; - self.add_drive_with_config(drive_cfg.clone())?; Ok(drive_cfg) } @@ -609,11 +290,10 @@ impl VmConfig { /// * `drive_conf` - The drive config to be added to the vm. pub fn add_drive_with_config(&mut self, drive_conf: DriveConfig) -> Result<()> { let drive_id = drive_conf.id.clone(); - if self.drives.get(&drive_id).is_none() { - self.drives.insert(drive_id, drive_conf); - } else { + if self.drives.get(&drive_id).is_some() { bail!("Drive {} has been added", drive_id); } + self.drives.insert(drive_id, drive_conf); Ok(()) } @@ -631,13 +311,13 @@ impl VmConfig { } /// Add new flash device to `VmConfig`. - fn add_flashdev(&mut self, pflash: PFlashConfig) -> Result<()> { + fn add_flashdev(&mut self, pflash: DriveConfig) -> Result<()> { if self.pflashs.is_some() { for pf in self.pflashs.as_ref().unwrap() { - if pf.unit == pflash.unit { + if pf.unit.unwrap() == pflash.unit.unwrap() { return Err(anyhow!(ConfigError::IdRepeat( "pflash".to_string(), - pf.unit.to_string() + pf.unit.unwrap().to_string() ))); } } @@ -647,147 +327,38 @@ impl VmConfig { } Ok(()) } - - /// Add '-pflash ...' pflash config to `VmConfig`. - pub fn add_pflash(&mut self, pflash_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("pflash"); - cmd_parser - .push("if") - .push("file") - .push("format") - .push("readonly") - .push("unit"); - - cmd_parser.parse(pflash_config)?; - - let mut pflash = PFlashConfig::default(); - - if let Some(format) = cmd_parser.get_value::("format")? { - if format.ne("raw") { - bail!("Only \'raw\' type of pflash is supported"); - } - } - pflash.path_on_host = cmd_parser.get_value::("file")?.with_context(|| { - ConfigError::FieldIsMissing("file".to_string(), "pflash".to_string()) - })?; - - if let Some(read_only) = cmd_parser.get_value::("readonly")? { - pflash.read_only = read_only.into(); - } - - pflash.unit = cmd_parser.get_value::("unit")?.with_context(|| { - ConfigError::FieldIsMissing("unit".to_string(), "pflash".to_string()) - })? as usize; - - pflash.check()?; - self.add_flashdev(pflash) - } } #[cfg(test)] mod tests { use super::*; - use crate::config::get_pci_bdf; #[test] - fn test_drive_config_cmdline_parser() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_drive( - "id=rootfs,file=/path/to/rootfs,readonly=off,direct=on,throttling.iops-total=200" - ) - .is_ok()); - let blk_cfg_res = parse_blk( - &mut vm_config, - "virtio-blk-device,drive=rootfs,id=rootfs,iothread=iothread1,serial=111111,num-queues=4", - None, - ); - assert!(blk_cfg_res.is_ok()); - let blk_device_config = blk_cfg_res.unwrap(); - assert_eq!(blk_device_config.id, "rootfs"); - assert_eq!(blk_device_config.path_on_host, "/path/to/rootfs"); - assert_eq!(blk_device_config.direct, true); - assert_eq!(blk_device_config.read_only, false); - assert_eq!(blk_device_config.serial_num, Some(String::from("111111"))); - assert_eq!(blk_device_config.queues, 4); - + fn test_pflash_drive_config_cmdline_parser() { + // Test1: Right. let mut vm_config = VmConfig::default(); assert!(vm_config - .add_drive("id=rootfs,file=/path/to/rootfs,readonly=off,direct=on") - .is_ok()); - let blk_cfg_res = parse_blk( - &mut vm_config, - "virtio-blk-device,drive=rootfs1,id=rootfs1,iothread=iothread1,iops=200,serial=111111", - None, - ); - assert!(blk_cfg_res.is_err()); // Can not find drive named "rootfs1". - } - - #[test] - fn test_pci_block_config_cmdline_parser() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_drive("id=rootfs,file=/path/to/rootfs,readonly=off,direct=on") - .is_ok()); - let blk_cfg = "virtio-blk-pci,id=rootfs,bus=pcie.0,addr=0x1.0x2,drive=rootfs,serial=111111,num-queues=4"; - let blk_cfg_res = parse_blk(&mut vm_config, blk_cfg, None); - assert!(blk_cfg_res.is_ok()); - let drive_configs = blk_cfg_res.unwrap(); - assert_eq!(drive_configs.id, "rootfs"); - assert_eq!(drive_configs.path_on_host, "/path/to/rootfs"); - assert_eq!(drive_configs.direct, true); - assert_eq!(drive_configs.read_only, false); - assert_eq!(drive_configs.serial_num, Some(String::from("111111"))); - assert_eq!(drive_configs.queues, 4); - - let pci_bdf = get_pci_bdf(blk_cfg); - assert!(pci_bdf.is_ok()); - let pci = pci_bdf.unwrap(); - assert_eq!(pci.bus, "pcie.0".to_string()); - assert_eq!(pci.addr, (1, 2)); - - // drive "rootfs" has been removed. - let blk_cfg_res = parse_blk(&mut vm_config, blk_cfg, None); - assert!(blk_cfg_res.is_err()); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_drive("id=rootfs,file=/path/to/rootfs,readonly=off,direct=on") - .is_ok()); - let blk_cfg = - "virtio-blk-pci,id=blk1,bus=pcie.0,addr=0x1.0x2,drive=rootfs,multifunction=on"; - assert!(parse_blk(&mut vm_config, blk_cfg, None).is_ok()); - } - - #[test] - fn test_pflash_config_cmdline_parser() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=0") + .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=0,format=raw") .is_ok()); assert!(vm_config.pflashs.is_some()); let pflash = vm_config.pflashs.unwrap(); assert!(pflash.len() == 1); let pflash_cfg = &pflash[0]; - assert_eq!(pflash_cfg.unit, 0); + assert_eq!(pflash_cfg.unit.unwrap(), 0); assert_eq!(pflash_cfg.path_on_host, "flash0.fd".to_string()); - assert_eq!(pflash_cfg.read_only, true); + assert_eq!(pflash_cfg.readonly, true); + // Test2: Change parameters sequence. let mut vm_config = VmConfig::default(); assert!(vm_config .add_drive("readonly=on,file=flash0.fd,unit=0,if=pflash") .is_ok()); - let mut vm_config = VmConfig::default(); assert!(vm_config .add_drive("readonly=on,if=pflash,file=flash0.fd,unit=0") .is_ok()); - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=2") - .is_err()); - + // Test3: Add duplicate pflash. let mut vm_config = VmConfig::default(); assert!(vm_config .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=0") @@ -795,52 +366,103 @@ mod tests { assert!(vm_config .add_drive("if=pflash,file=flash1.fd,unit=1") .is_ok()); + assert!(vm_config + .add_drive("if=pflash,file=flash1.fd,unit=1") + .is_err()); assert!(vm_config.pflashs.is_some()); let pflash = vm_config.pflashs.unwrap(); assert!(pflash.len() == 2); let pflash_cfg = &pflash[0]; - assert_eq!(pflash_cfg.unit, 0); + assert_eq!(pflash_cfg.unit.unwrap(), 0); assert_eq!(pflash_cfg.path_on_host, "flash0.fd".to_string()); - assert_eq!(pflash_cfg.read_only, true); + assert_eq!(pflash_cfg.readonly, true); let pflash_cfg = &pflash[1]; - assert_eq!(pflash_cfg.unit, 1); + assert_eq!(pflash_cfg.unit.unwrap(), 1); assert_eq!(pflash_cfg.path_on_host, "flash1.fd".to_string()); - assert_eq!(pflash_cfg.read_only, false); - } + assert_eq!(pflash_cfg.readonly, false); - #[test] - fn test_drive_config_check() { - let mut drive_conf = DriveConfig::default(); - for _ in 0..MAX_STRING_LENGTH { - drive_conf.id += "A"; - } - assert!(drive_conf.check().is_ok()); + // Test4: Illegal parameters unit/format. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=2") + .is_err()); + assert!(vm_config + .add_drive("if=pflash,readonly=on,file=flash0.fd,unit=0,format=qcow2") + .is_err()); - // Overflow - drive_conf.id += "A"; - assert!(drive_conf.check().is_err()); + // Test5: Missing parameters file/unit. + let mut vm_config = VmConfig::default(); + assert!(vm_config.add_drive("if=pflash,readonly=on,unit=2").is_err()); + assert!(vm_config + .add_drive("if=pflash,readonly=on,file=flash0.fd") + .is_err()); + } - let mut drive_conf = DriveConfig::default(); - for _ in 0..MAX_PATH_LENGTH { - drive_conf.path_on_host += "A"; - } - assert!(drive_conf.check().is_ok()); + #[test] + fn test_block_drive_config_cmdline_parser() { + // Test1: Right. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=qcow2,readonly=off,direct=on,throttling.iops-total=200,discard=unmap,detect-zeroes=unmap") + .is_ok()); + assert!(vm_config.drives.len() == 1); + let drive_cfg = &vm_config.drives.remove("rootfs").unwrap(); + + assert_eq!(drive_cfg.id, "rootfs"); + assert_eq!(drive_cfg.path_on_host, "/path/to/rootfs"); + assert_eq!(drive_cfg.format.to_string(), "qcow2"); + assert_eq!(drive_cfg.readonly, false); + assert_eq!(drive_cfg.direct, true); + assert_eq!(drive_cfg.iops.unwrap(), 200); + assert_eq!(drive_cfg.discard, true); + assert_eq!( + drive_cfg.write_zeroes, + WriteZeroesState::from_str("unmap").unwrap() + ); - // Overflow - drive_conf.path_on_host += "A"; - assert!(drive_conf.check().is_err()); + // Test2: Change parameters sequence. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("throttling.iops-total=200,file=/path/to/rootfs,format=qcow2,id=rootfs,readonly=off,direct=on,discard=unmap,detect-zeroes=unmap") + .is_ok()); - let mut drive_conf = DriveConfig::default(); - drive_conf.iops = Some(MAX_IOPS); - assert!(drive_conf.check().is_ok()); + // Test3: Add duplicate block drive config. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=qcow2,readonly=off,direct=on") + .is_ok()); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=qcow2,readonly=off,direct=on") + .is_err()); + let drive_cfg = &vm_config.drives.remove("rootfs"); + assert!(drive_cfg.is_some()); - let mut drive_conf = DriveConfig::default(); - drive_conf.iops = None; - assert!(drive_conf.check().is_ok()); + // Test4: Illegal parameters. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=vhdx") + .is_err()); + assert!(vm_config + .add_drive("id=rootfs,if=illegal,file=/path/to/rootfs,format=vhdx") + .is_err()); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=raw,throttling.iops-total=1000001") + .is_err()); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=raw,media=illegal") + .is_err()); + assert!(vm_config + .add_drive("id=rootfs,file=/path/to/rootfs,format=raw,detect-zeroes=illegal") + .is_err()); - // Overflow - drive_conf.iops = Some(MAX_IOPS + 1); - assert!(drive_conf.check().is_err()); + // Test5: Missing parameters id/file. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_drive("file=/path/to/rootfs,format=qcow2,readonly=off,direct=on,throttling.iops-total=200") + .is_err()); + assert!(vm_config + .add_drive("id=rootfs,format=qcow2,readonly=off,direct=on,throttling.iops-total=200") + .is_err()); } #[test] @@ -888,19 +510,19 @@ mod tests { fn test_drive_config_discard() { let mut vm_config = VmConfig::default(); let drive_conf = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,discard=ignore") + .add_drive("id=rootfs,file=/path/to/rootfs,discard=ignore") .unwrap(); assert_eq!(drive_conf.discard, false); let mut vm_config = VmConfig::default(); let drive_conf = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,discard=unmap") + .add_drive("id=rootfs,file=/path/to/rootfs,discard=unmap") .unwrap(); assert_eq!(drive_conf.discard, true); let mut vm_config = VmConfig::default(); let ret = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,discard=invalid") + .add_drive("id=rootfs,file=/path/to/rootfs,discard=invalid") .is_err(); assert_eq!(ret, true); } @@ -909,25 +531,25 @@ mod tests { fn test_drive_config_write_zeroes() { let mut vm_config = VmConfig::default(); let drive_conf = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=off") + .add_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=off") .unwrap(); assert_eq!(drive_conf.write_zeroes, WriteZeroesState::Off); let mut vm_config = VmConfig::default(); let drive_conf = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=on") + .add_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=on") .unwrap(); assert_eq!(drive_conf.write_zeroes, WriteZeroesState::On); let mut vm_config = VmConfig::default(); let drive_conf = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=unmap") + .add_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=unmap") .unwrap(); assert_eq!(drive_conf.write_zeroes, WriteZeroesState::Unmap); let mut vm_config = VmConfig::default(); let ret = vm_config - .add_block_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=invalid") + .add_drive("id=rootfs,file=/path/to/rootfs,detect-zeroes=invalid") .is_err(); assert_eq!(ret, true); } diff --git a/machine_manager/src/config/fs.rs b/machine_manager/src/config/fs.rs deleted file mode 100644 index e1a16ab..0000000 --- a/machine_manager/src/config/fs.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{anyhow, bail, Context, Result}; - -use super::error::ConfigError; -use crate::config::{ - pci_args_check, ChardevType, CmdParser, ConfigCheck, VmConfig, MAX_SOCK_PATH_LENGTH, - MAX_STRING_LENGTH, MAX_TAG_LENGTH, -}; - -/// Config struct for `fs`. -/// Contains fs device's attr. -#[derive(Debug, Clone)] -pub struct FsConfig { - /// Device tag. - pub tag: String, - /// Device id. - pub id: String, - /// Char device sock path. - pub sock: String, -} - -impl Default for FsConfig { - fn default() -> Self { - FsConfig { - tag: "".to_string(), - id: "".to_string(), - sock: "".to_string(), - } - } -} - -impl ConfigCheck for FsConfig { - fn check(&self) -> Result<()> { - if self.tag.len() >= MAX_TAG_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "fs device tag".to_string(), - MAX_TAG_LENGTH - 1, - ))); - } - - if self.id.len() >= MAX_STRING_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "fs device id".to_string(), - MAX_STRING_LENGTH - 1, - ))); - } - - if self.sock.len() > MAX_SOCK_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "fs sock path".to_string(), - MAX_SOCK_PATH_LENGTH, - ))); - } - - Ok(()) - } -} - -pub fn parse_fs(vm_config: &mut VmConfig, fs_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("fs"); - cmd_parser - .push("") - .push("tag") - .push("id") - .push("chardev") - .push("bus") - .push("addr") - .push("multifunction"); - cmd_parser.parse(fs_config)?; - pci_args_check(&cmd_parser)?; - - let mut fs_cfg = FsConfig { - tag: cmd_parser.get_value::("tag")?.with_context(|| { - ConfigError::FieldIsMissing("tag".to_string(), "virtio-fs".to_string()) - })?, - id: cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "virtio-fs".to_string()) - })?, - ..Default::default() - }; - - if let Some(name) = cmd_parser.get_value::("chardev")? { - if let Some(char_dev) = vm_config.chardev.remove(&name) { - match &char_dev.backend { - ChardevType::UnixSocket { path, .. } => { - fs_cfg.sock = path.clone(); - } - _ => { - bail!("Chardev {:?} backend should be unix-socket type.", &name); - } - } - } else { - bail!("Chardev {:?} not found or is in use", &name); - } - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "chardev".to_string(), - "virtio-fs".to_string() - ))); - } - fs_cfg.check()?; - - Ok(fs_cfg) -} diff --git a/machine_manager/src/config/gpu.rs b/machine_manager/src/config/gpu.rs deleted file mode 100644 index 56ab284..0000000 --- a/machine_manager/src/config/gpu.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{anyhow, Result}; -use log::warn; - -use super::{error::ConfigError, M}; -use crate::config::{check_arg_too_long, CmdParser, ConfigCheck}; - -/// The maximum number of outputs. -pub const VIRTIO_GPU_MAX_OUTPUTS: usize = 16; - -pub const VIRTIO_GPU_MAX_HOSTMEM: u64 = 256 * M; - -/// The bar0 size of enable_bar0 features -pub const VIRTIO_GPU_ENABLE_BAR0_SIZE: u64 = 64 * M; - -#[derive(Clone, Debug)] -pub struct GpuDevConfig { - pub id: String, - pub max_outputs: u32, - pub edid: bool, - pub xres: u32, - pub yres: u32, - pub max_hostmem: u64, - pub enable_bar0: bool, -} - -impl Default for GpuDevConfig { - fn default() -> Self { - GpuDevConfig { - id: "".to_string(), - max_outputs: 1, - edid: true, - xres: 1024, - yres: 768, - max_hostmem: VIRTIO_GPU_MAX_HOSTMEM, - enable_bar0: false, - } - } -} - -impl ConfigCheck for GpuDevConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "id")?; - if self.max_outputs > VIRTIO_GPU_MAX_OUTPUTS as u32 || self.max_outputs == 0 { - return Err(anyhow!(ConfigError::IllegalValue( - "max_outputs".to_string(), - 0, - false, - VIRTIO_GPU_MAX_OUTPUTS as u64, - true - ))); - } - - if self.max_hostmem == 0 { - return Err(anyhow!(ConfigError::IllegalValueUnilateral( - "max_hostmem".to_string(), - true, - false, - 0 - ))); - } - - if self.max_hostmem < VIRTIO_GPU_MAX_HOSTMEM { - warn!( - "max_hostmem should >= {}, allocating less than it may cause \ - the GPU to fail to start or refresh.", - VIRTIO_GPU_MAX_HOSTMEM - ); - } - - Ok(()) - } -} - -pub fn parse_gpu(gpu_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("virtio-gpu-pci"); - cmd_parser - .push("") - .push("id") - .push("max_outputs") - .push("edid") - .push("xres") - .push("yres") - .push("max_hostmem") - .push("bus") - .push("addr") - .push("enable_bar0"); - cmd_parser.parse(gpu_config)?; - - let mut gpu_cfg: GpuDevConfig = GpuDevConfig::default(); - if let Some(id) = cmd_parser.get_value::("id")? { - gpu_cfg.id = id; - } - if let Some(max_outputs) = cmd_parser.get_value::("max_outputs")? { - gpu_cfg.max_outputs = max_outputs; - } - if let Some(edid) = cmd_parser.get_value::("edid")? { - gpu_cfg.edid = edid; - } - if let Some(xres) = cmd_parser.get_value::("xres")? { - gpu_cfg.xres = xres; - } - if let Some(yres) = cmd_parser.get_value::("yres")? { - gpu_cfg.yres = yres; - } - if let Some(max_hostmem) = cmd_parser.get_value::("max_hostmem")? { - gpu_cfg.max_hostmem = max_hostmem; - } - if let Some(enable_bar0) = cmd_parser.get_value::("enable_bar0")? { - gpu_cfg.enable_bar0 = enable_bar0; - } - gpu_cfg.check()?; - - Ok(gpu_cfg) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_pci_gpu_config_cmdline_parser() { - let max_hostmem = VIRTIO_GPU_MAX_HOSTMEM + 1; - let gpu_cfg_cmdline = format!( - "{}{}", - "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,\ - max_outputs=1,edid=true,xres=1024,yres=768,max_hostmem=", - max_hostmem.to_string() - ); - let gpu_cfg_ = parse_gpu(&gpu_cfg_cmdline); - assert!(gpu_cfg_.is_ok()); - let gpu_cfg = gpu_cfg_.unwrap(); - assert_eq!(gpu_cfg.id, "gpu_1"); - assert_eq!(gpu_cfg.max_outputs, 1); - assert_eq!(gpu_cfg.edid, true); - assert_eq!(gpu_cfg.xres, 1024); - assert_eq!(gpu_cfg.yres, 768); - assert_eq!(gpu_cfg.max_hostmem, max_hostmem); - - // max_outputs is illegal - let gpu_cfg_cmdline = format!( - "{}{}", - "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,\ - max_outputs=17,edid=true,xres=1024,yres=768,max_hostmem=", - max_hostmem.to_string() - ); - let gpu_cfg_ = parse_gpu(&gpu_cfg_cmdline); - assert!(gpu_cfg_.is_err()); - - let gpu_cfg_cmdline = format!( - "{}{}", - "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,\ - max_outputs=0,edid=true,xres=1024,yres=768,max_hostmem=", - max_hostmem.to_string() - ); - let gpu_cfg_ = parse_gpu(&gpu_cfg_cmdline); - assert!(gpu_cfg_.is_err()); - - // max_hostmem is illegal - let gpu_cfg_cmdline = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,\ - max_outputs=1,edid=true,xres=1024,yres=768,max_hostmem=0"; - let gpu_cfg_ = parse_gpu(&gpu_cfg_cmdline); - assert!(gpu_cfg_.is_err()); - } -} diff --git a/machine_manager/src/config/iothread.rs b/machine_manager/src/config/iothread.rs index ac3a0a9..029d658 100644 --- a/machine_manager/src/config/iothread.rs +++ b/machine_manager/src/config/iothread.rs @@ -11,37 +11,29 @@ // See the Mulan PSL v2 for more details. use anyhow::{anyhow, Result}; +use clap::Parser; use serde::{Deserialize, Serialize}; -use super::error::ConfigError; -use crate::config::{check_arg_too_long, CmdParser, ConfigCheck, VmConfig}; +use super::{error::ConfigError, str_slip_to_clap, valid_id}; +use crate::config::VmConfig; const MAX_IOTHREAD_NUM: usize = 8; /// Config structure for iothread. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct IothreadConfig { + #[arg(long, value_parser = ["iothread"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] pub id: String, } -impl ConfigCheck for IothreadConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "iothread id") - } -} - impl VmConfig { /// Add new iothread device to `VmConfig`. pub fn add_iothread(&mut self, iothread_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("iothread"); - cmd_parser.push("").push("id"); - cmd_parser.parse(iothread_config)?; - - let mut iothread = IothreadConfig::default(); - if let Some(id) = cmd_parser.get_value::("id")? { - iothread.id = id; - } - iothread.check()?; + let iothread = + IothreadConfig::try_parse_from(str_slip_to_clap(iothread_config, true, false))?; if self.iothreads.is_some() { if self.iothreads.as_ref().unwrap().len() >= MAX_IOTHREAD_NUM { diff --git a/machine_manager/src/config/machine_config.rs b/machine_manager/src/config/machine_config.rs index c1a6fb7..b50cc3a 100644 --- a/machine_manager/src/config/machine_config.rs +++ b/machine_manager/src/config/machine_config.rs @@ -13,13 +13,15 @@ use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser}; use serde::{Deserialize, Serialize}; use super::error::ConfigError; -use crate::config::{ - check_arg_too_long, check_path_too_long, CmdParser, ConfigCheck, ExBool, IntegerList, VmConfig, - MAX_NODES, +use super::{ + get_value_of_parameter, parse_bool, parse_size, str_slip_to_clap, valid_id, valid_path, }; +use crate::config::{ConfigCheck, IntegerList, VmConfig, MAX_NODES}; +use crate::machine::HypervisorType; const DEFAULT_CPUS: u8 = 1; const DEFAULT_THREADS: u8 = 1; @@ -41,11 +43,12 @@ pub const G: u64 = 1024 * 1024 * 1024; pub enum MachineType { None, MicroVm, + #[cfg(not(target_arch = "riscv64"))] StandardVm, } impl FromStr for MachineType { - type Err = (); + type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result { match s.to_lowercase().as_str() { @@ -55,7 +58,7 @@ impl FromStr for MachineType { "q35" => Ok(MachineType::StandardVm), #[cfg(target_arch = "aarch64")] "virt" => Ok(MachineType::StandardVm), - _ => Err(()), + _ => Err(anyhow!("Invalid machine type.")), } } } @@ -82,32 +85,37 @@ impl From for HostMemPolicy { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct MemZoneConfig { + #[arg(long, alias = "classtype", value_parser = ["memory-backend-ram", "memory-backend-file", "memory-backend-memfd"])] + pub mem_type: String, + #[arg(long, value_parser = valid_id)] pub id: String, + #[arg(long, value_parser = parse_size)] pub size: u64, - pub host_numa_nodes: Option>, + // Note: + // `Clap` will incorrectly assume that we're trying to get multiple arguments since we got + // a `Vec` from parser function `get_host_nodes`. Generally, we should use `Box` or a `new struct type` + // to encapsulate this `Vec`. And fortunately, there's a trick (using full qualified path of Vec) + // to avoid the new type wrapper. See: github.com/clap-rs/clap/issues/4626. + #[arg(long, alias = "host-nodes", value_parser = get_host_nodes)] + pub host_numa_nodes: Option<::std::vec::Vec>, + #[arg(long, default_value = "default", value_parser=["default", "preferred", "bind", "interleave"])] pub policy: String, + #[arg(long, value_parser = valid_path)] pub mem_path: Option, + #[arg(long, default_value = "true", value_parser = parse_bool, action = ArgAction::Append)] pub dump_guest_core: bool, + #[arg(long, default_value = "off", value_parser = parse_bool, action = ArgAction::Append)] pub share: bool, + #[arg(long, alias = "mem-prealloc", default_value = "false", value_parser = parse_bool, action = ArgAction::Append)] pub prealloc: bool, - pub memfd: bool, } -impl Default for MemZoneConfig { - fn default() -> Self { - MemZoneConfig { - id: String::new(), - size: 0, - host_numa_nodes: None, - policy: String::from("bind"), - mem_path: None, - dump_guest_core: true, - share: false, - prealloc: false, - memfd: false, - } +impl MemZoneConfig { + pub fn memfd(&self) -> bool { + self.mem_type.eq("memory-backend-memfd") } } @@ -135,9 +143,14 @@ impl Default for MachineMemConfig { } } -#[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[derive(Parser, Clone, Debug, Serialize, Deserialize, Default)] +#[command(no_binary_name(true))] pub struct CpuConfig { + #[arg(long, alias = "classtype", value_parser = ["host"])] + pub family: String, + #[arg(long, default_value = "off")] pub pmu: PmuConfig, + #[arg(long, default_value = "off")] pub sve: SveConfig, } @@ -148,6 +161,20 @@ pub enum PmuConfig { Off, } +impl FromStr for PmuConfig { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "on" => Ok(PmuConfig::On), + "off" => Ok(PmuConfig::Off), + _ => Err(anyhow!( + "Invalid PMU option,must be one of \'on\" or \"off\"." + )), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum SveConfig { On, @@ -155,6 +182,20 @@ pub enum SveConfig { Off, } +impl FromStr for SveConfig { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "on" => Ok(SveConfig::On), + "off" => Ok(SveConfig::Off), + _ => Err(anyhow!( + "Invalid SVE option, must be one of \"on\" or \"off\"." + )), + } + } +} + #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)] pub enum ShutdownAction { #[default] @@ -167,6 +208,7 @@ pub enum ShutdownAction { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MachineConfig { pub mach_type: MachineType, + pub hypervisor: HypervisorType, pub nr_cpus: u8, pub nr_threads: u8, pub nr_cores: u8, @@ -185,6 +227,7 @@ impl Default for MachineConfig { fn default() -> Self { MachineConfig { mach_type: MachineType::MicroVm, + hypervisor: HypervisorType::Kvm, nr_cpus: DEFAULT_CPUS, nr_threads: DEFAULT_THREADS, nr_cores: DEFAULT_CORES, @@ -211,222 +254,190 @@ impl ConfigCheck for MachineConfig { } } -impl VmConfig { - /// Add argument `name` to `VmConfig`. - /// - /// # Arguments - /// - /// * `name` - The name `String` added to `VmConfig`. - pub fn add_machine(&mut self, mach_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("machine"); - cmd_parser - .push("") - .push("type") - .push("accel") - .push("usb") - .push("dump-guest-core") - .push("mem-share"); - #[cfg(target_arch = "aarch64")] - cmd_parser.push("gic-version"); - cmd_parser.parse(mach_config)?; +#[derive(Parser)] +#[command(no_binary_name(true))] +struct AccelConfig { + #[arg(long, alias = "classtype")] + hypervisor: HypervisorType, +} - #[cfg(target_arch = "aarch64")] - if let Some(gic_version) = cmd_parser.get_value::("gic-version")? { - if gic_version != 3 { - bail!("Unsupported gic version, only gicv3 is supported"); +#[derive(Parser)] +#[command(no_binary_name(true))] +struct MemSizeConfig { + #[arg(long, alias = "classtype", value_parser = parse_size)] + size: u64, +} + +#[derive(Parser)] +#[command(no_binary_name(true))] +struct MachineCmdConfig { + #[arg(long, aliases = ["classtype", "type"])] + mach_type: MachineType, + #[arg(long, default_value = "on", action = ArgAction::Append, value_parser = parse_bool)] + dump_guest_core: bool, + #[arg(long, default_value = "off", action = ArgAction::Append, value_parser = parse_bool)] + mem_share: bool, + #[arg(long, default_value = "kvm")] + accel: HypervisorType, + // The "usb" member is added for compatibility with libvirt and is currently not in use. + // It only supports configuration as "off". Currently, a `String` type is used to verify incoming values. + // When it will be used, it needs to be changed to a `bool` type. + #[arg(long, default_value = "off", value_parser = ["off"])] + usb: String, + #[cfg(target_arch = "aarch64")] + #[arg(long, default_value = "3", value_parser = clap::value_parser!(u8).range(3..=3))] + gic_version: u8, +} + +#[derive(Parser)] +#[command(no_binary_name(true))] +struct SmpConfig { + #[arg(long, alias = "classtype", value_parser = clap::value_parser!(u64).range(MIN_NR_CPUS..=MAX_NR_CPUS))] + cpus: u64, + #[arg(long, default_value = "0")] + maxcpus: u64, + #[arg(long, default_value = "0", value_parser = clap::value_parser!(u64).range(..u8::MAX as u64))] + sockets: u64, + #[arg(long, default_value = "1", value_parser = clap::value_parser!(u64).range(1..u8::MAX as u64))] + dies: u64, + #[arg(long, default_value = "1", value_parser = clap::value_parser!(u64).range(1..u8::MAX as u64))] + clusters: u64, + #[arg(long, default_value = "0", value_parser = clap::value_parser!(u64).range(..u8::MAX as u64))] + cores: u64, + #[arg(long, default_value = "0", value_parser = clap::value_parser!(u64).range(..u8::MAX as u64))] + threads: u64, +} + +impl SmpConfig { + fn auto_adjust_topology(&mut self) -> Result<()> { + let mut max_cpus = self.maxcpus; + let mut sockets = self.sockets; + let mut cores = self.cores; + let mut threads = self.threads; + + if max_cpus == 0 { + if sockets * self.dies * self.clusters * cores * threads > 0 { + max_cpus = sockets * self.dies * self.clusters * cores * threads; + } else { + max_cpus = self.cpus; } } - if let Some(accel) = cmd_parser.get_value::("accel")? { - // Libvirt checks the parameter types of 'kvm', 'kvm:tcg' and 'tcg'. - if accel.ne("kvm:tcg") && accel.ne("tcg") && accel.ne("kvm") { - bail!("Only \'kvm\', \'kvm:tcg\' and \'tcg\' are supported for \'accel\' of \'machine\'"); + if cores == 0 { + if sockets == 0 { + sockets = 1; } - } - if let Some(usb) = cmd_parser.get_value::("usb")? { - if usb.into() { - bail!("Argument \'usb\' should be set to \'off\'"); + if threads == 0 { + threads = 1; + } + cores = max_cpus / (sockets * self.dies * self.clusters * threads); + } else if sockets == 0 { + if threads == 0 { + threads = 1; } + sockets = max_cpus / (self.dies * self.clusters * cores * threads); } - if let Some(mach_type) = cmd_parser - .get_value::("") - .with_context(|| "Unrecognized machine type")? - { - self.machine_config.mach_type = mach_type; + + if threads == 0 { + threads = max_cpus / (sockets * self.dies * self.clusters * cores); } - if let Some(mach_type) = cmd_parser - .get_value::("type") - .with_context(|| "Unrecognized machine type")? - { - self.machine_config.mach_type = mach_type; + + let min_max_cpus = std::cmp::max(self.cpus, MIN_NR_CPUS); + + if !(min_max_cpus..=MAX_NR_CPUS).contains(&max_cpus) { + return Err(anyhow!(ConfigError::IllegalValue( + "MAX CPU number".to_string(), + min_max_cpus, + true, + MAX_NR_CPUS, + true, + ))); } - if let Some(dump_guest) = cmd_parser.get_value::("dump-guest-core")? { - self.machine_config.mem_config.dump_guest_core = dump_guest.into(); + + if sockets * self.dies * self.clusters * cores * threads != max_cpus { + bail!("sockets * dies * clusters * cores * threads must be equal to max_cpus"); } - if let Some(mem_share) = cmd_parser.get_value::("mem-share")? { - self.machine_config.mem_config.mem_share = mem_share.into(); + + self.maxcpus = max_cpus; + self.sockets = sockets; + self.cores = cores; + self.threads = threads; + + Ok(()) + } +} + +impl VmConfig { + /// Add argument `name` to `VmConfig`. + /// + /// # Arguments + /// + /// * `name` - The name `String` added to `VmConfig`. + pub fn add_machine(&mut self, mach_config: &str) -> Result<()> { + let mut has_type_label = false; + if get_value_of_parameter("type", mach_config).is_ok() { + has_type_label = true; } + let mach_cfg = MachineCmdConfig::try_parse_from(str_slip_to_clap( + mach_config, + !has_type_label, + false, + ))?; + // TODO: The current "accel" configuration in "-machine" command line and "-accel" command line are not foolproof. + // Later parsing will overwrite first parsing. We will optimize this in the future. + self.machine_config.hypervisor = mach_cfg.accel; + self.machine_config.mach_type = mach_cfg.mach_type; + self.machine_config.mem_config.dump_guest_core = mach_cfg.dump_guest_core; + self.machine_config.mem_config.mem_share = mach_cfg.mem_share; Ok(()) } /// Add '-accel' accelerator config to `VmConfig`. pub fn add_accel(&mut self, accel_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("accel"); - cmd_parser.push(""); - cmd_parser.parse(accel_config)?; - - if let Some(accel) = cmd_parser.get_value::("")? { - if accel.ne("kvm") { - bail!("Only \'kvm\' is supported for \'accel\'"); - } - } - + let accel_cfg = AccelConfig::try_parse_from(str_slip_to_clap(accel_config, true, false))?; + self.machine_config.hypervisor = accel_cfg.hypervisor; Ok(()) } /// Add '-m' memory config to `VmConfig`. pub fn add_memory(&mut self, mem_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("m"); - cmd_parser.push("").push("size"); - - cmd_parser.parse(mem_config)?; - - let mem = if let Some(mem_size) = cmd_parser.get_value::("")? { - memory_unit_conversion(&mem_size, M)? - } else if let Some(mem_size) = cmd_parser.get_value::("size")? { - memory_unit_conversion(&mem_size, M)? - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "size".to_string(), - "memory".to_string() - ))); - }; - - self.machine_config.mem_config.mem_size = mem; + // Is there a "size=" prefix tag in the command line. + let mut has_size_label = false; + if get_value_of_parameter("size", mem_config).is_ok() { + has_size_label = true; + } + let mem_cfg = + MemSizeConfig::try_parse_from(str_slip_to_clap(mem_config, !has_size_label, false))?; + self.machine_config.mem_config.mem_size = mem_cfg.size; Ok(()) } /// Add '-smp' cpu config to `VmConfig`. pub fn add_cpu(&mut self, cpu_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("smp"); - cmd_parser - .push("") - .push("maxcpus") - .push("sockets") - .push("dies") - .push("clusters") - .push("cores") - .push("threads") - .push("cpus"); - - cmd_parser.parse(cpu_config)?; - - let cpu = if let Some(cpu) = cmd_parser.get_value::("")? { - cpu - } else if let Some(cpu) = cmd_parser.get_value::("cpus")? { - if cpu == 0 { - return Err(anyhow!(ConfigError::IllegalValue( - "cpu".to_string(), - 1, - true, - MAX_NR_CPUS, - true - ))); - } - cpu - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "cpus".to_string(), - "smp".to_string() - ))); - }; - - let sockets = smp_read_and_check(&cmd_parser, "sockets", 0)?; - - let dies = smp_read_and_check(&cmd_parser, "dies", 1)?; - - let clusters = smp_read_and_check(&cmd_parser, "clusters", 1)?; - - let cores = smp_read_and_check(&cmd_parser, "cores", 0)?; - - let threads = smp_read_and_check(&cmd_parser, "threads", 0)?; - - let max_cpus = cmd_parser.get_value::("maxcpus")?.unwrap_or_default(); - - let (max_cpus, sockets, cores, threads) = - adjust_topology(cpu, max_cpus, sockets, dies, clusters, cores, threads); - - // limit cpu count - if !(MIN_NR_CPUS..=MAX_NR_CPUS).contains(&cpu) { - return Err(anyhow!(ConfigError::IllegalValue( - "CPU number".to_string(), - MIN_NR_CPUS, - true, - MAX_NR_CPUS, - true, - ))); + let mut has_cpus_label = false; + if get_value_of_parameter("cpus", cpu_config).is_ok() { + has_cpus_label = true; } - - if !(MIN_NR_CPUS..=MAX_NR_CPUS).contains(&max_cpus) { - return Err(anyhow!(ConfigError::IllegalValue( - "MAX CPU number".to_string(), - MIN_NR_CPUS, - true, - MAX_NR_CPUS, - true, - ))); - } - - if max_cpus < cpu { - return Err(anyhow!(ConfigError::IllegalValue( - "maxcpus".to_string(), - cpu, - true, - MAX_NR_CPUS, - true, - ))); - } - - if sockets * dies * clusters * cores * threads != max_cpus { - bail!("sockets * dies * clusters * cores * threads must be equal to max_cpus"); - } - - self.machine_config.nr_cpus = cpu as u8; - self.machine_config.nr_threads = threads as u8; - self.machine_config.nr_cores = cores as u8; - self.machine_config.nr_dies = dies as u8; - self.machine_config.nr_clusters = clusters as u8; - self.machine_config.nr_sockets = sockets as u8; - self.machine_config.max_cpus = max_cpus as u8; + let mut smp_cfg = + SmpConfig::try_parse_from(str_slip_to_clap(cpu_config, !has_cpus_label, false))?; + smp_cfg.auto_adjust_topology()?; + + self.machine_config.nr_cpus = smp_cfg.cpus as u8; + self.machine_config.nr_threads = smp_cfg.threads as u8; + self.machine_config.nr_cores = smp_cfg.cores as u8; + self.machine_config.nr_dies = smp_cfg.dies as u8; + self.machine_config.nr_clusters = smp_cfg.clusters as u8; + self.machine_config.nr_sockets = smp_cfg.sockets as u8; + self.machine_config.max_cpus = smp_cfg.maxcpus as u8; Ok(()) } pub fn add_cpu_feature(&mut self, features: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("cpu"); - cmd_parser.push(""); - cmd_parser.push("pmu"); - cmd_parser.push("sve"); - cmd_parser.parse(features)?; - - // Check PMU when actually enabling PMU. - if let Some(k) = cmd_parser.get_value::("pmu")? { - self.machine_config.cpu_config.pmu = match k.as_ref() { - "on" => PmuConfig::On, - "off" => PmuConfig::Off, - _ => bail!("Invalid PMU option,must be one of \'on\" or \"off\"."), - } - } - - if let Some(k) = cmd_parser.get_value::("sve")? { - self.machine_config.cpu_config.sve = match k.as_ref() { - "on" => SveConfig::On, - "off" => SveConfig::Off, - _ => bail!("Invalid SVE option, must be one of \"on\" or \"off\"."), - } - } + let cpu_config = CpuConfig::try_parse_from(str_slip_to_clap(features, true, false))?; + self.machine_config.cpu_config = cpu_config; Ok(()) } @@ -452,149 +463,26 @@ impl VmConfig { } impl VmConfig { - fn get_mem_zone_id(&self, cmd_parser: &CmdParser) -> Result { - if let Some(id) = cmd_parser.get_value::("id")? { - check_arg_too_long(&id, "id")?; - Ok(id) - } else { - Err(anyhow!(ConfigError::FieldIsMissing( - "id".to_string(), - "memory-backend-ram".to_string() - ))) - } - } - - fn get_mem_path(&self, cmd_parser: &CmdParser) -> Result> { - if let Some(path) = cmd_parser.get_value::("mem-path")? { - check_path_too_long(&path, "mem-path")?; - return Ok(Some(path)); - } - Ok(None) - } - - fn get_mem_zone_size(&self, cmd_parser: &CmdParser) -> Result { - if let Some(mem) = cmd_parser.get_value::("size")? { - let size = memory_unit_conversion(&mem, M)?; - Ok(size) - } else { - Err(anyhow!(ConfigError::FieldIsMissing( - "size".to_string(), - "memory-backend-ram".to_string() - ))) - } - } - - fn get_mem_zone_host_nodes(&self, cmd_parser: &CmdParser) -> Result>> { - if let Some(mut host_nodes) = cmd_parser - .get_value::("host-nodes") - .with_context(|| { - ConfigError::ConvertValueFailed(String::from("u32"), "host-nodes".to_string()) - })? - .map(|v| v.0.iter().map(|e| *e as u32).collect::>()) - { - host_nodes.sort_unstable(); - if host_nodes[host_nodes.len() - 1] >= MAX_NODES { - return Err(anyhow!(ConfigError::IllegalValue( - "host_nodes".to_string(), - 0, - true, - MAX_NODES as u64, - false, - ))); - } - Ok(Some(host_nodes)) - } else { - Ok(None) - } - } - - fn get_mem_zone_policy(&self, cmd_parser: &CmdParser) -> Result { - let policy = cmd_parser - .get_value::("policy")? - .unwrap_or_else(|| "default".to_string()); - if HostMemPolicy::from(policy.clone()) == HostMemPolicy::NotSupported { - return Err(anyhow!(ConfigError::InvalidParam( - "policy".to_string(), - policy - ))); - } - Ok(policy) - } - - fn get_mem_share(&self, cmd_parser: &CmdParser) -> Result { - let share = cmd_parser - .get_value::("share")? - .unwrap_or_else(|| "off".to_string()); - - if share.eq("on") || share.eq("off") { - Ok(share.eq("on")) - } else { - Err(anyhow!(ConfigError::InvalidParam( - "share".to_string(), - share - ))) - } - } - - fn get_mem_dump(&self, cmd_parser: &CmdParser) -> Result { - if let Some(dump_guest) = cmd_parser.get_value::("dump-guest-core")? { - return Ok(dump_guest.into()); - } - Ok(true) - } - - fn get_mem_prealloc(&self, cmd_parser: &CmdParser) -> Result { - if let Some(mem_prealloc) = cmd_parser.get_value::("mem-prealloc")? { - return Ok(mem_prealloc.into()); - } - Ok(false) - } - /// Convert memory zone cmdline to VM config /// /// # Arguments /// /// * `mem_zone` - The memory zone cmdline string. - /// * `mem_type` - The memory zone type - pub fn add_mem_zone(&mut self, mem_zone: &str, mem_type: String) -> Result { - let mut cmd_parser = CmdParser::new("mem_zone"); - cmd_parser - .push("") - .push("id") - .push("size") - .push("host-nodes") - .push("policy") - .push("share") - .push("mem-path") - .push("dump-guest-core") - .push("mem-prealloc"); - cmd_parser.parse(mem_zone)?; - - let zone_config = MemZoneConfig { - id: self.get_mem_zone_id(&cmd_parser)?, - size: self.get_mem_zone_size(&cmd_parser)?, - host_numa_nodes: self.get_mem_zone_host_nodes(&cmd_parser)?, - policy: self.get_mem_zone_policy(&cmd_parser)?, - dump_guest_core: self.get_mem_dump(&cmd_parser)?, - share: self.get_mem_share(&cmd_parser)?, - mem_path: self.get_mem_path(&cmd_parser)?, - prealloc: self.get_mem_prealloc(&cmd_parser)?, - memfd: mem_type.eq("memory-backend-memfd"), - }; + pub fn add_mem_zone(&mut self, mem_zone: &str) -> Result { + let zone_config = MemZoneConfig::try_parse_from(str_slip_to_clap(mem_zone, true, false))?; - if (zone_config.mem_path.is_none() && mem_type.eq("memory-backend-file")) - || (zone_config.mem_path.is_some() && mem_type.ne("memory-backend-file")) + if (zone_config.mem_path.is_none() && zone_config.mem_type.eq("memory-backend-file")) + || (zone_config.mem_path.is_some() && zone_config.mem_type.ne("memory-backend-file")) { - bail!("Object type: {} config path err", mem_type); + bail!("Object type: {} config path err", zone_config.mem_type); } - if self.object.mem_object.get(&zone_config.id).is_none() { - self.object - .mem_object - .insert(zone_config.id.clone(), zone_config.clone()); - } else { + if self.object.mem_object.get(&zone_config.id).is_some() { bail!("Object: {} has been added", zone_config.id); } + self.object + .mem_object + .insert(zone_config.id.clone(), zone_config.clone()); if zone_config.host_numa_nodes.is_none() { return Ok(zone_config); @@ -615,62 +503,6 @@ impl VmConfig { } } -fn smp_read_and_check(cmd_parser: &CmdParser, name: &str, default_val: u64) -> Result { - if let Some(values) = cmd_parser.get_value::(name)? { - if values == 0 { - return Err(anyhow!(ConfigError::IllegalValue( - name.to_string(), - 1, - true, - u8::MAX as u64, - false - ))); - } - Ok(values) - } else { - Ok(default_val) - } -} - -fn adjust_topology( - cpu: u64, - mut max_cpus: u64, - mut sockets: u64, - dies: u64, - clusters: u64, - mut cores: u64, - mut threads: u64, -) -> (u64, u64, u64, u64) { - if max_cpus == 0 { - if sockets * dies * clusters * cores * threads > 0 { - max_cpus = sockets * dies * clusters * cores * threads; - } else { - max_cpus = cpu; - } - } - - if cores == 0 { - if sockets == 0 { - sockets = 1; - } - if threads == 0 { - threads = 1; - } - cores = max_cpus / (sockets * dies * clusters * threads); - } else if sockets == 0 { - if threads == 0 { - threads = 1; - } - sockets = max_cpus / (dies * clusters * cores * threads); - } - - if threads == 0 { - threads = max_cpus / (sockets * dies * clusters * cores); - } - - (max_cpus, sockets, cores, threads) -} - /// Convert memory units from GiB, Mib to Byte. /// /// # Arguments @@ -731,6 +563,34 @@ fn get_inner(outer: Option) -> Result { outer.with_context(|| ConfigError::IntegerOverflow("-m".to_string())) } +fn get_host_nodes(nodes: &str) -> Result> { + let mut host_nodes = IntegerList::from_str(nodes) + .with_context(|| { + ConfigError::ConvertValueFailed(String::from("u32"), "host-nodes".to_string()) + })? + .0 + .iter() + .map(|e| *e as u32) + .collect::>(); + + if host_nodes.is_empty() { + bail!("Got empty host nodes list!"); + } + + host_nodes.sort_unstable(); + if host_nodes[host_nodes.len() - 1] >= MAX_NODES { + return Err(anyhow!(ConfigError::IllegalValue( + "host_nodes".to_string(), + 0, + true, + MAX_NODES as u64, + false, + ))); + } + + Ok(host_nodes) +} + #[cfg(test)] mod tests { use super::*; @@ -747,6 +607,7 @@ mod tests { }; let mut machine_config = MachineConfig { mach_type: MachineType::MicroVm, + hypervisor: HypervisorType::Kvm, nr_cpus: 1, nr_cores: 1, nr_threads: 1, @@ -970,11 +831,12 @@ mod tests { assert_eq!(machine_cfg.mem_config.mem_share, true); let mut vm_config = VmConfig::default(); - let memory_cfg_str = "type=none,dump-guest-core=off,mem-share=off,accel=kvm,usb=off"; + let memory_cfg_str = "none,dump-guest-core=off,mem-share=off,accel=kvm,usb=off"; let machine_cfg_ret = vm_config.add_machine(memory_cfg_str); assert!(machine_cfg_ret.is_ok()); let machine_cfg = vm_config.machine_config; assert_eq!(machine_cfg.mach_type, MachineType::None); + assert_eq!(machine_cfg.hypervisor, HypervisorType::Kvm); assert_eq!(machine_cfg.mem_config.dump_guest_core, false); assert_eq!(machine_cfg.mem_config.mem_share, false); @@ -1079,10 +941,7 @@ mod tests { fn test_add_mem_zone() { let mut vm_config = VmConfig::default(); let zone_config_1 = vm_config - .add_mem_zone( - "-object memory-backend-ram,size=2G,id=mem1,host-nodes=1,policy=bind", - String::from("memory-backend-ram"), - ) + .add_mem_zone("memory-backend-ram,size=2G,id=mem1,host-nodes=1,policy=bind") .unwrap(); assert_eq!(zone_config_1.id, "mem1"); assert_eq!(zone_config_1.size, 2147483648); @@ -1090,38 +949,26 @@ mod tests { assert_eq!(zone_config_1.policy, "bind"); let zone_config_2 = vm_config - .add_mem_zone( - "-object memory-backend-ram,size=2G,id=mem2,host-nodes=1-2,policy=default", - String::from("memory-backend-ram"), - ) + .add_mem_zone("memory-backend-ram,size=2G,id=mem2,host-nodes=1-2,policy=default") .unwrap(); assert_eq!(zone_config_2.host_numa_nodes, Some(vec![1, 2])); let zone_config_3 = vm_config - .add_mem_zone( - "-object memory-backend-ram,size=2M,id=mem3,share=on", - String::from("memory-backend-ram"), - ) + .add_mem_zone("memory-backend-ram,size=2M,id=mem3,share=on") .unwrap(); assert_eq!(zone_config_3.size, 2 * 1024 * 1024); assert_eq!(zone_config_3.share, true); let zone_config_4 = vm_config - .add_mem_zone( - "-object memory-backend-ram,size=2M,id=mem4", - String::from("memory-backend-ram"), - ) + .add_mem_zone("memory-backend-ram,size=2M,id=mem4") .unwrap(); assert_eq!(zone_config_4.share, false); - assert_eq!(zone_config_4.memfd, false); + assert_eq!(zone_config_4.memfd(), false); let zone_config_5 = vm_config - .add_mem_zone( - "-object memory-backend-memfd,size=2M,id=mem5", - String::from("memory-backend-memfd"), - ) + .add_mem_zone("memory-backend-memfd,size=2M,id=mem5") .unwrap(); - assert_eq!(zone_config_5.memfd, true); + assert_eq!(zone_config_5.memfd(), true); } #[test] @@ -1145,15 +992,44 @@ mod tests { assert!(vm_config.machine_config.cpu_config.pmu == PmuConfig::Off); vm_config.add_cpu_feature("host,pmu=off").unwrap(); assert!(vm_config.machine_config.cpu_config.pmu == PmuConfig::Off); - vm_config.add_cpu_feature("pmu=off").unwrap(); - assert!(vm_config.machine_config.cpu_config.pmu == PmuConfig::Off); vm_config.add_cpu_feature("host,pmu=on").unwrap(); assert!(vm_config.machine_config.cpu_config.pmu == PmuConfig::On); - vm_config.add_cpu_feature("pmu=on").unwrap(); - assert!(vm_config.machine_config.cpu_config.pmu == PmuConfig::On); - vm_config.add_cpu_feature("sve=on").unwrap(); + vm_config.add_cpu_feature("host,sve=on").unwrap(); assert!(vm_config.machine_config.cpu_config.sve == SveConfig::On); - vm_config.add_cpu_feature("sve=off").unwrap(); + vm_config.add_cpu_feature("host,sve=off").unwrap(); assert!(vm_config.machine_config.cpu_config.sve == SveConfig::Off); + + // Illegal cpu command lines: should set cpu family. + let result = vm_config.add_cpu_feature("pmu=off"); + assert!(result.is_err()); + let result = vm_config.add_cpu_feature("sve=on"); + assert!(result.is_err()); + + // Illegal parameters. + let result = vm_config.add_cpu_feature("host,sve1=on"); + assert!(result.is_err()); + + // Illegal values. + let result = vm_config.add_cpu_feature("host,sve=false"); + assert!(result.is_err()); + } + + #[test] + fn test_add_accel() { + let mut vm_config = VmConfig::default(); + let accel_cfg = "kvm"; + assert!(vm_config.add_accel(accel_cfg).is_ok()); + let machine_cfg = vm_config.machine_config; + assert_eq!(machine_cfg.hypervisor, HypervisorType::Kvm); + + let mut vm_config = VmConfig::default(); + let accel_cfg = "kvm:tcg"; + assert!(vm_config.add_accel(accel_cfg).is_ok()); + let machine_cfg = vm_config.machine_config; + assert_eq!(machine_cfg.hypervisor, HypervisorType::Kvm); + + let mut vm_config = VmConfig::default(); + let accel_cfg = "kvm1"; + assert!(vm_config.add_accel(accel_cfg).is_err()); } } diff --git a/machine_manager/src/config/mod.rs b/machine_manager/src/config/mod.rs index 8b2944d..ca1281a 100644 --- a/machine_manager/src/config/mod.rs +++ b/machine_manager/src/config/mod.rs @@ -20,75 +20,53 @@ pub mod vnc; mod boot_source; mod chardev; -#[cfg(feature = "demo_device")] -mod demo_dev; mod devices; mod drive; -mod fs; -#[cfg(feature = "virtio_gpu")] -mod gpu; mod incoming; mod iothread; mod machine_config; mod network; mod numa; mod pci; -#[cfg(feature = "pvpanic")] -mod pvpanic_pci; -#[cfg(all(feature = "ramfb", target_arch = "aarch64"))] -mod ramfb; mod rng; #[cfg(feature = "vnc_auth")] mod sasl_auth; -mod scsi; mod smbios; #[cfg(feature = "vnc_auth")] mod tls_creds; -mod usb; -mod vfio; pub use boot_source::*; #[cfg(feature = "usb_camera")] pub use camera::*; pub use chardev::*; -#[cfg(feature = "demo_device")] -pub use demo_dev::*; pub use devices::*; #[cfg(any(feature = "gtk", feature = "ohui_srv"))] pub use display::*; pub use drive::*; pub use error::ConfigError; -pub use fs::*; -#[cfg(feature = "virtio_gpu")] -pub use gpu::*; pub use incoming::*; pub use iothread::*; pub use machine_config::*; pub use network::*; pub use numa::*; pub use pci::*; -#[cfg(feature = "pvpanic")] -pub use pvpanic_pci::*; -#[cfg(all(feature = "ramfb", target_arch = "aarch64"))] -pub use ramfb::*; pub use rng::*; #[cfg(feature = "vnc_auth")] pub use sasl_auth::*; -pub use scsi::*; pub use smbios::*; #[cfg(feature = "vnc_auth")] pub use tls_creds::*; -pub use usb::*; -pub use vfio::*; #[cfg(feature = "vnc")] pub use vnc::*; use std::collections::HashMap; -use std::fs::File; +use std::fs::{canonicalize, File}; use std::io::Read; +use std::path::Path; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use log::error; use serde::{Deserialize, Serialize}; @@ -110,10 +88,22 @@ pub const MAX_SOCK_PATH_LENGTH: usize = 108; pub const MAX_VIRTIO_QUEUE: usize = 32; pub const FAST_UNPLUG_ON: &str = "1"; pub const FAST_UNPLUG_OFF: &str = "0"; -pub const MAX_TAG_LENGTH: usize = 36; pub const MAX_NODES: u32 = 128; /// Default virtqueue size for virtio devices excepts virtio-fs. pub const DEFAULT_VIRTQUEUE_SIZE: u16 = 256; +// Seg_max = queue_size - 2. So, size of each virtqueue for virtio-scsi/virtio-blk should be larger than 2. +pub const MIN_QUEUE_SIZE_BLOCK_DEVICE: u64 = 2; +// Max size of each virtqueue for virtio-scsi/virtio-blk. +pub const MAX_QUEUE_SIZE_BLOCK_DEVICE: u64 = 1024; +/// The bar0 size of enable_bar0 features +pub const VIRTIO_GPU_ENABLE_BAR0_SIZE: u64 = 64 * M; + +#[derive(Parser)] +#[command(no_binary_name(true))] +struct GlobalConfig { + #[arg(long, alias = "pcie-root-port.fast-unplug", value_parser = ["0", "1"])] + fast_unplug: Option, +} #[derive(Clone, Default, Debug, Serialize, Deserialize)] pub struct ObjectConfig { @@ -139,7 +129,7 @@ pub struct VmConfig { pub serial: Option, pub iothreads: Option>, pub object: ObjectConfig, - pub pflashs: Option>, + pub pflashs: Option>, pub dev_name: HashMap, pub global_config: HashMap, pub numa_nodes: Vec<(String, String)>, @@ -151,7 +141,7 @@ pub struct VmConfig { #[cfg(feature = "usb_camera")] pub camera_backend: HashMap, #[cfg(feature = "windows_emu_pid")] - pub windows_emu_pid: Option, + pub emulator_pid: Option, pub smbios: SmbiosConfig, } @@ -179,12 +169,12 @@ impl VmConfig { let mut stdio_count = 0; if let Some(serial) = self.serial.as_ref() { - if serial.chardev.backend == ChardevType::Stdio { + if let ChardevType::Stdio { .. } = serial.chardev.classtype { stdio_count += 1; } } for (_, char_dev) in self.chardev.clone() { - if char_dev.backend == ChardevType::Stdio { + if let ChardevType::Stdio { .. } = char_dev.classtype { stdio_count += 1; } } @@ -214,29 +204,24 @@ impl VmConfig { /// /// * `object_args` - The args of object. pub fn add_object(&mut self, object_args: &str) -> Result<()> { - let mut cmd_params = CmdParser::new("object"); - cmd_params.push(""); - - cmd_params.get_parameters(object_args)?; - let device_type = cmd_params - .get_value::("")? - .with_context(|| "Object type not specified")?; - match device_type.as_str() { + let object_type = + get_class_type(object_args).with_context(|| "Object type not specified")?; + match object_type.as_str() { "iothread" => { self.add_iothread(object_args) .with_context(|| "Failed to add iothread")?; } "rng-random" => { - let rng_cfg = parse_rng_obj(object_args)?; + let rng_cfg = + RngObjConfig::try_parse_from(str_slip_to_clap(object_args, true, false))?; let id = rng_cfg.id.clone(); - if self.object.rng_object.get(&id).is_none() { - self.object.rng_object.insert(id, rng_cfg); - } else { + if self.object.rng_object.get(&id).is_some() { bail!("Object: {} has been added", id); } + self.object.rng_object.insert(id, rng_cfg); } "memory-backend-ram" | "memory-backend-file" | "memory-backend-memfd" => { - self.add_mem_zone(object_args, device_type)?; + self.add_mem_zone(object_args)?; } #[cfg(feature = "vnc_auth")] "tls-creds-x509" => { @@ -247,7 +232,7 @@ impl VmConfig { self.add_saslauth(object_args)?; } _ => { - bail!("Unknow object type: {:?}", &device_type); + bail!("Unknow object type: {:?}", &object_type); } } @@ -260,24 +245,18 @@ impl VmConfig { /// /// * `global_config` - The args of global config. pub fn add_global_config(&mut self, global_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("global"); - cmd_parser.push("pcie-root-port.fast-unplug"); - cmd_parser.parse(global_config)?; + let global_config = + GlobalConfig::try_parse_from(str_slip_to_clap(global_config, false, false))?; - if let Some(fast_unplug_value) = - cmd_parser.get_value::("pcie-root-port.fast-unplug")? - { - if fast_unplug_value != FAST_UNPLUG_ON && fast_unplug_value != FAST_UNPLUG_OFF { - bail!("The value of fast-unplug is invalid: {}", fast_unplug_value); - } + if let Some(fast_unplug_value) = global_config.fast_unplug { let fast_unplug_key = String::from("pcie-root-port.fast-unplug"); - if self.global_config.get(&fast_unplug_key).is_none() { - self.global_config - .insert(fast_unplug_key, fast_unplug_value); - } else { + if self.global_config.get(&fast_unplug_key).is_some() { bail!("Global config {} has been added", fast_unplug_key); } + self.global_config + .insert(fast_unplug_key, fast_unplug_value); } + Ok(()) } @@ -289,9 +268,9 @@ impl VmConfig { #[cfg(feature = "windows_emu_pid")] pub fn add_windows_emu_pid(&mut self, windows_emu_pid: &str) -> Result<()> { if windows_emu_pid.is_empty() { - bail!("The arg of windows_emu_pid is empty!"); + bail!("The arg of emulator_pid is empty!"); } - self.windows_emu_pid = Some(windows_emu_pid.to_string()); + self.emulator_pid = Some(windows_emu_pid.to_string()); Ok(()) } @@ -395,7 +374,7 @@ impl VmConfig { &mut drive_files, &drive.id, &drive.path_on_host, - drive.read_only, + drive.readonly, drive.direct, )?; } @@ -405,7 +384,7 @@ impl VmConfig { &mut drive_files, "", &pflash.path_on_host, - pflash.read_only, + pflash.readonly, false, )?; } @@ -436,163 +415,6 @@ pub trait ConfigCheck: AsAny + Send + Sync + std::fmt::Debug { fn check(&self) -> Result<()>; } -/// Struct `CmdParser` used to parse and check cmdline parameters to vm config. -pub struct CmdParser { - name: String, - params: HashMap>, -} - -impl CmdParser { - /// Allocates an empty `CmdParser`. - pub fn new(name: &str) -> Self { - CmdParser { - name: name.to_string(), - params: HashMap::>::new(), - } - } - - /// Push a new param field into `params`. - /// - /// # Arguments - /// - /// * `param_field`: The cmdline parameter field name. - pub fn push(&mut self, param_field: &str) -> &mut Self { - self.params.insert(param_field.to_string(), None); - - self - } - - /// Parse cmdline parameters string into `params`. - /// - /// # Arguments - /// - /// * `cmd_param`: The whole cmdline parameter string. - pub fn parse(&mut self, cmd_param: &str) -> Result<()> { - if cmd_param.starts_with(',') || cmd_param.ends_with(',') { - return Err(anyhow!(ConfigError::InvalidParam( - cmd_param.to_string(), - self.name.clone() - ))); - } - let param_items = cmd_param.split(',').collect::>(); - for (i, param_item) in param_items.iter().enumerate() { - if param_item.starts_with('=') || param_item.ends_with('=') { - return Err(anyhow!(ConfigError::InvalidParam( - param_item.to_string(), - self.name.clone() - ))); - } - let param = param_item.splitn(2, '=').collect::>(); - let (param_key, param_value) = match param.len() { - 1 => { - if i == 0 { - ("", param[0]) - } else { - (param[0], "") - } - } - 2 => (param[0], param[1]), - _ => { - return Err(anyhow!(ConfigError::InvalidParam( - param_item.to_string(), - self.name.clone() - ))); - } - }; - - if self.params.contains_key(param_key) { - let field_value = self.params.get_mut(param_key).unwrap(); - if field_value.is_none() { - *field_value = Some(String::from(param_value)); - } else { - return Err(anyhow!(ConfigError::FieldRepeat( - self.name.clone(), - param_key.to_string() - ))); - } - } else { - return Err(anyhow!(ConfigError::InvalidParam( - param[0].to_string(), - self.name.clone() - ))); - } - } - - Ok(()) - } - - /// Parse all cmdline parameters string into `params`. - /// - /// # Arguments - /// - /// * `cmd_param`: The whole cmdline parameter string. - fn get_parameters(&mut self, cmd_param: &str) -> Result<()> { - if cmd_param.starts_with(',') || cmd_param.ends_with(',') { - return Err(anyhow!(ConfigError::InvalidParam( - cmd_param.to_string(), - self.name.clone() - ))); - } - let param_items = cmd_param.split(',').collect::>(); - for param_item in param_items { - let param = param_item.splitn(2, '=').collect::>(); - let (param_key, param_value) = match param.len() { - 1 => ("", param[0]), - 2 => (param[0], param[1]), - _ => { - return Err(anyhow!(ConfigError::InvalidParam( - param_item.to_string(), - self.name.clone() - ))); - } - }; - - if self.params.contains_key(param_key) { - let field_value = self.params.get_mut(param_key).unwrap(); - if field_value.is_none() { - *field_value = Some(String::from(param_value)); - } else { - return Err(anyhow!(ConfigError::FieldRepeat( - self.name.clone(), - param_key.to_string() - ))); - } - } - } - - Ok(()) - } - - /// Get cmdline parameters value from param field name. - /// - /// # Arguments - /// - /// * `param_field`: The cmdline parameter field name. - pub fn get_value(&self, param_field: &str) -> Result> { - match self.params.get(param_field) { - Some(value) => { - let field_msg = if param_field.is_empty() { - &self.name - } else { - param_field - }; - - if let Some(raw_value) = value { - Ok(Some(raw_value.parse().map_err(|_| { - anyhow!(ConfigError::ConvertValueFailed( - field_msg.to_string(), - raw_value.clone() - )) - })?)) - } else { - Ok(None) - } - } - None => Ok(None), - } - } -} - /// This struct is a wrapper for `bool`. /// More switch string can be transferred to this structure. pub struct ExBool { @@ -619,8 +441,8 @@ impl From for bool { pub fn parse_bool(s: &str) -> Result { match s { - "on" => Ok(true), - "off" => Ok(false), + "true" | "on" | "yes" | "unmap" => Ok(true), + "false" | "off" | "no" | "ignore" => Ok(false), _ => Err(anyhow!("Unknow bool value {s}")), } } @@ -644,15 +466,16 @@ fn enable_trace_state(path: &str) -> Result<()> { Ok(()) } -pub fn parse_trace_options(opt: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("trace"); - cmd_parser.push("file"); - cmd_parser.get_parameters(opt)?; +#[derive(Parser)] +#[command(no_binary_name(true))] +struct TraceConfig { + #[arg(long)] + file: String, +} - let path = cmd_parser - .get_value::("file")? - .with_context(|| "trace: trace file must be set.")?; - enable_trace_state(&path)?; +pub fn add_trace(opt: &str) -> Result<()> { + let trace_cfg = TraceConfig::try_parse_from(str_slip_to_clap(opt, false, false))?; + enable_trace_state(&trace_cfg.file)?; Ok(()) } @@ -673,7 +496,7 @@ impl FromStr for UnsignedInteger { pub struct IntegerList(pub Vec); impl FromStr for IntegerList { - type Err = (); + type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result { let mut integer_list = Vec::new(); @@ -685,19 +508,22 @@ impl FromStr for IntegerList { for list in lists.iter() { let items: Vec<&str> = list.split('-').collect(); if items.len() > 2 { - return Err(()); + return Err(anyhow!( + "{} parameters connected by -, should be no more than 2.", + items.len() + )); } let start = items[0] .parse::() - .map_err(|e| error!("Invalid value {}, error is {:?}", items[0], e))?; + .map_err(|e| anyhow!("Invalid value {}, error is {:?}", items[0], e))?; integer_list.push(start); if items.len() == 2 { let end = items[1] .parse::() - .map_err(|e| error!("Invalid value {}, error is {:?}", items[1], e))?; + .map_err(|e| anyhow!("Invalid value {}, error is {:?}", items[1], e))?; if start >= end { - return Err(()); + return Err(anyhow!("start {} is bigger than end {}.", start, end)); } for i in start..end { @@ -730,128 +556,200 @@ pub fn check_path_too_long(arg: &str, name: &str) -> Result<()> { Ok(()) } -pub fn check_arg_nonexist(arg: Option, name: &str, device: &str) -> Result<()> { - arg.with_context(|| ConfigError::FieldIsMissing(name.to_string(), device.to_string()))?; +/// Make sure args are existed. +/// +/// arg_name: Name of arg. +/// arg_value: Value of arg. Should be Option<> class. +/// Eg: +/// check_arg_exist!(("id", id)); +/// check_arg_exist!(("bus", bus), ("addr", addr)); +#[macro_export] +macro_rules! check_arg_exist{ + ($(($arg_name:tt, $arg_value:expr)),*) => { + $($arg_value.clone().with_context(|| format!("Should set {}.", $arg_name))?;)* + } +} - Ok(()) +/// Make sure args are existed. +/// +/// arg_name: Name of arg. +/// arg_value: Value of arg. Should be Option<> class. +/// Eg: +/// check_arg_nonexist!(("id", id)); +/// check_arg_nonexist!(("bus", bus), ("addr", addr)); +#[macro_export] +macro_rules! check_arg_nonexist{ + ($(($arg_name:tt, $arg_value:expr)),*) => { + $($arg_value.clone().map_or(Some(0), |_| None).with_context(|| format!("Should not set {}", $arg_name))?;)* + } +} + +fn concat_classtype(args: &str, concat: bool) -> String { + if concat { + format!("classtype={}", args) + } else { + args.to_string() + } } /// Configure StratoVirt parameters in clap format. -pub fn str_slip_to_clap(args: &str) -> Vec { - let args_vecs = args.split([',', '=']).collect::>(); - let mut itr: Vec = Vec::with_capacity(args_vecs.len()); - for (cnt, param) in args_vecs.iter().enumerate() { - if cnt % 2 == 1 { - itr.push(format!("--{}", param)); - } else { - itr.push(param.to_string()); +/// +/// The first parameter will be parsed as the `binary name` unless Command::no_binary_name is used when using `clap`. +/// Stratovirt command line may use the first parameter as class type. +/// Eg: +/// 1. drive config: "-drive file=,if=pflash,unit=0" +/// This cmdline has no class type. +/// 2. device config: "-device virtio-balloon-pci,id=,bus=,addr=<0x4>" +/// This cmdline sets device type `virtio-balloon-pci` as the first parameter. +/// +/// Use first_pos_is_type to indicate whether the first parameter is a type class which needs a separate analysis. +/// Eg: +/// 1. drive config: "-drive file=,if=pflash,unit=0" +/// Set first_pos_is_type false for this cmdline has no class type. +/// 2. device config: "-device virtio-balloon-pci,id=,bus=,addr=<0x4>" +/// Set first_pos_is_type true for this cmdline has device type "virtio-balloon-pci" as the first parameter. +/// +/// Use first_pos_is_subcommand to indicate whether the first parameter is a subclass. +/// Eg: +/// Chardev has stdio/unix-socket/tcp-socket/pty/file classes. These classes have different configurations but will be stored +/// in the same `ChardevConfig` structure by using `enum`. So, we will use class type as a subcommand to indicate which subtype +/// will be used to store the configuration in enumeration type. Subcommand in `clap` doesn't need `--` in parameter. +/// 1. -serial file,path= +/// Set first_pos_is_subcommand true for first parameter `file` is the subclass type for chardev. +pub fn str_slip_to_clap( + args: &str, + first_pos_is_type: bool, + first_pos_is_subcommand: bool, +) -> Vec { + let mut subcommand = first_pos_is_subcommand; + let args_str = concat_classtype(args, first_pos_is_type && !subcommand); + let args_vecs = args_str.split([',']).collect::>(); + let mut itr: Vec = Vec::with_capacity(args_vecs.len() * 2); + for params in args_vecs { + let key_value = params.split(['=']).collect::>(); + // Command line like "key=value" will be converted to "--key value". + // Command line like "key" will be converted to "--key". + for (cnt, param) in key_value.iter().enumerate() { + if cnt % 2 == 0 { + if subcommand { + itr.push(param.to_string()); + subcommand = false; + } else { + itr.push(format!("--{}", param)); + } + } else { + itr.push(param.to_string()); + } } } itr } +/// Retrieve the value of the specified parameter from a string in the format "key=value". +pub fn get_value_of_parameter(parameter: &str, args_str: &str) -> Result { + let args_vecs = args_str.split([',']).collect::>(); + + for args in args_vecs { + let key_value = args.split(['=']).collect::>(); + if key_value.len() != 2 || key_value[0] != parameter { + continue; + } + if key_value[1].is_empty() { + bail!("Find empty arg {} in string {}.", key_value[0], args_str); + } + return Ok(key_value[1].to_string()); + } + + bail!("Cannot find {}'s value from string {}", parameter, args_str); +} + +pub fn get_class_type(args: &str) -> Result { + let args_str = concat_classtype(args, true); + get_value_of_parameter("classtype", &args_str) +} + pub fn valid_id(id: &str) -> Result { check_arg_too_long(id, "id")?; Ok(id.to_string()) } +// Virtio queue size must be power of 2 and in range [min_size, max_size]. +pub fn valid_virtqueue_size(size: u64, min_size: u64, max_size: u64) -> Result<()> { + if size < min_size || size > max_size { + return Err(anyhow!(ConfigError::IllegalValue( + "virtqueue size".to_string(), + min_size, + true, + max_size, + true + ))); + } + + if size & (size - 1) != 0 { + bail!("Virtqueue size should be power of 2!"); + } + + Ok(()) +} + +pub fn valid_path(path: &str) -> Result { + if path.len() > MAX_PATH_LENGTH { + return Err(anyhow!(ConfigError::StringLengthTooLong( + "path".to_string(), + MAX_PATH_LENGTH, + ))); + } + + let canonical_path = canonicalize(path).map_or(path.to_string(), |pathbuf| { + String::from(pathbuf.to_str().unwrap()) + }); + + Ok(canonical_path) +} + +pub fn valid_socket_path(sock_path: &str) -> Result { + if sock_path.len() > MAX_SOCK_PATH_LENGTH { + return Err(anyhow!(ConfigError::StringLengthTooLong( + "socket path".to_string(), + MAX_SOCK_PATH_LENGTH, + ))); + } + valid_path(sock_path) +} + +pub fn valid_dir(d: &str) -> Result { + let dir = String::from(d); + if !Path::new(&dir).is_dir() { + return Err(anyhow!(ConfigError::DirNotExist(dir))); + } + Ok(dir) +} + +pub fn valid_block_device_virtqueue_size(s: &str) -> Result { + let size: u64 = s.parse()?; + valid_virtqueue_size( + size, + MIN_QUEUE_SIZE_BLOCK_DEVICE + 1, + MAX_QUEUE_SIZE_BLOCK_DEVICE, + )?; + + Ok(size as u16) +} + +pub fn parse_size(s: &str) -> Result { + let size = memory_unit_conversion(s, M).with_context(|| format!("Invalid size: {}", s))?; + Ok(size) +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_cmd_parser() { - let mut cmd_parser = CmdParser::new("test"); - cmd_parser - .push("") - .push("id") - .push("path") - .push("num") - .push("test1") - .push("test2") - .push("test3") - .push("test4") - .push("test5") - .push("test6") - .push("test7"); - assert!(cmd_parser - .parse("socket,id=charconsole0,path=/tmp/console.sock,num=1,test1=true,test2=on,test3=yes,test4=false,test5=off,test6=no,test7=random") - .is_ok()); - assert_eq!( - cmd_parser.get_value::("").unwrap().unwrap(), - "socket".to_string() - ); - assert_eq!( - cmd_parser.get_value::("id").unwrap().unwrap(), - "charconsole0".to_string() - ); - assert_eq!( - cmd_parser.get_value::("path").unwrap().unwrap(), - "/tmp/console.sock".to_string() - ); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_u64); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_u32); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_u16); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_u8); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_i64); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_i32); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_i16); - assert_eq!(cmd_parser.get_value::("num").unwrap().unwrap(), 1_i8); - assert!(cmd_parser.get_value::("test1").unwrap().unwrap()); - assert!( - cmd_parser - .get_value::("test1") - .unwrap() - .unwrap() - .inner - ); - assert!( - cmd_parser - .get_value::("test2") - .unwrap() - .unwrap() - .inner - ); - assert!( - cmd_parser - .get_value::("test3") - .unwrap() - .unwrap() - .inner - ); - assert!(!cmd_parser.get_value::("test4").unwrap().unwrap()); - assert!( - !cmd_parser - .get_value::("test4") - .unwrap() - .unwrap() - .inner - ); - assert!( - !cmd_parser - .get_value::("test5") - .unwrap() - .unwrap() - .inner - ); - assert!( - !cmd_parser - .get_value::("test6") - .unwrap() - .unwrap() - .inner - ); - assert!(cmd_parser.get_value::("test7").is_err()); - assert!(cmd_parser.get_value::("test7").is_err()); - assert!(cmd_parser.get_value::("random").unwrap().is_none()); - assert!(cmd_parser.parse("random=false").is_err()); - } - - #[test] - fn test_parse_trace_options() { - assert!(parse_trace_options("fil=test_trace").is_err()); - assert!(parse_trace_options("file").is_err()); - assert!(parse_trace_options("file=test_trace").is_err()); + fn test_add_trace() { + assert!(add_trace("fil=test_trace").is_err()); + assert!(add_trace("file").is_err()); + assert!(add_trace("file=test_trace").is_err()); } #[test] @@ -886,4 +784,20 @@ mod tests { let res = vm_config.add_global_config("pcie-root-port.fast-unplug=1"); assert!(res.is_err()); } + + #[test] + fn test_get_value_of_parameter() { + let cmd = "scsi-hd,id=disk1,drive=scsi-drive-0"; + let id = get_value_of_parameter("id", cmd).unwrap(); + assert_eq!(id, "disk1"); + + let cmd = "id="; + assert!(get_value_of_parameter("id", cmd).is_err()); + + let cmd = "id"; + assert!(get_value_of_parameter("id", cmd).is_err()); + + let cmd = "scsi-hd,idxxx=disk1"; + assert!(get_value_of_parameter("id", cmd).is_err()); + } } diff --git a/machine_manager/src/config/network.rs b/machine_manager/src/config/network.rs index f7d79a1..c9e5897 100644 --- a/machine_manager/src/config/network.rs +++ b/machine_manager/src/config/network.rs @@ -13,40 +13,95 @@ use std::os::unix::io::RawFd; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser}; use serde::{Deserialize, Serialize}; -use super::{error::ConfigError, pci_args_check}; -use crate::config::get_chardev_socket_path; -use crate::config::{ - check_arg_too_long, CmdParser, ConfigCheck, ExBool, VmConfig, DEFAULT_VIRTQUEUE_SIZE, - MAX_PATH_LENGTH, MAX_VIRTIO_QUEUE, -}; +use super::error::ConfigError; +use super::{get_pci_df, parse_bool, str_slip_to_clap, valid_id, valid_virtqueue_size}; +use crate::config::{ConfigCheck, VmConfig, DEFAULT_VIRTQUEUE_SIZE, MAX_VIRTIO_QUEUE}; use crate::qmp::{qmp_channel::QmpChannel, qmp_schema}; const MAC_ADDRESS_LENGTH: usize = 17; /// Max virtqueue size of each virtqueue. -pub const MAX_QUEUE_SIZE_NET: u16 = 4096; +const MAX_QUEUE_SIZE_NET: u64 = 4096; /// Max num of virtqueues. const MAX_QUEUE_PAIRS: usize = MAX_VIRTIO_QUEUE / 2; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct NetDevcfg { + #[arg(long, alias="classtype", value_parser = ["tap", "vhost-user"])] + pub netdev_type: String, + #[arg(long, value_parser = valid_id)] pub id: String, + #[arg(long, aliases = ["fds", "fd"], use_value_delimiter = true, value_delimiter = ':')] pub tap_fds: Option>, - pub vhost_type: Option, + #[arg(long, alias = "vhost", default_value = "off", value_parser = parse_bool, action = ArgAction::Append)] + pub vhost_kernel: bool, + #[arg(long, aliases = ["vhostfds", "vhostfd"], use_value_delimiter = true, value_delimiter = ':')] pub vhost_fds: Option>, + #[arg(long, default_value = "", value_parser = valid_id)] pub ifname: String, + #[arg(long, default_value = "1", value_parser = parse_queues)] pub queues: u16, + #[arg(long)] pub chardev: Option, } +impl NetDevcfg { + pub fn vhost_type(&self) -> Option { + if self.vhost_kernel { + return Some("vhost-kernel".to_string()); + } + if self.netdev_type == "vhost-user" { + return Some("vhost-user".to_string()); + } + // Default: virtio net. + None + } + + fn auto_queues(&mut self) -> Result<()> { + if let Some(fds) = &self.tap_fds { + let fds_num = fds + .len() + .checked_mul(2) + .with_context(|| format!("Invalid fds number {}", fds.len()))? + as u16; + if fds_num > self.queues { + self.queues = fds_num; + } + } + if let Some(fds) = &self.vhost_fds { + let fds_num = fds + .len() + .checked_mul(2) + .with_context(|| format!("Invalid vhostfds number {}", fds.len()))? + as u16; + if fds_num > self.queues { + self.queues = fds_num; + } + } + Ok(()) + } +} + +fn parse_queues(q: &str) -> Result { + let queues = q + .parse::()? + .checked_mul(2) + .with_context(|| "Invalid 'queues' value")?; + is_netdev_queues_valid(queues)?; + Ok(queues) +} + impl Default for NetDevcfg { fn default() -> Self { NetDevcfg { + netdev_type: "".to_string(), id: "".to_string(), tap_fds: None, - vhost_type: None, + vhost_kernel: false, vhost_fds: None, ifname: "".to_string(), queues: 2, @@ -57,24 +112,18 @@ impl Default for NetDevcfg { impl ConfigCheck for NetDevcfg { fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "id")?; - check_arg_too_long(&self.ifname, "ifname")?; - - if let Some(vhost_type) = self.vhost_type.as_ref() { - if vhost_type != "vhost-kernel" && vhost_type != "vhost-user" { - return Err(anyhow!(ConfigError::UnknownVhostType)); - } + if self.vhost_kernel && self.netdev_type == "vhost-user" { + bail!("vhost-user netdev does not support 'vhost' option"); } - if !is_netdev_queues_valid(self.queues) { - return Err(anyhow!(ConfigError::IllegalValue( - "number queues of net device".to_string(), - 1, - true, - MAX_VIRTIO_QUEUE as u64 / 2, - true, - ))); + if self.vhost_fds.is_some() && self.vhost_type().is_none() { + bail!("Argument 'vhostfd' or 'vhostfds' are not needed for virtio-net device"); } + if self.tap_fds.is_none() && self.ifname.eq("") && self.netdev_type.ne("vhost-user") { + bail!("Tap device is missing, use \'ifname\' or \'fd\' to configure a tap device"); + } + + is_netdev_queues_valid(self.queues)?; Ok(()) } @@ -82,227 +131,73 @@ impl ConfigCheck for NetDevcfg { /// Config struct for network /// Contains network device config, such as `host_dev_name`, `mac`... -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Parser)] #[serde(deny_unknown_fields)] +#[command(no_binary_name(true))] pub struct NetworkInterfaceConfig { + #[arg(long, value_parser = ["virtio-net-pci", "virtio-net-device"])] + pub classtype: String, + #[arg(long, default_value = "", value_parser = valid_id)] pub id: String, - pub host_dev_name: String, + #[arg(long)] + pub netdev: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: Option, + #[arg(long, value_parser = valid_mac)] pub mac: Option, - pub tap_fds: Option>, - pub vhost_type: Option, - pub vhost_fds: Option>, + #[arg(long)] pub iothread: Option, - pub queues: u16, + #[arg(long, default_value="off", value_parser = parse_bool, action = ArgAction::Append)] pub mq: bool, - pub socket_path: Option, - /// All queues of a net device have the same queue size now. + // All queues of a net device have the same queue size now. + #[arg(long, default_value = "256", alias = "queue-size", value_parser = valid_network_queue_size)] pub queue_size: u16, + // MSI-X vectors the this network device has. This member isn't used now in stratovirt. + #[arg(long, default_value = "0")] + pub vectors: u16, } impl Default for NetworkInterfaceConfig { fn default() -> Self { NetworkInterfaceConfig { + classtype: "".to_string(), id: "".to_string(), - host_dev_name: "".to_string(), + netdev: "".to_string(), + bus: None, + addr: None, + multifunction: None, mac: None, - tap_fds: None, - vhost_type: None, - vhost_fds: None, iothread: None, - queues: 2, mq: false, - socket_path: None, queue_size: DEFAULT_VIRTQUEUE_SIZE, + vectors: 0, } } } +fn valid_network_queue_size(s: &str) -> Result { + let size: u64 = s.parse()?; + valid_virtqueue_size(size, DEFAULT_VIRTQUEUE_SIZE as u64, MAX_QUEUE_SIZE_NET)?; + + Ok(size as u16) +} + impl ConfigCheck for NetworkInterfaceConfig { fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "id")?; - check_arg_too_long(&self.host_dev_name, "host dev name")?; - if self.mac.is_some() && !check_mac_address(self.mac.as_ref().unwrap()) { return Err(anyhow!(ConfigError::MacFormatError)); } - if self.iothread.is_some() { - check_arg_too_long(self.iothread.as_ref().unwrap(), "iothread name")?; - } - - if self.socket_path.is_some() && self.socket_path.as_ref().unwrap().len() > MAX_PATH_LENGTH - { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "socket path".to_string(), - MAX_PATH_LENGTH - ))); - } - - if self.queue_size < DEFAULT_VIRTQUEUE_SIZE || self.queue_size > MAX_QUEUE_SIZE_NET { - return Err(anyhow!(ConfigError::IllegalValue( - "queue size of net device".to_string(), - DEFAULT_VIRTQUEUE_SIZE as u64, - true, - MAX_QUEUE_SIZE_NET as u64, - true - ))); - } - - if self.queue_size & (self.queue_size - 1) != 0 { - bail!("queue size of net device should be power of 2!"); - } + valid_network_queue_size(&self.queue_size.to_string())?; Ok(()) } } -fn parse_fds(cmd_parser: &CmdParser, name: &str) -> Result>> { - if let Some(fds) = cmd_parser.get_value::(name)? { - let mut raw_fds = Vec::new(); - for fd in fds.split(':').collect::>().iter() { - raw_fds.push( - (*fd) - .parse::() - .with_context(|| "Failed to parse fds")?, - ); - } - Ok(Some(raw_fds)) - } else { - Ok(None) - } -} - -fn parse_netdev(cmd_parser: CmdParser) -> Result { - let mut net = NetDevcfg::default(); - let netdev_type = cmd_parser.get_value::("")?.unwrap_or_default(); - if netdev_type.ne("tap") && netdev_type.ne("vhost-user") { - bail!("Unsupported netdev type: {:?}", &netdev_type); - } - net.id = cmd_parser - .get_value::("id")? - .with_context(|| ConfigError::FieldIsMissing("id".to_string(), "netdev".to_string()))?; - if let Some(ifname) = cmd_parser.get_value::("ifname")? { - net.ifname = ifname; - } - if let Some(queue_pairs) = cmd_parser.get_value::("queues")? { - let queues = queue_pairs.checked_mul(2); - if queues.is_none() || !is_netdev_queues_valid(queues.unwrap()) { - return Err(anyhow!(ConfigError::IllegalValue( - "number queues of net device".to_string(), - 1, - true, - MAX_VIRTIO_QUEUE as u64 / 2, - true, - ))); - } - - net.queues = queues.unwrap(); - } - - if let Some(tap_fd) = parse_fds(&cmd_parser, "fd")? { - net.tap_fds = Some(tap_fd); - } else if let Some(tap_fds) = parse_fds(&cmd_parser, "fds")? { - net.tap_fds = Some(tap_fds); - } - if let Some(fds) = &net.tap_fds { - let fds_num = - fds.len() - .checked_mul(2) - .with_context(|| format!("Invalid fds number {}", fds.len()))? as u16; - if fds_num > net.queues { - net.queues = fds_num; - } - } - - if let Some(vhost) = cmd_parser.get_value::("vhost")? { - if vhost.into() { - net.vhost_type = Some(String::from("vhost-kernel")); - } - } else if netdev_type.eq("vhost-user") { - net.vhost_type = Some(String::from("vhost-user")); - } - if let Some(chardev) = cmd_parser.get_value::("chardev")? { - net.chardev = Some(chardev); - } - if let Some(vhost_fd) = parse_fds(&cmd_parser, "vhostfd")? { - net.vhost_fds = Some(vhost_fd); - } else if let Some(vhost_fds) = parse_fds(&cmd_parser, "vhostfds")? { - net.vhost_fds = Some(vhost_fds); - } - if let Some(fds) = &net.vhost_fds { - let fds_num = fds - .len() - .checked_mul(2) - .with_context(|| format!("Invalid vhostfds number {}", fds.len()))? - as u16; - if fds_num > net.queues { - net.queues = fds_num; - } - } - - if net.vhost_fds.is_some() && net.vhost_type.is_none() { - bail!("Argument \'vhostfd\' is not needed for virtio-net device"); - } - if net.tap_fds.is_none() && net.ifname.eq("") && netdev_type.ne("vhost-user") { - bail!("Tap device is missing, use \'ifname\' or \'fd\' to configure a tap device"); - } - - net.check()?; - - Ok(net) -} - -pub fn parse_net(vm_config: &mut VmConfig, net_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("virtio-net"); - cmd_parser - .push("") - .push("id") - .push("netdev") - .push("mq") - .push("vectors") - .push("bus") - .push("addr") - .push("multifunction") - .push("mac") - .push("iothread") - .push("queue-size"); - - cmd_parser.parse(net_config)?; - pci_args_check(&cmd_parser)?; - let mut netdevinterfacecfg = NetworkInterfaceConfig::default(); - - let netdev = cmd_parser - .get_value::("netdev")? - .with_context(|| ConfigError::FieldIsMissing("netdev".to_string(), "net".to_string()))?; - let netid = cmd_parser.get_value::("id")?.unwrap_or_default(); - - if let Some(mq) = cmd_parser.get_value::("mq")? { - netdevinterfacecfg.mq = mq.inner; - } - netdevinterfacecfg.iothread = cmd_parser.get_value::("iothread")?; - netdevinterfacecfg.mac = cmd_parser.get_value::("mac")?; - if let Some(queue_size) = cmd_parser.get_value::("queue-size")? { - netdevinterfacecfg.queue_size = queue_size; - } - - let netcfg = &vm_config - .netdevs - .remove(&netdev) - .with_context(|| format!("Netdev: {:?} not found for net device", &netdev))?; - netdevinterfacecfg.id = netid; - netdevinterfacecfg.host_dev_name = netcfg.ifname.clone(); - netdevinterfacecfg.tap_fds = netcfg.tap_fds.clone(); - netdevinterfacecfg.vhost_fds = netcfg.vhost_fds.clone(); - netdevinterfacecfg.vhost_type = netcfg.vhost_type.clone(); - netdevinterfacecfg.queues = netcfg.queues; - if let Some(chardev) = &netcfg.chardev { - netdevinterfacecfg.socket_path = Some(get_chardev_socket_path(chardev, vm_config)?); - } - - netdevinterfacecfg.check()?; - Ok(netdevinterfacecfg) -} - fn get_netdev_fd(fd_name: &str) -> Result { if let Some(fd) = QmpChannel::get_fd(fd_name) { Ok(fd) @@ -337,17 +232,12 @@ pub fn get_netdev_config(args: Box) -> Result) -> Result Result<()> { - let mut cmd_parser = CmdParser::new("netdev"); - cmd_parser - .push("") - .push("id") - .push("fd") - .push("fds") - .push("vhost") - .push("ifname") - .push("vhostfd") - .push("vhostfds") - .push("queues") - .push("chardev"); - - cmd_parser.parse(netdev_config)?; - let drive_cfg = parse_netdev(cmd_parser)?; - self.add_netdev_with_config(drive_cfg) + let mut netdev_cfg = + NetDevcfg::try_parse_from(str_slip_to_clap(netdev_config, true, false))?; + netdev_cfg.auto_queues()?; + netdev_cfg.check()?; + self.add_netdev_with_config(netdev_cfg) } pub fn add_netdev_with_config(&mut self, conf: NetDevcfg) -> Result<()> { let netdev_id = conf.id.clone(); - if self.netdevs.get(&netdev_id).is_none() { - self.netdevs.insert(netdev_id, conf); - } else { + if self.netdevs.get(&netdev_id).is_some() { bail!("Netdev {:?} has been added", netdev_id); } + self.netdevs.insert(netdev_id, conf); Ok(()) } pub fn del_netdev_by_id(&mut self, id: &str) -> Result<()> { - if self.netdevs.get(id).is_some() { - self.netdevs.remove(id); - } else { - bail!("Netdev {} not found", id); - } + self.netdevs + .remove(id) + .with_context(|| format!("Netdev {} not found", id))?; + Ok(()) } } +fn valid_mac(mac: &str) -> Result { + if !check_mac_address(mac) { + return Err(anyhow!(ConfigError::MacFormatError)); + } + Ok(mac.to_string()) +} + fn check_mac_address(mac: &str) -> bool { if mac.len() != MAC_ADDRESS_LENGTH { return false; @@ -485,207 +351,136 @@ fn check_mac_address(mac: &str) -> bool { true } -fn is_netdev_queues_valid(queues: u16) -> bool { - queues >= 1 && queues <= MAX_VIRTIO_QUEUE as u16 +fn is_netdev_queues_valid(queues: u16) -> Result<()> { + if !(queues >= 2 && queues <= MAX_VIRTIO_QUEUE as u16) { + return Err(anyhow!(ConfigError::IllegalValue( + "number queues of net device".to_string(), + 1, + true, + MAX_QUEUE_PAIRS as u64, + true, + ))); + } + + Ok(()) } #[cfg(test)] mod tests { use super::*; - use crate::config::{get_pci_bdf, MAX_STRING_LENGTH}; #[test] - fn test_network_config_cmdline_parser() { + fn test_netdev_config_cmdline_parser() { let mut vm_config = VmConfig::default(); + + // Test1: Right. assert!(vm_config.add_netdev("tap,id=eth0,ifname=tap0").is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net0,netdev=eth0,iothread=iothread0", - ); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.id, "net0"); - assert_eq!(network_configs.host_dev_name, "tap0"); - assert_eq!(network_configs.iothread, Some("iothread0".to_string())); - assert!(network_configs.mac.is_none()); - assert!(network_configs.tap_fds.is_none()); - assert!(network_configs.vhost_type.is_none()); - assert!(network_configs.vhost_fds.is_none()); + assert!(vm_config.add_netdev("tap,id=eth0,ifname=tap0").is_err()); + let netdev_cfg = vm_config.netdevs.get("eth0").unwrap(); + assert_eq!(netdev_cfg.id, "eth0"); + assert_eq!(netdev_cfg.ifname, "tap0"); + assert!(netdev_cfg.tap_fds.is_none()); + assert_eq!(netdev_cfg.vhost_kernel, false); + assert!(netdev_cfg.vhost_fds.is_none()); + assert_eq!(netdev_cfg.queues, 2); + assert!(netdev_cfg.vhost_type().is_none()); - let mut vm_config = VmConfig::default(); assert!(vm_config .add_netdev("tap,id=eth1,ifname=tap1,vhost=on,vhostfd=4") .is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net1,netdev=eth1,mac=12:34:56:78:9A:BC", - ); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.id, "net1"); - assert_eq!(network_configs.host_dev_name, "tap1"); - assert_eq!(network_configs.mac, Some(String::from("12:34:56:78:9A:BC"))); - assert!(network_configs.tap_fds.is_none()); - assert_eq!( - network_configs.vhost_type, - Some(String::from("vhost-kernel")) - ); - assert_eq!(network_configs.vhost_fds, Some(vec![4])); + let netdev_cfg = vm_config.netdevs.get("eth1").unwrap(); + assert_eq!(netdev_cfg.ifname, "tap1"); + assert_eq!(netdev_cfg.vhost_type().unwrap(), "vhost-kernel"); + assert_eq!(netdev_cfg.vhost_fds, Some(vec![4])); - let mut vm_config = VmConfig::default(); - assert!(vm_config.add_netdev("tap,id=eth1,fd=35").is_ok()); - let net_cfg_res = parse_net(&mut vm_config, "virtio-net-device,id=net1,netdev=eth1"); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.id, "net1"); - assert_eq!(network_configs.host_dev_name, ""); - assert_eq!(network_configs.tap_fds, Some(vec![35])); + assert!(vm_config.add_netdev("tap,id=eth2,fd=35").is_ok()); + let netdev_cfg = vm_config.netdevs.get("eth2").unwrap(); + assert_eq!(netdev_cfg.tap_fds, Some(vec![35])); - let mut vm_config = VmConfig::default(); assert!(vm_config - .add_netdev("tap,id=eth1,ifname=tap1,vhost=on,vhostfd=4") + .add_netdev("tap,id=eth3,ifname=tap0,queues=4") .is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net1,netdev=eth2,mac=12:34:56:78:9A:BC", - ); - assert!(net_cfg_res.is_err()); + let netdev_cfg = vm_config.netdevs.get("eth3").unwrap(); + assert_eq!(netdev_cfg.queues, 8); - let mut vm_config = VmConfig::default(); - assert!(vm_config.add_netdev("tap,id=eth1,fd=35").is_ok()); - let net_cfg_res = parse_net(&mut vm_config, "virtio-net-device,id=net1,netdev=eth3"); - assert!(net_cfg_res.is_err()); - - // multi queue testcases - let mut vm_config = VmConfig::default(); assert!(vm_config - .add_netdev("tap,id=eth0,ifname=tap0,queues=4") + .add_netdev("tap,id=eth4,fds=34:35:36:37:38") .is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net0,netdev=eth0,iothread=iothread0,mq=on,vectors=6", - ); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.queues, 8); - assert_eq!(network_configs.mq, true); + let netdev_cfg = vm_config.netdevs.get("eth4").unwrap(); + assert_eq!(netdev_cfg.queues, 10); + assert_eq!(netdev_cfg.tap_fds, Some(vec![34, 35, 36, 37, 38])); - let mut vm_config = VmConfig::default(); assert!(vm_config - .add_netdev("tap,id=eth0,fds=34:35:36:37:38") + .add_netdev("tap,id=eth5,fds=34:35:36:37:38,vhost=on,vhostfds=39:40:41:42:43") .is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net0,netdev=eth0,iothread=iothread0,mq=off,vectors=12", - ); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.queues, 10); - assert_eq!(network_configs.tap_fds, Some(vec![34, 35, 36, 37, 38])); - assert_eq!(network_configs.mq, false); + let netdev_cfg = vm_config.netdevs.get("eth5").unwrap(); + assert_eq!(netdev_cfg.queues, 10); + assert_eq!(netdev_cfg.vhost_fds, Some(vec![39, 40, 41, 42, 43])); - let mut vm_config = VmConfig::default(); + // Test2: Missing values assert!(vm_config - .add_netdev("tap,id=eth0,fds=34:35:36:37:38,vhost=on,vhostfds=39:40:41:42:43") - .is_ok()); - let net_cfg_res = parse_net( - &mut vm_config, - "virtio-net-device,id=net0,netdev=eth0,iothread=iothread0,mq=off,vectors=12", - ); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.queues, 10); - assert_eq!(network_configs.vhost_fds, Some(vec![39, 40, 41, 42, 43])); - assert_eq!(network_configs.mq, false); + .add_netdev("tap,fds=34:35:36:37:38,vhost=on") + .is_err()); + + // Test3: Illegal values. + assert!(vm_config + .add_netdev("tap,id=eth10,fds=34:35:36:37:38,vhost=on,vhostfds=39,40,41,42,43") + .is_err()); + assert!(vm_config.add_netdev("tap,id=eth10,queues=0").is_err()); + assert!(vm_config.add_netdev("tap,id=eth10,queues=17").is_err()); } #[test] - fn test_pci_network_config_cmdline_parser() { + fn test_networkinterface_config_cmdline_parser() { + // Test1: Right. let mut vm_config = VmConfig::default(); - assert!(vm_config .add_netdev("tap,id=eth1,ifname=tap1,vhost=on,vhostfd=4") .is_ok()); + let net_cmd = + "virtio-net-pci,id=net1,netdev=eth1,bus=pcie.0,addr=0x1.0x2,mac=12:34:56:78:9A:BC,mq=on,vectors=6,queue-size=2048,multifunction=on"; let net_cfg = - "virtio-net-pci,id=net1,netdev=eth1,bus=pcie.0,addr=0x1.0x2,mac=12:34:56:78:9A:BC"; - let net_cfg_res = parse_net(&mut vm_config, net_cfg); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.id, "net1"); - assert_eq!(network_configs.host_dev_name, "tap1"); - assert_eq!(network_configs.mac, Some(String::from("12:34:56:78:9A:BC"))); - assert!(network_configs.tap_fds.is_none()); - assert_eq!( - network_configs.vhost_type, - Some(String::from("vhost-kernel")) - ); - assert_eq!(network_configs.vhost_fds.unwrap()[0], 4); - let pci_bdf = get_pci_bdf(net_cfg); - assert!(pci_bdf.is_ok()); - let pci = pci_bdf.unwrap(); - assert_eq!(pci.bus, "pcie.0".to_string()); - assert_eq!(pci.addr, (1, 2)); - - let net_cfg_res = parse_net(&mut vm_config, net_cfg); - assert!(net_cfg_res.is_err()); - + NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)).unwrap(); + assert_eq!(net_cfg.id, "net1"); + assert_eq!(net_cfg.netdev, "eth1"); + assert_eq!(net_cfg.bus.unwrap(), "pcie.0"); + assert_eq!(net_cfg.addr.unwrap(), (1, 2)); + assert_eq!(net_cfg.mac.unwrap(), "12:34:56:78:9A:BC"); + assert_eq!(net_cfg.vectors, 6); + assert_eq!(net_cfg.mq, true); + assert_eq!(net_cfg.queue_size, 2048); + assert_eq!(net_cfg.multifunction, Some(true)); + let netdev_cfg = vm_config.netdevs.get(&net_cfg.netdev).unwrap(); + assert_eq!(netdev_cfg.vhost_type().unwrap(), "vhost-kernel"); + + // Test2: Default values. let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_netdev("tap,id=eth1,ifname=tap1,vhost=on,vhostfd=4") - .is_ok()); - let net_cfg = - "virtio-net-pci,id=net1,netdev=eth1,bus=pcie.0,addr=0x1.0x2,mac=12:34:56:78:9A:BC,multifunction=on"; - assert!(parse_net(&mut vm_config, net_cfg).is_ok()); - - // For vhost-user net assert!(vm_config.add_netdev("vhost-user,id=netdevid").is_ok()); - let net_cfg = + let net_cmd = "virtio-net-pci,id=netid,netdev=netdevid,bus=pcie.0,addr=0x2.0x0,mac=12:34:56:78:9A:BC"; - let net_cfg_res = parse_net(&mut vm_config, net_cfg); - assert!(net_cfg_res.is_ok()); - let network_configs = net_cfg_res.unwrap(); - assert_eq!(network_configs.id, "netid"); - assert_eq!(network_configs.vhost_type, Some("vhost-user".to_string())); - assert_eq!(network_configs.mac, Some("12:34:56:78:9A:BC".to_string())); - - assert!(vm_config - .add_netdev("vhost-user,id=netdevid2,chardev=chardevid2") - .is_ok()); let net_cfg = - "virtio-net-pci,id=netid2,netdev=netdevid2,bus=pcie.0,addr=0x2.0x0,mac=12:34:56:78:9A:BC"; - let net_cfg_res = parse_net(&mut vm_config, net_cfg); - assert!(net_cfg_res.is_err()); - } - - #[test] - fn test_netdev_config_check() { - let mut netdev_conf = NetDevcfg::default(); - for _ in 0..MAX_STRING_LENGTH { - netdev_conf.id += "A"; - } - assert!(netdev_conf.check().is_ok()); - - // Overflow - netdev_conf.id += "A"; - assert!(netdev_conf.check().is_err()); - - let mut netdev_conf = NetDevcfg::default(); - for _ in 0..MAX_STRING_LENGTH { - netdev_conf.ifname += "A"; - } - assert!(netdev_conf.check().is_ok()); - - // Overflow - netdev_conf.ifname += "A"; - assert!(netdev_conf.check().is_err()); - - let mut netdev_conf = NetDevcfg::default(); - netdev_conf.vhost_type = None; - assert!(netdev_conf.check().is_ok()); - netdev_conf.vhost_type = Some(String::from("vhost-kernel")); - assert!(netdev_conf.check().is_ok()); - netdev_conf.vhost_type = Some(String::from("vhost-")); - assert!(netdev_conf.check().is_err()); + NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)).unwrap(); + assert_eq!(net_cfg.queue_size, 256); + assert_eq!(net_cfg.mq, false); + assert_eq!(net_cfg.vectors, 0); + let netdev_cfg = vm_config.netdevs.get(&net_cfg.netdev).unwrap(); + assert_eq!(netdev_cfg.vhost_type().unwrap(), "vhost-user"); + + // Test3: Missing Parameters. + let net_cmd = "virtio-net-pci,id=netid"; + let result = NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)); + assert!(result.is_err()); + + // Test4: Illegal Parameters. + let net_cmd = "virtio-net-pci,id=netid,netdev=netdevid,mac=1:1:1"; + let result = NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)); + assert!(result.is_err()); + let net_cmd = "virtio-net-pci,id=netid,netdev=netdevid,queue-size=128"; + let result = NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)); + assert!(result.is_err()); + let net_cmd = "virtio-net-pci,id=netid,netdev=netdevid,queue-size=10240"; + let result = NetworkInterfaceConfig::try_parse_from(str_slip_to_clap(net_cmd, true, false)); + assert!(result.is_err()); } #[test] @@ -811,9 +606,9 @@ mod tests { ..qmp_schema::NetDevAddArgument::default() }); let net_cfg = get_netdev_config(netdev).unwrap(); + assert_eq!(net_cfg.vhost_type().unwrap(), "vhost-kernel"); assert_eq!(net_cfg.tap_fds.unwrap()[0], 11); assert_eq!(net_cfg.vhost_fds.unwrap()[0], 21); - assert_eq!(net_cfg.vhost_type.unwrap(), "vhost-kernel"); } // Normal test with 'vhostfds'. @@ -831,9 +626,9 @@ mod tests { ..qmp_schema::NetDevAddArgument::default() }); let net_cfg = get_netdev_config(netdev).unwrap(); - assert_eq!(net_cfg.tap_fds.unwrap(), [11, 12, 13, 14]); - assert_eq!(net_cfg.vhost_fds.unwrap(), [21, 22, 23, 24]); - assert_eq!(net_cfg.vhost_type.unwrap(), "vhost-kernel"); + assert_eq!(net_cfg.vhost_type().unwrap(), "vhost-kernel"); + assert_eq!(net_cfg.tap_fds.unwrap(), vec![11, 12, 13, 14]); + assert_eq!(net_cfg.vhost_fds.unwrap(), vec![21, 22, 23, 24]); } let err_msgs = [ @@ -859,8 +654,7 @@ mod tests { ..qmp_schema::NetDevAddArgument::default() }); let err_msg = format!( - "The 'queues' {} is bigger than max queue num {}", - MAX_QUEUE_PAIRS + 1, + "number queues of net device must >= 1 and <= {}.", MAX_QUEUE_PAIRS ); check_err_msg(netdev, &err_msg); @@ -929,6 +723,7 @@ mod tests { fds: Some(fds.to_string()), ..qmp_schema::NetDevAddArgument::default() }); + // number queues of net device let err_msg = format!( "The num of fd {} is bigger than max queue num {}", MAX_QUEUE_PAIRS + 1, diff --git a/machine_manager/src/config/numa.rs b/machine_manager/src/config/numa.rs index a9a0bfa..664b180 100644 --- a/machine_manager/src/config/numa.rs +++ b/machine_manager/src/config/numa.rs @@ -12,29 +12,17 @@ use std::cmp::max; use std::collections::{BTreeMap, HashSet}; +use std::str::FromStr; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; +use clap::Parser; use super::error::ConfigError; -use crate::config::{CmdParser, IntegerList, VmConfig, MAX_NODES}; +use super::{get_class_type, str_slip_to_clap}; +use crate::config::{IntegerList, VmConfig, MAX_NODES}; const MIN_NUMA_DISTANCE: u8 = 10; -#[derive(Default, Debug)] -pub struct NumaDistance { - pub destination: u32, - pub distance: u8, -} - -#[derive(Default, Debug)] -pub struct NumaConfig { - pub numa_id: u32, - pub cpus: Vec, - pub distances: Option>, - pub size: u64, - pub mem_dev: String, -} - #[derive(Default)] pub struct NumaNode { pub cpus: Vec, @@ -109,126 +97,83 @@ pub fn complete_numa_node(numa_nodes: &mut NumaNodes, nr_cpus: u8, mem_size: u64 Ok(()) } -/// Parse the NUMA node memory parameters. -/// -/// # Arguments -/// -/// * `numa_config` - The NUMA node configuration. -pub fn parse_numa_mem(numa_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("numa"); - cmd_parser - .push("") - .push("nodeid") - .push("cpus") - .push("memdev"); - cmd_parser.parse(numa_config)?; - - let mut config: NumaConfig = NumaConfig::default(); - if let Some(node_id) = cmd_parser.get_value::("nodeid")? { - if node_id >= MAX_NODES { - return Err(anyhow!(ConfigError::IllegalValue( - "nodeid".to_string(), - 0, - true, - MAX_NODES as u64, - false, - ))); - } - config.numa_id = node_id; - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "nodeid".to_string(), - "numa".to_string() - ))); - } - if let Some(mut cpus) = cmd_parser - .get_value::("cpus") +#[derive(Parser)] +#[command(no_binary_name(true))] +pub struct NumaNodeConfig { + #[arg(long, value_parser = ["node"])] + pub classtype: String, + #[arg(long, alias = "nodeid", value_parser = clap::value_parser!(u32).range(..MAX_NODES as i64))] + pub numa_id: u32, + #[arg(long, value_parser = get_cpus)] + pub cpus: ::std::vec::Vec, + #[arg(long, alias = "memdev")] + pub mem_dev: String, +} + +fn get_cpus(cpus_str: &str) -> Result> { + let mut cpus = IntegerList::from_str(cpus_str) .with_context(|| ConfigError::ConvertValueFailed(String::from("u8"), "cpus".to_string()))? - .map(|v| v.0.iter().map(|e| *e as u8).collect::>()) - { - cpus.sort_unstable(); - config.cpus = cpus; - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "cpus".to_string(), - "numa".to_string() - ))); + .0 + .iter() + .map(|e| *e as u8) + .collect::>(); + + if cpus.is_empty() { + bail!("Got empty cpus list!"); } - config.mem_dev = cmd_parser - .get_value::("memdev")? - .with_context(|| ConfigError::FieldIsMissing("memdev".to_string(), "numa".to_string()))?; - Ok(config) + cpus.sort_unstable(); + + Ok(cpus) } -/// Parse the NUMA node distance parameters. +/// Parse the NUMA node memory parameters. /// /// # Arguments /// -/// * `numa_dist` - The NUMA node distance configuration. -pub fn parse_numa_distance(numa_dist: &str) -> Result<(u32, NumaDistance)> { - let mut cmd_parser = CmdParser::new("numa"); - cmd_parser.push("").push("src").push("dst").push("val"); - cmd_parser.parse(numa_dist)?; - - let mut dist: NumaDistance = NumaDistance::default(); - let numa_id = if let Some(src) = cmd_parser.get_value::("src")? { - if src >= MAX_NODES { - return Err(anyhow!(ConfigError::IllegalValue( - "src".to_string(), - 0, - true, - MAX_NODES as u64, - false, - ))); - } - src - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "src".to_string(), - "numa".to_string() - ))); - }; - if let Some(dst) = cmd_parser.get_value::("dst")? { - if dst >= MAX_NODES { - return Err(anyhow!(ConfigError::IllegalValue( - "dst".to_string(), - 0, - true, - MAX_NODES as u64, - false, - ))); - } - dist.destination = dst; - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "dst".to_string(), - "numa".to_string() - ))); - } - if let Some(val) = cmd_parser.get_value::("val")? { - if val < MIN_NUMA_DISTANCE { - bail!("NUMA distance shouldn't be less than 10"); - } - if numa_id == dist.destination && val != MIN_NUMA_DISTANCE { - bail!("Local distance of node {} should be 10.", numa_id); +/// * `numa_config` - The NUMA node configuration. +pub fn parse_numa_mem(numa_config: &str) -> Result { + let config = NumaNodeConfig::try_parse_from(str_slip_to_clap(numa_config, true, false))?; + Ok(config) +} + +#[derive(Parser)] +#[command(no_binary_name(true))] +pub struct NumaDistConfig { + #[arg(long, value_parser = ["dist"])] + pub classtype: String, + #[arg(long, alias = "src", value_parser = clap::value_parser!(u32).range(..MAX_NODES as i64))] + pub numa_id: u32, + #[arg(long, alias = "dst", value_parser = clap::value_parser!(u32).range(..MAX_NODES as i64))] + pub destination: u32, + #[arg(long, alias = "val", value_parser = clap::value_parser!(u8).range(MIN_NUMA_DISTANCE as i64..))] + pub distance: u8, +} + +impl NumaDistConfig { + fn check(&self) -> Result<()> { + if self.numa_id == self.destination && self.distance != MIN_NUMA_DISTANCE { + bail!("Local distance of node {} should be 10.", self.numa_id); } - if numa_id != dist.destination && val == MIN_NUMA_DISTANCE { + if self.numa_id != self.destination && self.distance == MIN_NUMA_DISTANCE { bail!( "Remote distance of node {} should be more than 10.", - numa_id + self.numa_id ); } - - dist.distance = val; - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "val".to_string(), - "numa".to_string() - ))); + Ok(()) } +} - Ok((numa_id, dist)) +/// Parse the NUMA node distance parameters. +/// +/// # Arguments +/// +/// * `numa_dist` - The NUMA node distance configuration. +pub fn parse_numa_distance(numa_dist: &str) -> Result { + let dist_cfg = NumaDistConfig::try_parse_from(str_slip_to_clap(numa_dist, true, false))?; + dist_cfg.check()?; + Ok(dist_cfg) } impl VmConfig { @@ -238,13 +183,8 @@ impl VmConfig { /// /// * `numa_config` - The NUMA node configuration. pub fn add_numa(&mut self, numa_config: &str) -> Result<()> { - let mut cmd_params = CmdParser::new("numa"); - cmd_params.push(""); - - cmd_params.get_parameters(numa_config)?; - if let Some(numa_type) = cmd_params.get_value::("")? { - self.numa_nodes.push((numa_type, numa_config.to_string())); - } + let numa_type = get_class_type(numa_config).with_context(|| "Numa type not specified")?; + self.numa_nodes.push((numa_type, numa_config.to_string())); Ok(()) } @@ -258,17 +198,15 @@ mod tests { fn test_parse_numa_mem() { let mut vm_config = VmConfig::default(); assert!(vm_config - .add_numa("-numa node,nodeid=0,cpus=0-1,memdev=mem0") - .is_ok()); - assert!(vm_config - .add_numa("-numa node,nodeid=1,cpus=2-1,memdev=mem1") + .add_numa("node,nodeid=0,cpus=0-1,memdev=mem0") .is_ok()); assert!(vm_config - .add_numa("-numa node,nodeid=2,memdev=mem2") + .add_numa("node,nodeid=1,cpus=2-1,memdev=mem1") .is_ok()); - assert!(vm_config.add_numa("-numa node,nodeid=3,cpus=3-4").is_ok()); + assert!(vm_config.add_numa("node,nodeid=2,memdev=mem2").is_ok()); + assert!(vm_config.add_numa("node,nodeid=3,cpus=3-4").is_ok()); assert!(vm_config - .add_numa("-numa node,nodeid=0,cpus=[0-1:3-5],memdev=mem0") + .add_numa("node,nodeid=0,cpus=[0-1:3-5],memdev=mem0") .is_ok()); let numa = vm_config.numa_nodes.get(0).unwrap(); @@ -291,17 +229,17 @@ mod tests { #[test] fn test_parse_numa_distance() { let mut vm_config = VmConfig::default(); - assert!(vm_config.add_numa("-numa dist,src=0,dst=1,val=15").is_ok()); - assert!(vm_config.add_numa("-numa dist,dst=1,val=10").is_ok()); - assert!(vm_config.add_numa("-numa dist,src=0,val=10").is_ok()); - assert!(vm_config.add_numa("-numa dist,src=0,dst=1").is_ok()); - assert!(vm_config.add_numa("-numa dist,src=0,dst=1,val=10").is_ok()); + assert!(vm_config.add_numa("dist,src=0,dst=1,val=15").is_ok()); + assert!(vm_config.add_numa("dist,dst=1,val=10").is_ok()); + assert!(vm_config.add_numa("dist,src=0,val=10").is_ok()); + assert!(vm_config.add_numa("dist,src=0,dst=1").is_ok()); + assert!(vm_config.add_numa("dist,src=0,dst=1,val=10").is_ok()); let numa = vm_config.numa_nodes.get(0).unwrap(); - let dist = parse_numa_distance(numa.1.as_str()).unwrap(); - assert_eq!(dist.0, 0); - assert_eq!(dist.1.destination, 1); - assert_eq!(dist.1.distance, 15); + let dist_cfg = parse_numa_distance(numa.1.as_str()).unwrap(); + assert_eq!(dist_cfg.numa_id, 0); + assert_eq!(dist_cfg.destination, 1); + assert_eq!(dist_cfg.distance, 15); let numa = vm_config.numa_nodes.get(1).unwrap(); assert!(parse_numa_distance(numa.1.as_str()).is_err()); diff --git a/machine_manager/src/config/pci.rs b/machine_manager/src/config/pci.rs index e1ad498..6642f4f 100644 --- a/machine_manager/src/config/pci.rs +++ b/machine_manager/src/config/pci.rs @@ -13,9 +13,7 @@ use anyhow::{bail, Context, Result}; use serde::{Deserialize, Serialize}; -use super::error::ConfigError; -use super::{CmdParser, ConfigCheck, UnsignedInteger}; -use crate::config::{check_arg_too_long, ExBool}; +use super::get_value_of_parameter; use util::num_ops::str_to_num; /// Basic information of pci devices such as bus number, @@ -43,30 +41,6 @@ impl Default for PciBdf { } } -/// Basic information of RootPort like port number. -#[derive(Debug, Clone)] -pub struct RootPortConfig { - pub port: u8, - pub id: String, - pub multifunction: bool, -} - -impl ConfigCheck for RootPortConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "root_port id") - } -} - -impl Default for RootPortConfig { - fn default() -> Self { - RootPortConfig { - port: 0, - id: "".to_string(), - multifunction: false, - } - } -} - pub fn get_pci_df(addr: &str) -> Result<(u8, u8)> { let addr_vec: Vec<&str> = addr.split('.').collect(); if addr_vec.len() > 2 { @@ -96,89 +70,15 @@ pub fn get_pci_df(addr: &str) -> Result<(u8, u8)> { } pub fn get_pci_bdf(pci_cfg: &str) -> Result { - let mut cmd_parser = CmdParser::new("bdf"); - cmd_parser.push("").push("bus").push("addr"); - cmd_parser.get_parameters(pci_cfg)?; - - let mut pci_bdf = PciBdf { - bus: cmd_parser - .get_value::("bus")? - .with_context(|| "Bus not specified for pci device")?, - ..Default::default() - }; - if let Some(addr) = cmd_parser.get_value::("addr")? { - pci_bdf.addr = get_pci_df(&addr).with_context(|| "Failed to get addr")?; - } else { - bail!("No addr found for pci device"); + let bus = get_value_of_parameter("bus", pci_cfg)?; + let addr_str = get_value_of_parameter("addr", pci_cfg)?; + if addr_str.is_empty() { + bail!("Invalid addr."); } - Ok(pci_bdf) -} - -pub fn get_multi_function(pci_cfg: &str) -> Result { - let mut cmd_parser = CmdParser::new("multifunction"); - cmd_parser.push("").push("multifunction"); - cmd_parser.get_parameters(pci_cfg)?; - - if let Some(multi_func) = cmd_parser - .get_value::("multifunction") - .with_context(|| "Failed to get multifunction parameter, please set on or off (default).")? - { - return Ok(multi_func.inner); - } - - Ok(false) -} - -pub fn parse_root_port(rootport_cfg: &str) -> Result { - let mut cmd_parser = CmdParser::new("pcie-root-port"); - cmd_parser - .push("") - .push("bus") - .push("addr") - .push("port") - .push("chassis") - .push("multifunction") - .push("id"); - cmd_parser.parse(rootport_cfg)?; - - let root_port = RootPortConfig { - port: cmd_parser - .get_value::("port")? - .with_context(|| { - ConfigError::FieldIsMissing("port".to_string(), "rootport".to_string()) - })? - .0 as u8, - id: cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "rootport".to_string()) - })?, - multifunction: cmd_parser - .get_value::("multifunction")? - .map_or(false, bool::from), - }; - - let _ = cmd_parser.get_value::("chassis")?; + let addr = get_pci_df(&addr_str).with_context(|| "Failed to get addr")?; + let pci_bdf = PciBdf::new(bus, addr); - root_port.check()?; - Ok(root_port) -} - -pub fn pci_args_check(cmd_parser: &CmdParser) -> Result<()> { - let device_type = cmd_parser.get_value::("")?; - let dev_type = device_type.unwrap(); - // Safe, because this function only be called when certain - // devices type are added. - if dev_type.ends_with("-device") { - if cmd_parser.get_value::("bus")?.is_some() { - bail!("virtio mmio device does not support bus arguments"); - } - if cmd_parser.get_value::("addr")?.is_some() { - bail!("virtio mmio device does not support addr arguments"); - } - if cmd_parser.get_value::("multifunction")?.is_some() { - bail!("virtio mmio device does not support multifunction arguments"); - } - } - Ok(()) + Ok(pci_bdf) } #[cfg(test)] @@ -241,26 +141,4 @@ mod tests { let pci_bdf = get_pci_bdf("virtio-balloon-device,addr=0x1.0x2"); assert!(pci_bdf.is_err()); } - - #[test] - fn test_get_multi_function() { - assert_eq!( - get_multi_function("virtio-balloon-device,bus=pcie.0,addr=0x1.0x2").unwrap(), - false - ); - assert_eq!( - get_multi_function("virtio-balloon-device,bus=pcie.0,addr=0x1.0x2,multifunction=on") - .unwrap(), - true - ); - assert_eq!( - get_multi_function("virtio-balloon-device,bus=pcie.0,addr=0x1.0x2,multifunction=off") - .unwrap(), - false - ); - assert!(get_multi_function( - "virtio-balloon-device,bus=pcie.0,addr=0x1.0x2,multifunction=close" - ) - .is_err()); - } } diff --git a/machine_manager/src/config/pvpanic_pci.rs b/machine_manager/src/config/pvpanic_pci.rs deleted file mode 100644 index d0c3b87..0000000 --- a/machine_manager/src/config/pvpanic_pci.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use crate::config::{CmdParser, ConfigCheck}; -use anyhow::{bail, Context, Result}; -use serde::{Deserialize, Serialize}; - -pub const PVPANIC_PANICKED: u32 = 1 << 0; -pub const PVPANIC_CRASHLOADED: u32 = 1 << 1; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PvpanicDevConfig { - pub id: String, - pub supported_features: u32, -} - -impl Default for PvpanicDevConfig { - fn default() -> Self { - PvpanicDevConfig { - id: "".to_string(), - supported_features: PVPANIC_PANICKED | PVPANIC_CRASHLOADED, - } - } -} - -impl ConfigCheck for PvpanicDevConfig { - fn check(&self) -> Result<()> { - Ok(()) - } -} - -pub fn parse_pvpanic(args_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("pvpanic"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("supported-features"); - cmd_parser.parse(args_config)?; - - let mut pvpanicdevcfg = PvpanicDevConfig::default(); - - if let Some(features) = cmd_parser.get_value::("supported-features")? { - pvpanicdevcfg.supported_features = - match features & !(PVPANIC_PANICKED | PVPANIC_CRASHLOADED) { - 0 => features, - _ => bail!("Unsupported pvpanic device features {}", features), - } - } - - pvpanicdevcfg.id = cmd_parser - .get_value::("id")? - .with_context(|| "No id configured for pvpanic device")?; - - Ok(pvpanicdevcfg) -} diff --git a/machine_manager/src/config/rng.rs b/machine_manager/src/config/rng.rs index b153dcf..78c3ef7 100644 --- a/machine_manager/src/config/rng.rs +++ b/machine_manager/src/config/rng.rs @@ -10,243 +10,18 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use serde::{Deserialize, Serialize}; -use super::error::ConfigError; -use super::pci_args_check; -use crate::config::{CmdParser, ConfigCheck, VmConfig, MAX_PATH_LENGTH}; +use crate::config::{valid_id, valid_path}; -const MIN_BYTES_PER_SEC: u64 = 64; -const MAX_BYTES_PER_SEC: u64 = 1_000_000_000; - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct RngObjConfig { + #[arg(long, value_parser = ["rng-random"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] pub id: String, + #[arg(long, value_parser = valid_path)] pub filename: String, } - -/// Config structure for virtio-rng. -#[derive(Debug, Clone, Default)] -pub struct RngConfig { - pub id: String, - pub random_file: String, - pub bytes_per_sec: Option, -} - -impl ConfigCheck for RngConfig { - fn check(&self) -> Result<()> { - if self.id.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "rng id".to_string(), - MAX_PATH_LENGTH - ))); - } - - if self.random_file.len() > MAX_PATH_LENGTH { - return Err(anyhow!(ConfigError::StringLengthTooLong( - "rng random file".to_string(), - MAX_PATH_LENGTH, - ))); - } - - if let Some(bytes_per_sec) = self.bytes_per_sec { - if !(MIN_BYTES_PER_SEC..=MAX_BYTES_PER_SEC).contains(&bytes_per_sec) { - return Err(anyhow!(ConfigError::IllegalValue( - "The bytes per second of rng device".to_string(), - MIN_BYTES_PER_SEC, - true, - MAX_BYTES_PER_SEC, - true, - ))); - } - } - - Ok(()) - } -} - -pub fn parse_rng_dev(vm_config: &mut VmConfig, rng_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("rng"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("multifunction") - .push("max-bytes") - .push("period") - .push("rng"); - - cmd_parser.parse(rng_config)?; - pci_args_check(&cmd_parser)?; - let mut rng_cfg = RngConfig::default(); - let rng = cmd_parser - .get_value::("rng")? - .with_context(|| ConfigError::FieldIsMissing("rng".to_string(), "rng".to_string()))?; - - rng_cfg.id = cmd_parser.get_value::("id")?.unwrap_or_default(); - - if let Some(max) = cmd_parser.get_value::("max-bytes")? { - if let Some(peri) = cmd_parser.get_value::("period")? { - let mul = max - .checked_mul(1000) - .with_context(|| format!("Illegal max-bytes arguments: {:?}", max))?; - let div = mul - .checked_div(peri) - .with_context(|| format!("Illegal period arguments: {:?}", peri))?; - rng_cfg.bytes_per_sec = Some(div); - } else { - bail!("Argument 'period' is missing"); - } - } else if cmd_parser.get_value::("period")?.is_some() { - bail!("Argument 'max-bytes' is missing"); - } - - rng_cfg.random_file = vm_config - .object - .rng_object - .remove(&rng) - .map(|rng_object| rng_object.filename) - .with_context(|| "Object for rng-random device not found")?; - - rng_cfg.check()?; - Ok(rng_cfg) -} - -pub fn parse_rng_obj(object_args: &str) -> Result { - let mut cmd_params = CmdParser::new("rng-object"); - cmd_params.push("").push("id").push("filename"); - - cmd_params.parse(object_args)?; - let id = cmd_params - .get_value::("id")? - .with_context(|| ConfigError::FieldIsMissing("id".to_string(), "rng-object".to_string()))?; - let filename = cmd_params - .get_value::("filename")? - .with_context(|| { - ConfigError::FieldIsMissing("filename".to_string(), "rng-object".to_string()) - })?; - let rng_obj_cfg = RngObjConfig { id, filename }; - - Ok(rng_obj_cfg) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::config::get_pci_bdf; - - #[test] - fn test_rng_config_cmdline_parser_01() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev(&mut vm_config, "virtio-rng-device,rng=objrng0"); - assert!(rng_config.is_ok()); - let config = rng_config.unwrap(); - assert_eq!(config.random_file, "/path/to/random_file"); - assert_eq!(config.bytes_per_sec, None); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev( - &mut vm_config, - "virtio-rng-device,rng=objrng0,max-bytes=1234,period=1000", - ); - assert!(rng_config.is_ok()); - let config = rng_config.unwrap(); - assert_eq!(config.random_file, "/path/to/random_file"); - assert_eq!(config.bytes_per_sec, Some(1234)); - } - - #[test] - fn test_rng_config_cmdline_parser_02() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev( - &mut vm_config, - "virtio-rng-device,rng=objrng0,max-bytes=63,period=1000", - ); - assert!(rng_config.is_err()); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev( - &mut vm_config, - "virtio-rng-device,rng=objrng0,max-bytes=64,period=1000", - ); - assert!(rng_config.is_ok()); - let config = rng_config.unwrap(); - assert_eq!(config.random_file, "/path/to/random_file"); - assert_eq!(config.bytes_per_sec, Some(64)); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev( - &mut vm_config, - "virtio-rng-device,rng=objrng0,max-bytes=1000000000,period=1000", - ); - assert!(rng_config.is_ok()); - let config = rng_config.unwrap(); - assert_eq!(config.random_file, "/path/to/random_file"); - assert_eq!(config.bytes_per_sec, Some(1000000000)); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_config = parse_rng_dev( - &mut vm_config, - "virtio-rng-device,rng=objrng0,max-bytes=1000000001,period=1000", - ); - assert!(rng_config.is_err()); - } - - #[test] - fn test_pci_rng_config_cmdline_parser() { - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_cfg = "virtio-rng-pci,rng=objrng0,bus=pcie.0,addr=0x1.0x3"; - let rng_config = parse_rng_dev(&mut vm_config, rng_cfg); - assert!(rng_config.is_ok()); - let config = rng_config.unwrap(); - assert_eq!(config.random_file, "/path/to/random_file"); - assert_eq!(config.bytes_per_sec, None); - let pci_bdf = get_pci_bdf(rng_cfg); - assert!(pci_bdf.is_ok()); - let pci = pci_bdf.unwrap(); - assert_eq!(pci.bus, "pcie.0".to_string()); - assert_eq!(pci.addr, (1, 3)); - - // object "objrng0" has been removed. - let rng_config = parse_rng_dev(&mut vm_config, rng_cfg); - assert!(rng_config.is_err()); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_cfg = "virtio-rng-device,rng=objrng0,bus=pcie.0,addr=0x1.0x3"; - let rng_config = parse_rng_dev(&mut vm_config, rng_cfg); - assert!(rng_config.is_err()); - - let mut vm_config = VmConfig::default(); - assert!(vm_config - .add_object("rng-random,id=objrng0,filename=/path/to/random_file") - .is_ok()); - let rng_cfg = "virtio-rng-pci,rng=objrng0,bus=pcie.0,addr=0x1.0x3,multifunction=on"; - assert!(parse_rng_dev(&mut vm_config, rng_cfg).is_ok()); - } -} diff --git a/machine_manager/src/config/sasl_auth.rs b/machine_manager/src/config/sasl_auth.rs index 506763a..37b47bc 100644 --- a/machine_manager/src/config/sasl_auth.rs +++ b/machine_manager/src/config/sasl_auth.rs @@ -10,44 +10,33 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; +use clap::Parser; use serde::{Deserialize, Serialize}; -use crate::config::{ - ConfigError, {CmdParser, VmConfig}, -}; +use crate::config::{str_slip_to_clap, valid_id, ConfigError, VmConfig}; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SaslAuthObjConfig { - /// Object Id. + #[arg(long, value_parser = ["authz-simple"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] pub id: String, /// Authentication User Name. + #[arg(long, default_value = "")] pub identity: String, } impl VmConfig { pub fn add_saslauth(&mut self, saslauth_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("authz-simple"); - cmd_parser.push("").push("id").push("identity"); - cmd_parser.parse(saslauth_config)?; - - let mut saslauth = SaslAuthObjConfig { - id: cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "vnc sasl_auth".to_string()) - })?, - ..Default::default() - }; - - if let Some(identity) = cmd_parser.get_value::("identity")? { - saslauth.identity = identity; - } - + let saslauth = + SaslAuthObjConfig::try_parse_from(str_slip_to_clap(saslauth_config, true, false))?; let id = saslauth.id.clone(); - if self.object.sasl_object.get(&id).is_none() { - self.object.sasl_object.insert(id, saslauth); - } else { + if self.object.sasl_object.get(&id).is_some() { return Err(anyhow!(ConfigError::IdRepeat("saslauth".to_string(), id))); } + self.object.sasl_object.insert(id, saslauth); Ok(()) } diff --git a/machine_manager/src/config/scsi.rs b/machine_manager/src/config/scsi.rs deleted file mode 100644 index b73833b..0000000 --- a/machine_manager/src/config/scsi.rs +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{anyhow, bail, Context, Result}; - -use super::{error::ConfigError, pci_args_check, DiskFormat}; -use crate::config::{ - check_arg_too_long, CmdParser, ConfigCheck, VmConfig, DEFAULT_VIRTQUEUE_SIZE, MAX_VIRTIO_QUEUE, -}; -use util::aio::AioEngine; - -/// According to Virtio Spec. -/// Max_channel should be 0. -/// Max_target should be less than or equal to 255. -pub const VIRTIO_SCSI_MAX_TARGET: u16 = 255; -/// Max_lun should be less than or equal to 16383 (2^14 - 1). -pub const VIRTIO_SCSI_MAX_LUN: u16 = 16383; - -/// Only support peripheral device addressing format(8 bits for lun) in stratovirt now. -/// So, max lun id supported is 255 (2^8 - 1). -const SUPPORT_SCSI_MAX_LUN: u16 = 255; - -// Seg_max = queue_size - 2. So, size of each virtqueue for virtio-scsi should be larger than 2. -const MIN_QUEUE_SIZE_SCSI: u16 = 2; -// Max size of each virtqueue for virtio-scsi. -const MAX_QUEUE_SIZE_SCSI: u16 = 1024; - -#[derive(Debug, Clone)] -pub struct ScsiCntlrConfig { - /// Virtio-scsi-pci device id. - pub id: String, - /// Thread name of io handler. - pub iothread: Option, - /// Number of scsi cmd queues. - pub queues: u32, - /// Boot path of this scsi controller. It's prefix of scsi device's boot path. - pub boot_prefix: Option, - /// Virtqueue size for all queues. - pub queue_size: u16, -} - -impl Default for ScsiCntlrConfig { - fn default() -> Self { - ScsiCntlrConfig { - id: "".to_string(), - iothread: None, - // At least 1 cmd queue. - queues: 1, - boot_prefix: None, - queue_size: DEFAULT_VIRTQUEUE_SIZE, - } - } -} - -impl ConfigCheck for ScsiCntlrConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.id, "virtio-scsi-pci device id")?; - - if self.iothread.is_some() { - check_arg_too_long(self.iothread.as_ref().unwrap(), "iothread name")?; - } - - if self.queues < 1 || self.queues > MAX_VIRTIO_QUEUE as u32 { - return Err(anyhow!(ConfigError::IllegalValue( - "queues number of scsi controller".to_string(), - 1, - true, - MAX_VIRTIO_QUEUE as u64, - true, - ))); - } - - if self.queue_size <= MIN_QUEUE_SIZE_SCSI || self.queue_size > MAX_QUEUE_SIZE_SCSI { - return Err(anyhow!(ConfigError::IllegalValue( - "virtqueue size of scsi controller".to_string(), - MIN_QUEUE_SIZE_SCSI as u64, - false, - MAX_QUEUE_SIZE_SCSI as u64, - true - ))); - } - - if self.queue_size & (self.queue_size - 1) != 0 { - bail!("Virtqueue size should be power of 2!"); - } - - Ok(()) - } -} - -pub fn parse_scsi_controller( - drive_config: &str, - queues_auto: Option, -) -> Result { - let mut cmd_parser = CmdParser::new("virtio-scsi-pci"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("addr") - .push("multifunction") - .push("iothread") - .push("num-queues") - .push("queue-size"); - - cmd_parser.parse(drive_config)?; - - pci_args_check(&cmd_parser)?; - - let mut cntlr_cfg = ScsiCntlrConfig::default(); - - if let Some(iothread) = cmd_parser.get_value::("iothread")? { - cntlr_cfg.iothread = Some(iothread); - } - - cntlr_cfg.id = cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "virtio scsi pci".to_string()) - })?; - - if let Some(queues) = cmd_parser.get_value::("num-queues")? { - cntlr_cfg.queues = queues; - } else if let Some(queues) = queues_auto { - cntlr_cfg.queues = queues as u32; - } - - if let Some(size) = cmd_parser.get_value::("queue-size")? { - cntlr_cfg.queue_size = size; - } - - cntlr_cfg.check()?; - Ok(cntlr_cfg) -} - -#[derive(Clone, Debug)] -pub struct ScsiDevConfig { - /// Scsi Device id. - pub id: String, - /// The image file path. - pub path_on_host: String, - /// Serial number of the scsi device. - pub serial: Option, - /// Scsi controller which the scsi device attaches to. - pub cntlr: String, - /// Scsi device can not do write operation. - pub read_only: bool, - /// If true, use direct access io. - pub direct: bool, - /// Async IO type. - pub aio_type: AioEngine, - /// Boot order. - pub boot_index: Option, - /// Scsi four level hierarchical address(host, channel, target, lun). - pub channel: u8, - pub target: u8, - pub lun: u16, - pub format: DiskFormat, - pub l2_cache_size: Option, - pub refcount_cache_size: Option, -} - -impl Default for ScsiDevConfig { - fn default() -> Self { - ScsiDevConfig { - id: "".to_string(), - path_on_host: "".to_string(), - serial: None, - cntlr: "".to_string(), - read_only: false, - direct: true, - aio_type: AioEngine::Native, - boot_index: None, - channel: 0, - target: 0, - lun: 0, - format: DiskFormat::Raw, - l2_cache_size: None, - refcount_cache_size: None, - } - } -} - -pub fn parse_scsi_device(vm_config: &mut VmConfig, drive_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("scsi-device"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("scsi-id") - .push("lun") - .push("serial") - .push("bootindex") - .push("drive"); - - cmd_parser.parse(drive_config)?; - - let mut scsi_dev_cfg = ScsiDevConfig::default(); - - let scsi_drive = cmd_parser.get_value::("drive")?.with_context(|| { - ConfigError::FieldIsMissing("drive".to_string(), "scsi device".to_string()) - })?; - - if let Some(boot_index) = cmd_parser.get_value::("bootindex")? { - scsi_dev_cfg.boot_index = Some(boot_index); - } - - if let Some(serial) = cmd_parser.get_value::("serial")? { - scsi_dev_cfg.serial = Some(serial); - } - - scsi_dev_cfg.id = cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "scsi device".to_string()) - })?; - - if let Some(bus) = cmd_parser.get_value::("bus")? { - // Format "$parent_cntlr_name.0" is required by scsi bus. - let strs = bus.split('.').collect::>(); - if strs.len() != 2 || strs[1] != "0" { - bail!("Invalid scsi bus {}", bus); - } - scsi_dev_cfg.cntlr = strs[0].to_string(); - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "bus".to_string(), - "scsi device".to_string() - ))); - } - - if let Some(target) = cmd_parser.get_value::("scsi-id")? { - if target > VIRTIO_SCSI_MAX_TARGET as u8 { - return Err(anyhow!(ConfigError::IllegalValue( - "scsi-id of scsi device".to_string(), - 0, - true, - VIRTIO_SCSI_MAX_TARGET as u64, - true, - ))); - } - scsi_dev_cfg.target = target; - } - - if let Some(lun) = cmd_parser.get_value::("lun")? { - // Do not support Flat space addressing format(14 bits for lun) in stratovirt now. - // We now support peripheral device addressing format(8 bits for lun). - // So, MAX_LUN should be less than 255(2^8 - 1) temporarily. - if lun > SUPPORT_SCSI_MAX_LUN { - return Err(anyhow!(ConfigError::IllegalValue( - "lun of scsi device".to_string(), - 0, - true, - SUPPORT_SCSI_MAX_LUN as u64, - true, - ))); - } - scsi_dev_cfg.lun = lun; - } - - let drive_arg = &vm_config - .drives - .remove(&scsi_drive) - .with_context(|| "No drive configured matched for scsi device")?; - scsi_dev_cfg.path_on_host = drive_arg.path_on_host.clone(); - scsi_dev_cfg.read_only = drive_arg.read_only; - scsi_dev_cfg.direct = drive_arg.direct; - scsi_dev_cfg.aio_type = drive_arg.aio; - scsi_dev_cfg.format = drive_arg.format; - scsi_dev_cfg.l2_cache_size = drive_arg.l2_cache_size; - scsi_dev_cfg.refcount_cache_size = drive_arg.refcount_cache_size; - - Ok(scsi_dev_cfg) -} diff --git a/machine_manager/src/config/smbios.rs b/machine_manager/src/config/smbios.rs index 2c8f0d9..75220f4 100644 --- a/machine_manager/src/config/smbios.rs +++ b/machine_manager/src/config/smbios.rs @@ -12,74 +12,138 @@ use std::str::FromStr; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Result}; +use clap::Parser; use serde::{Deserialize, Serialize}; -use crate::config::{CmdParser, VmConfig}; +use super::{get_value_of_parameter, str_slip_to_clap}; +use crate::config::VmConfig; -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType0Config { - pub vender: Option, + #[arg(long, alias = "type", value_parser = ["0"])] + pub smbios_type: String, + #[arg(long)] + pub vendor: Option, + #[arg(long)] pub version: Option, + #[arg(long)] pub date: Option, + // Note: we don't set `ArgAction::Append` for `added`, so it cannot be specified + // from the command line, as command line will parse errors. + #[arg(long, default_value = "true")] pub added: bool, } -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType1Config { + #[arg(long, alias = "type", value_parser = ["1"])] + pub smbios_type: String, + #[arg(long)] pub manufacturer: Option, + #[arg(long)] pub product: Option, + #[arg(long)] pub version: Option, + #[arg(long)] pub serial: Option, + #[arg(long)] pub sku: Option, + #[arg(long)] pub family: Option, + #[arg(long, value_parser = get_uuid)] pub uuid: Option, + #[arg(long, default_value = "true")] pub added: bool, } -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType2Config { + #[arg(long, alias = "type", value_parser = ["2"])] + pub smbios_type: String, + #[arg(long)] pub manufacturer: Option, + #[arg(long)] pub product: Option, + #[arg(long)] pub version: Option, + #[arg(long)] pub serial: Option, + #[arg(long)] pub asset: Option, + #[arg(long)] pub location: Option, + #[arg(long, default_value = "true")] pub added: bool, } -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType3Config { + #[arg(long, alias = "type", value_parser = ["3"])] + pub smbios_type: String, + #[arg(long)] pub manufacturer: Option, + #[arg(long)] pub version: Option, + #[arg(long)] pub serial: Option, + #[arg(long)] pub sku: Option, + #[arg(long)] pub asset: Option, + #[arg(long, default_value = "true")] pub added: bool, } -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType4Config { + #[arg(long, alias = "type", value_parser = ["4"])] + pub smbios_type: String, + #[arg(long)] pub manufacturer: Option, + #[arg(long)] pub version: Option, + #[arg(long)] pub serial: Option, + #[arg(long)] pub asset: Option, + #[arg(long, alias = "sock_pfx")] pub sock_pfx: Option, + #[arg(long)] pub part: Option, + #[arg(long)] pub max_speed: Option, + #[arg(long)] pub current_speed: Option, + #[arg(long, default_value = "true")] pub added: bool, } -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Parser, Clone, Default, Debug, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct SmbiosType17Config { + #[arg(long, alias = "type", value_parser = ["17"])] + pub smbios_type: String, + #[arg(long)] pub manufacturer: Option, + #[arg(long)] pub serial: Option, + #[arg(long)] pub asset: Option, + #[arg(long, alias = "loc_pfx")] pub loc_pfx: Option, + #[arg(long)] pub part: Option, + #[arg(long, default_value = "0")] pub speed: u16, + #[arg(long)] pub bank: Option, + #[arg(long, default_value = "true")] pub added: bool, } @@ -124,13 +188,13 @@ pub struct Uuid { } impl FromStr for Uuid { - type Err = (); + type Err = anyhow::Error; fn from_str(str: &str) -> std::result::Result { let name = str.to_string(); if !check_valid_uuid(&name) { - return Err(()); + return Err(anyhow!("Invalid uuid {}", name)); } let mut uuid_bytes = Vec::new(); @@ -149,6 +213,11 @@ impl FromStr for Uuid { } } +fn get_uuid(s: &str) -> Result { + let uuid = Uuid::from_str(s)?; + Ok(uuid) +} + impl VmConfig { /// # Arguments /// @@ -158,19 +227,8 @@ impl VmConfig { bail!("smbios type0 has been added"); } - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("vendor") - .push("version") - .push("date"); - cmd_parser.parse(type0)?; - - self.smbios.type0.vender = cmd_parser.get_value::("vendor")?; - self.smbios.type0.version = cmd_parser.get_value::("version")?; - self.smbios.type0.date = cmd_parser.get_value::("date")?; - self.smbios.type0.added = true; + let type0_cfg = SmbiosType0Config::try_parse_from(str_slip_to_clap(type0, false, false))?; + self.smbios.type0 = type0_cfg; Ok(()) } @@ -183,27 +241,8 @@ impl VmConfig { bail!("smbios type1 has been added"); } - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("manufacturer") - .push("product") - .push("version") - .push("serial") - .push("sku") - .push("uuid") - .push("family"); - cmd_parser.parse(type1)?; - - self.smbios.type1.manufacturer = cmd_parser.get_value::("manufacturer")?; - self.smbios.type1.product = cmd_parser.get_value::("product")?; - self.smbios.type1.version = cmd_parser.get_value::("version")?; - self.smbios.type1.serial = cmd_parser.get_value::("serial")?; - self.smbios.type1.sku = cmd_parser.get_value::("sku")?; - self.smbios.type1.family = cmd_parser.get_value::("family")?; - self.smbios.type1.uuid = cmd_parser.get_value::("uuid")?; - self.smbios.type1.added = true; + let type1_cfg = SmbiosType1Config::try_parse_from(str_slip_to_clap(type1, false, false))?; + self.smbios.type1 = type1_cfg; Ok(()) } @@ -215,26 +254,8 @@ impl VmConfig { if self.smbios.type2.added { bail!("smbios type2 has been added"); } - - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("manufacturer") - .push("product") - .push("version") - .push("serial") - .push("asset") - .push("location"); - cmd_parser.parse(type2)?; - - self.smbios.type2.manufacturer = cmd_parser.get_value::("manufacturer")?; - self.smbios.type2.product = cmd_parser.get_value::("product")?; - self.smbios.type2.version = cmd_parser.get_value::("version")?; - self.smbios.type2.serial = cmd_parser.get_value::("serial")?; - self.smbios.type2.asset = cmd_parser.get_value::("asset")?; - self.smbios.type2.location = cmd_parser.get_value::("location")?; - self.smbios.type2.added = true; + let type2_cfg = SmbiosType2Config::try_parse_from(str_slip_to_clap(type2, false, false))?; + self.smbios.type2 = type2_cfg; Ok(()) } @@ -247,23 +268,8 @@ impl VmConfig { bail!("smbios type3 has been added"); } - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("manufacturer") - .push("version") - .push("serial") - .push("sku") - .push("asset"); - cmd_parser.parse(type3)?; - - self.smbios.type3.manufacturer = cmd_parser.get_value::("manufacturer")?; - self.smbios.type3.version = cmd_parser.get_value::("version")?; - self.smbios.type3.serial = cmd_parser.get_value::("serial")?; - self.smbios.type3.sku = cmd_parser.get_value::("sku")?; - self.smbios.type3.asset = cmd_parser.get_value::("asset")?; - self.smbios.type3.added = true; + let type3_cfg = SmbiosType3Config::try_parse_from(str_slip_to_clap(type3, false, false))?; + self.smbios.type3 = type3_cfg; Ok(()) } @@ -276,29 +282,8 @@ impl VmConfig { bail!("smbios type4 has been added"); } - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("manufacturer") - .push("version") - .push("serial") - .push("sock_pfx") - .push("max-speed") - .push("current-speed") - .push("part") - .push("asset"); - cmd_parser.parse(type4)?; - - self.smbios.type4.manufacturer = cmd_parser.get_value::("manufacturer")?; - self.smbios.type4.version = cmd_parser.get_value::("version")?; - self.smbios.type4.serial = cmd_parser.get_value::("serial")?; - self.smbios.type4.asset = cmd_parser.get_value::("asset")?; - self.smbios.type4.part = cmd_parser.get_value::("part")?; - self.smbios.type4.sock_pfx = cmd_parser.get_value::("sock_pfx")?; - self.smbios.type4.max_speed = cmd_parser.get_value::("max-speed")?; - self.smbios.type4.current_speed = cmd_parser.get_value::("current-speed")?; - self.smbios.type4.added = true; + let type4_cfg = SmbiosType4Config::try_parse_from(str_slip_to_clap(type4, false, false))?; + self.smbios.type4 = type4_cfg; Ok(()) } @@ -311,31 +296,9 @@ impl VmConfig { bail!("smbios type17 has been added"); } - let mut cmd_parser = CmdParser::new("smbios"); - cmd_parser - .push("") - .push("type") - .push("loc_pfx") - .push("bank") - .push("manufacturer") - .push("serial") - .push("speed") - .push("part") - .push("asset"); - cmd_parser.parse(type17)?; - - self.smbios.type17.manufacturer = cmd_parser.get_value::("manufacturer")?; - self.smbios.type17.loc_pfx = cmd_parser.get_value::("loc_pfx")?; - self.smbios.type17.serial = cmd_parser.get_value::("serial")?; - self.smbios.type17.asset = cmd_parser.get_value::("asset")?; - self.smbios.type17.part = cmd_parser.get_value::("part")?; - self.smbios.type17.speed = if let Some(speed) = cmd_parser.get_value::("speed")? { - speed - } else { - 0 - }; - self.smbios.type17.bank = cmd_parser.get_value::("bank")?; - self.smbios.type17.added = true; + let type17_cfg = + SmbiosType17Config::try_parse_from(str_slip_to_clap(type17, false, false))?; + self.smbios.type17 = type17_cfg; Ok(()) } @@ -346,13 +309,7 @@ impl VmConfig { /// /// * `smbios_args` - The args of object. pub fn add_smbios(&mut self, smbios_args: &str) -> Result<()> { - let mut cmd_params = CmdParser::new("smbios"); - cmd_params.push("").push("type"); - - cmd_params.get_parameters(smbios_args)?; - let smbios_type = cmd_params - .get_value::("type")? - .with_context(|| "smbios type not specified")?; + let smbios_type = get_value_of_parameter("type", smbios_args)?; match smbios_type.as_str() { "0" => { self.add_smbios_type0(smbios_args)?; @@ -397,4 +354,24 @@ mod test { ] ); } + + #[test] + fn test_add_smbios() { + let mut vm_config = VmConfig::default(); + + let smbios0 = "type=0,vendor=fake,version=fake,date=fake"; + let smbios1 = "type=1,manufacturer=fake,version=fake,product=fake,serial=fake,uuid=33DB4D5E-1FF7-401C-9657-7441C03DD766,sku=fake,family=fake"; + let smbios2 = "type=2,manufacturer=fake,product=fake,version=fake,serial=fake,asset=fake,location=fake"; + let smbios3 = "type=3,manufacturer=fake,version=fake,serial=fake,asset=fake,sku=fake"; + let smbios4 = "type=4,sock_pfx=fake,manufacturer=fake,version=fake,serial=fake,asset=fake,part=fake,max-speed=1,current-speed=1"; + let smbios17 = "type=17,loc_pfx=fake,bank=fake,manufacturer=fake,serial=fake,asset=fake,part=fake,speed=1"; + + assert!(vm_config.add_smbios(smbios0).is_ok()); + assert!(vm_config.add_smbios(smbios1).is_ok()); + assert!(vm_config.add_smbios(smbios2).is_ok()); + assert!(vm_config.add_smbios(smbios3).is_ok()); + assert!(vm_config.add_smbios(smbios4).is_ok()); + assert!(vm_config.add_smbios(smbios17).is_ok()); + assert!(vm_config.add_smbios(smbios0).is_err()); + } } diff --git a/machine_manager/src/config/tls_creds.rs b/machine_manager/src/config/tls_creds.rs index 8803ea4..9290316 100644 --- a/machine_manager/src/config/tls_creds.rs +++ b/machine_manager/src/config/tls_creds.rs @@ -10,64 +10,36 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use std::path::Path; - -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; +use clap::{ArgAction, Parser}; use serde::{Deserialize, Serialize}; -use crate::config::{ - ConfigError, {CmdParser, VmConfig}, -}; +use crate::config::{str_slip_to_clap, valid_dir, valid_id, ConfigError, VmConfig}; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct TlsCredObjConfig { + #[arg(long)] + pub classtype: String, + #[arg(long, value_parser = valid_id)] pub id: String, + #[arg(long, value_parser = valid_dir)] pub dir: String, - pub cred_type: String, + #[arg(long)] pub endpoint: Option, + #[arg(long, alias = "verify-peer", default_value= "false", action = ArgAction::Append)] pub verifypeer: bool, } impl VmConfig { pub fn add_tlscred(&mut self, tlscred_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("tls-creds-x509"); - cmd_parser - .push("") - .push("id") - .push("dir") - .push("endpoint") - .push("verify-peer"); - cmd_parser.parse(tlscred_config)?; - - let mut tlscred = TlsCredObjConfig { - id: cmd_parser.get_value::("id")?.with_context(|| { - ConfigError::FieldIsMissing("id".to_string(), "vnc tls_creds".to_string()) - })?, - ..Default::default() - }; - - if let Some(dir) = cmd_parser.get_value::("dir")? { - if Path::new(&dir).is_dir() { - tlscred.dir = dir; - } else { - return Err(anyhow!(ConfigError::DirNotExist(dir))); - } - } - if let Some(endpoint) = cmd_parser.get_value::("endpoint")? { - tlscred.endpoint = Some(endpoint); - } - if let Some(verifypeer) = cmd_parser.get_value::("verify-peer")? { - tlscred.verifypeer = verifypeer == *"true"; - } - tlscred.cred_type = "x509".to_string(); - + let tlscred = + TlsCredObjConfig::try_parse_from(str_slip_to_clap(tlscred_config, true, false))?; let id = tlscred.id.clone(); - if self.object.tls_object.get(&id).is_none() { - self.object.tls_object.insert(id, tlscred); - } else { + if self.object.tls_object.get(&id).is_some() { return Err(anyhow!(ConfigError::IdRepeat("tlscred".to_string(), id))); } - + self.object.tls_object.insert(id, tlscred); Ok(()) } } diff --git a/machine_manager/src/config/usb.rs b/machine_manager/src/config/usb.rs deleted file mode 100644 index da363b7..0000000 --- a/machine_manager/src/config/usb.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{bail, Context, Result}; - -use super::error::ConfigError; -use crate::config::{ - check_arg_nonexist, check_arg_too_long, CmdParser, ConfigCheck, ScsiDevConfig, VmConfig, -}; -use util::aio::AioEngine; - -pub fn check_id(id: Option, device: &str) -> Result<()> { - check_arg_nonexist(id.clone(), "id", device)?; - check_arg_too_long(&id.unwrap(), "id")?; - - Ok(()) -} - -#[derive(Clone, Debug)] -pub struct UsbStorageConfig { - /// USB Storage device id. - pub id: Option, - /// The scsi backend config. - pub scsi_cfg: ScsiDevConfig, - /// The backend scsi device type(Disk or CD-ROM). - pub media: String, -} - -impl UsbStorageConfig { - fn new() -> Self { - Self { - id: None, - scsi_cfg: ScsiDevConfig::default(), - media: "".to_string(), - } - } -} - -impl Default for UsbStorageConfig { - fn default() -> Self { - Self::new() - } -} - -impl ConfigCheck for UsbStorageConfig { - fn check(&self) -> Result<()> { - check_id(self.id.clone(), "usb-storage")?; - - if self.scsi_cfg.aio_type != AioEngine::Off || self.scsi_cfg.direct { - bail!("USB-storage: \"aio=off,direct=false\" must be configured."); - } - - Ok(()) - } -} - -pub fn parse_usb_storage(vm_config: &mut VmConfig, drive_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("usb-storage"); - cmd_parser - .push("") - .push("id") - .push("bus") - .push("port") - .push("drive"); - - cmd_parser.parse(drive_config)?; - - let mut dev = UsbStorageConfig::new(); - dev.id = cmd_parser.get_value::("id")?; - - let storage_drive = cmd_parser.get_value::("drive")?.with_context(|| { - ConfigError::FieldIsMissing("drive".to_string(), "usb storage device".to_string()) - })?; - - let drive_arg = &vm_config - .drives - .remove(&storage_drive) - .with_context(|| "No drive configured matched for usb storage device.")?; - dev.scsi_cfg.path_on_host = drive_arg.path_on_host.clone(); - dev.scsi_cfg.read_only = drive_arg.read_only; - dev.scsi_cfg.aio_type = drive_arg.aio; - dev.scsi_cfg.direct = drive_arg.direct; - dev.scsi_cfg.format = drive_arg.format; - dev.scsi_cfg.l2_cache_size = drive_arg.l2_cache_size; - dev.scsi_cfg.refcount_cache_size = drive_arg.refcount_cache_size; - dev.media = drive_arg.media.clone(); - - dev.check()?; - Ok(dev) -} diff --git a/machine_manager/src/config/vfio.rs b/machine_manager/src/config/vfio.rs deleted file mode 100644 index dddebde..0000000 --- a/machine_manager/src/config/vfio.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. -// -// StratoVirt is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. -// You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -use anyhow::{anyhow, Result}; - -use super::error::ConfigError; -use crate::config::{check_arg_too_long, CmdParser, ConfigCheck}; - -#[derive(Default, Debug)] -pub struct VfioConfig { - pub sysfsdev: String, - pub host: String, - pub id: String, -} - -impl ConfigCheck for VfioConfig { - fn check(&self) -> Result<()> { - check_arg_too_long(&self.host, "host")?; - check_arg_too_long(&self.id, "id")?; - - Ok(()) - } -} - -pub fn parse_vfio(vfio_config: &str) -> Result { - let mut cmd_parser = CmdParser::new("vfio-pci"); - cmd_parser - .push("") - .push("host") - .push("sysfsdev") - .push("id") - .push("bus") - .push("addr") - .push("multifunction"); - cmd_parser.parse(vfio_config)?; - - let mut vfio: VfioConfig = VfioConfig::default(); - if let Some(host) = cmd_parser.get_value::("host")? { - vfio.host = host; - } - - if let Some(sysfsdev) = cmd_parser.get_value::("sysfsdev")? { - vfio.sysfsdev = sysfsdev; - } - - if vfio.host.is_empty() && vfio.sysfsdev.is_empty() { - return Err(anyhow!(ConfigError::FieldIsMissing( - "host nor sysfsdev".to_string(), - "vfio".to_string() - ))); - } - - if !vfio.host.is_empty() && !vfio.sysfsdev.is_empty() { - return Err(anyhow!(ConfigError::InvalidParam( - "host and sysfsdev".to_string(), - "vfio".to_string() - ))); - } - - if let Some(id) = cmd_parser.get_value::("id")? { - vfio.id = id; - } - vfio.check()?; - - Ok(vfio) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::config::get_pci_bdf; - - #[test] - fn test_check_vfio_config() { - let mut vfio_config = - parse_vfio("vfio-pci,host=0000:1a:00.3,id=net,bus=pcie.0,addr=0x1.0x2").unwrap(); - assert!(vfio_config.check().is_ok()); - - vfio_config.host = "IYqUdAMXggoUMU28eBJCxQGUirYYSyW1cfGJI3ZpZAzMFCKnVPA5e7gnurLtXjCm\ - YoG5pfqRDbN7M2dpSd8fzSbufAJaor8UY9xbH7BybZ7WDEFmkxgCQp6PWgaBSmLOCe1tEMs4RQ938ZLnh8ej\ - Q81VovbrU7ecafacCn9AJQoidN3Seab3QOEd4SJbtd4hAPeYvsXLVa6xOZxtVjqjRxk9b36feF0C5JrucVcs\ - QsusZZtVfUFUZxOoV8JltVsBmdasnic" - .to_string(); - assert!(vfio_config.check().is_err()); - - vfio_config.id = "LPwM1h4QUTCjL4fX2gFdCdPrF9S0kGHf0onpU6E4fyI6Jmzg0DCM9sffvEVjaVu1ilp\ - 2OrgCWzvNBflYvUUihPj3ePPYs3erSHmSOmQZbnGEFsiBSTJHfPAsRtWJoipeIh9cgIR1tnU3OjwPPli4gmb6\ - E6GgSyMd0oQtUGFyNf5pRHlYqlx3s7PMPVUtRJP0bBnNd5eDwWAotInu33h6UI0zfKgckAxeVdEROKAExx5xWK\ - V3AgPhvvPzFx3chYymy" - .to_string(); - assert!(vfio_config.check().is_err()); - } - - #[test] - fn test_vfio_config_cmdline_parser() { - let vfio_cfg = parse_vfio("vfio-pci,host=0000:1a:00.3,id=net"); - assert!(vfio_cfg.is_ok()); - let vfio_config = vfio_cfg.unwrap(); - assert_eq!(vfio_config.host, "0000:1a:00.3"); - assert_eq!(vfio_config.id, "net"); - } - - #[test] - fn test_pci_vfio_config_cmdline_parser() { - let vfio_cfg1 = "vfio-pci,host=0000:1a:00.3,id=net,bus=pcie.0,addr=0x1.0x2"; - let config1 = parse_vfio(vfio_cfg1); - assert!(config1.is_ok()); - let vfio_cfg2 = "vfio-pci,host=0000:1a:00.3,bus=pcie.0,addr=0x1.0x2"; - let config2 = parse_vfio(vfio_cfg2); - assert!(config2.is_ok()); - let vfio_cfg3 = "vfio-pci,id=net,bus=pcie.0,addr=0x1.0x2"; - let config3 = parse_vfio(vfio_cfg3); - assert!(config3.is_err()); - - let pci_bdf = get_pci_bdf(vfio_cfg1); - assert!(pci_bdf.is_ok()); - let pci = pci_bdf.unwrap(); - assert_eq!(pci.bus, "pcie.0".to_string()); - assert_eq!(pci.addr, (1, 2)); - - let vfio_cfg1 = - "vfio-pci,host=0000:1a:00.3,id=net,bus=pcie.0,addr=0x1.0x2,multifunction=on"; - assert!(parse_vfio(vfio_cfg1).is_ok()); - } -} diff --git a/machine_manager/src/config/vnc.rs b/machine_manager/src/config/vnc.rs index b243d94..ca1fd10 100644 --- a/machine_manager/src/config/vnc.rs +++ b/machine_manager/src/config/vnc.rs @@ -13,22 +13,26 @@ use std::net::Ipv4Addr; use anyhow::{anyhow, Context, Result}; +use clap::{ArgAction, Parser}; use serde::{Deserialize, Serialize}; -use crate::config::{CmdParser, ConfigError, VmConfig}; +use crate::config::{str_slip_to_clap, ConfigError, VmConfig}; /// Configuration of vnc. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)] +#[command(no_binary_name(true))] pub struct VncConfig { - /// Listening ip. - pub ip: String, - /// Listening port. - pub port: String, + /// Vnc listening addr (ip, port). + #[arg(long, alias = "classtype", value_parser = parse_ip_port)] + pub addr: (String, u16), /// Configuration of encryption. + #[arg(long, alias = "tls-creds", default_value = "")] pub tls_creds: String, /// Authentication switch. + #[arg(long, default_value = "false", action = ArgAction::SetTrue)] pub sasl: bool, /// Configuration of authentication. + #[arg(long, alias = "sasl-authz", default_value = "")] pub sasl_authz: String, } @@ -38,45 +42,13 @@ const VNC_PORT_OFFSET: i32 = 5900; impl VmConfig { /// Make configuration for vnc: "chardev" -> "vnc". pub fn add_vnc(&mut self, vnc_config: &str) -> Result<()> { - let mut cmd_parser = CmdParser::new("vnc"); - cmd_parser - .push("") - .push("tls-creds") - .push("sasl") - .push("sasl-authz"); - cmd_parser.parse(vnc_config)?; - - let mut vnc_config = VncConfig::default(); - // Parse Ip:Port. - if let Some(addr) = cmd_parser.get_value::("")? { - parse_port(&mut vnc_config, addr)?; - } else { - return Err(anyhow!(ConfigError::FieldIsMissing( - "ip".to_string(), - "port".to_string() - ))); - } - - // VNC Security Type. - if let Some(tls_creds) = cmd_parser.get_value::("tls-creds")? { - vnc_config.tls_creds = tls_creds - } - if let Some(_sasl) = cmd_parser.get_value::("sasl")? { - vnc_config.sasl = true - } else { - vnc_config.sasl = false - } - if let Some(sasl_authz) = cmd_parser.get_value::("sasl-authz")? { - vnc_config.sasl_authz = sasl_authz; - } - + let vnc_config = VncConfig::try_parse_from(str_slip_to_clap(vnc_config, true, false))?; self.vnc = Some(vnc_config); Ok(()) } } -/// Parse Ip:port. -fn parse_port(vnc_config: &mut VncConfig, addr: String) -> Result<()> { +fn parse_ip_port(addr: &str) -> Result<(String, u16)> { let v: Vec<&str> = addr.split(':').collect(); if v.len() != 2 { return Err(anyhow!(ConfigError::FieldIsMissing( @@ -97,10 +69,8 @@ fn parse_port(vnc_config: &mut VncConfig, addr: String) -> Result<()> { "port".to_string() ))); } - vnc_config.ip = ip.to_string(); - vnc_config.port = ((base_port + VNC_PORT_OFFSET) as u16).to_string(); - Ok(()) + Ok((ip.to_string(), (base_port + VNC_PORT_OFFSET) as u16)) } #[cfg(test)] @@ -113,8 +83,8 @@ mod tests { let config_line = "0.0.0.0:1,tls-creds=vnc-tls-creds0,sasl,sasl-authz=authz0"; assert!(vm_config.add_vnc(config_line).is_ok()); let vnc_config = vm_config.vnc.unwrap(); - assert_eq!(vnc_config.ip, String::from("0.0.0.0")); - assert_eq!(vnc_config.port, String::from("5901")); + assert_eq!(vnc_config.addr.0, String::from("0.0.0.0")); + assert_eq!(vnc_config.addr.1, 5901); assert_eq!(vnc_config.tls_creds, String::from("vnc-tls-creds0")); assert_eq!(vnc_config.sasl, true); assert_eq!(vnc_config.sasl_authz, String::from("authz0")); @@ -124,7 +94,7 @@ mod tests { assert!(vm_config.add_vnc(config_line).is_ok()); let vnc_config = vm_config.vnc.unwrap(); assert_eq!(vnc_config.sasl, false); - assert_eq!(vnc_config.port, String::from("11800")); + assert_eq!(vnc_config.addr.1, 11800); let mut vm_config = VmConfig::default(); let config_line = "0.0.0.0:1,sasl,sasl-authz=authz0"; diff --git a/machine_manager/src/event_loop.rs b/machine_manager/src/event_loop.rs index 7acaca8..79d7cf6 100644 --- a/machine_manager/src/event_loop.rs +++ b/machine_manager/src/event_loop.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use std::os::unix::prelude::RawFd; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Barrier, Mutex}; use std::{process, thread}; use anyhow::{bail, Result}; @@ -49,9 +49,18 @@ impl EventLoop { /// * `iothreads` - refer to `-iothread` params pub fn object_init(iothreads: &Option>) -> Result<()> { let mut io_threads = HashMap::new(); + let cnt = match iothreads { + Some(thrs) => thrs.len(), + None => 0, + }; + let thread_exit_barrier = Arc::new(Barrier::new(cnt + 1)); + if let Some(thrs) = iothreads { for thr in thrs { - io_threads.insert(thr.id.clone(), EventLoopContext::new()); + io_threads.insert( + thr.id.clone(), + EventLoopContext::new(thread_exit_barrier.clone()), + ); } } @@ -60,7 +69,7 @@ impl EventLoop { unsafe { if GLOBAL_EVENT_LOOP.is_none() { GLOBAL_EVENT_LOOP = Some(EventLoop { - main_loop: EventLoopContext::new(), + main_loop: EventLoopContext::new(thread_exit_barrier), io_threads, }); @@ -76,10 +85,11 @@ impl EventLoop { }; IOTHREADS.lock().unwrap().push(iothread_info); while let Ok(ret) = ctx.iothread_run() { - if !ret { + if !ret || get_signal() != 0 { break; } } + ctx.thread_exit_barrier.wait(); })?; } } else { @@ -115,11 +125,16 @@ impl EventLoop { /// /// # Arguments /// - /// * `manager` - The main part to manager the event loop specified by name. - /// * `name` - specify which event loop to manage - pub fn set_manager(manager: Arc>, name: Option<&String>) { - if let Some(ctx) = Self::get_ctx(name) { - ctx.set_manager(manager) + /// * `manager` - The main part to manager the event loop. + pub fn set_manager(manager: Arc>) { + // SAFETY: All concurrently accessed data of EventLoopContext is protected. + unsafe { + if let Some(event_loop) = GLOBAL_EVENT_LOOP.as_mut() { + event_loop.main_loop.set_manager(manager.clone()); + for (_name, io_thread) in event_loop.io_threads.iter_mut() { + io_thread.set_manager(manager.clone()); + } + } } } @@ -152,10 +167,12 @@ impl EventLoop { let sig_num = get_signal(); if sig_num != 0 { info!("MainLoop exits due to receive signal {}", sig_num); + event_loop.main_loop.thread_exit_barrier.wait(); return Ok(()); } if !event_loop.main_loop.run()? { info!("MainLoop exits due to guest internal operation."); + event_loop.main_loop.thread_exit_barrier.wait(); return Ok(()); } } @@ -172,6 +189,18 @@ impl EventLoop { GLOBAL_EVENT_LOOP = None; } } + + pub fn kick_all() { + // SAFETY: All concurrently accessed data of EventLoopContext is protected. + unsafe { + if let Some(event_loop) = GLOBAL_EVENT_LOOP.as_mut() { + for (_name, io_thread) in event_loop.io_threads.iter_mut() { + io_thread.kick(); + } + event_loop.main_loop.kick(); + } + } + } } pub fn register_event_helper( diff --git a/machine_manager/src/machine.rs b/machine_manager/src/machine.rs index f0f00aa..4579439 100644 --- a/machine_manager/src/machine.rs +++ b/machine_manager/src/machine.rs @@ -11,9 +11,12 @@ // See the Mulan PSL v2 for more details. use std::os::unix::io::RawFd; +use std::str::FromStr; use std::sync::Mutex; +use anyhow::anyhow; use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; use strum::VariantNames; use crate::config::ShutdownAction; @@ -44,10 +47,26 @@ pub enum VmState { } /// Type for Hypervisor. -#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum HypervisorType { #[default] Kvm, + #[cfg(not(target_arch = "riscv64"))] + Test, +} + +impl FromStr for HypervisorType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + // Note: "kvm:tcg" is a configuration compatible with libvirt. + "kvm" | "kvm:tcg" => Ok(HypervisorType::Kvm), + #[cfg(not(target_arch = "riscv64"))] + "test" => Ok(HypervisorType::Test), + _ => Err(anyhow!("Not supported or invalid hypervisor type {}.", s)), + } + } } /// Trait to handle virtual machine lifecycle. @@ -251,6 +270,10 @@ pub trait DeviceInterface { let target = Target { arch: "aarch64".to_string(), }; + #[cfg(target_arch = "riscv64")] + let target = Target { + arch: "riscv64".to_string(), + }; Response::create_response(serde_json::to_value(target).unwrap(), None) } @@ -314,6 +337,16 @@ pub trait DeviceInterface { }; #[cfg(target_arch = "aarch64")] vec_machine.push(machine_info); + #[cfg(target_arch = "riscv64")] + let machine_info = MachineInfo { + hotplug: false, + name: "virt".to_string(), + numa_mem_support: false, + cpu_max: 255, + deprecated: false, + }; + #[cfg(target_arch = "riscv64")] + vec_machine.push(machine_info); Response::create_response(serde_json::to_value(&vec_machine).unwrap(), None) } diff --git a/machine_manager/src/qmp/qmp_channel.rs b/machine_manager/src/qmp/qmp_channel.rs index 42b0f44..a083950 100644 --- a/machine_manager/src/qmp/qmp_channel.rs +++ b/machine_manager/src/qmp/qmp_channel.rs @@ -153,9 +153,10 @@ impl QmpChannel { pub fn send_event(event: &schema::QmpEvent) { if Self::is_connected() { let mut event_str = serde_json::to_string(&event).unwrap(); - let mut writer_unlocked = Self::inner().event_writer.write().unwrap(); - let writer = writer_unlocked.as_mut().unwrap(); + let mut writer_locked = Self::inner().event_writer.write().unwrap(); + let writer = writer_locked.as_mut().unwrap(); + info!("EVENT: --> {:?}", event); if let Err(e) = writer.flush() { error!("flush err, {:?}", e); return; @@ -163,9 +164,7 @@ impl QmpChannel { event_str.push_str("\r\n"); if let Err(e) = writer.write(event_str.as_bytes()) { error!("write err, {:?}", e); - return; } - info!("EVENT: --> {:?}", event); } } diff --git a/machine_manager/src/qmp/qmp_schema.rs b/machine_manager/src/qmp/qmp_schema.rs index 4281624..e1f9440 100644 --- a/machine_manager/src/qmp/qmp_schema.rs +++ b/machine_manager/src/qmp/qmp_schema.rs @@ -843,6 +843,14 @@ pub enum CpuInfo { #[serde(rename = "Arm")] arm: CpuInfoArm, }, + #[serde(rename = "riscv")] + RISCV { + #[serde(flatten)] + common: CpuInfoCommon, + #[serde(flatten)] + #[serde(rename = "RISCV")] + riscv: CpuInfoRISCV, + }, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -851,6 +859,9 @@ pub struct CpuInfoX86 {} #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct CpuInfoArm {} +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct CpuInfoRISCV {} + /// query-status /// /// Query the run status of all VCPUs. diff --git a/machine_manager/src/signal_handler.rs b/machine_manager/src/signal_handler.rs index 2f1ba86..6d67919 100644 --- a/machine_manager/src/signal_handler.rs +++ b/machine_manager/src/signal_handler.rs @@ -50,7 +50,7 @@ pub fn set_signal(num: c_int) { unsafe { RECEIVED_SIGNAL.store(num, Ordering::SeqCst); } - EventLoop::get_ctx(None).unwrap().kick(); + EventLoop::kick_all(); } } diff --git a/machine_manager/src/temp_cleaner.rs b/machine_manager/src/temp_cleaner.rs index e63d273..cd5b2f4 100644 --- a/machine_manager/src/temp_cleaner.rs +++ b/machine_manager/src/temp_cleaner.rs @@ -81,15 +81,12 @@ impl TempCleaner { while let Some(path) = self.paths.pop() { if Path::new(&path).exists() { if let Err(ref e) = fs::remove_file(&path) { - error!( - "Failed to delete console / socket file:{} :{} \r\n", - &path, e - ); + error!("Failed to delete console / socket file:{} :{}", &path, e); } else { - info!("Delete file: {} successfully.\r\n", &path); + info!("Delete file: {} successfully", &path); } } else { - info!("file: {} has been removed \r\n", &path); + info!("file: {} has been removed", &path); } } } diff --git a/migration/src/manager.rs b/migration/src/manager.rs index d381ae3..96d9943 100644 --- a/migration/src/manager.rs +++ b/migration/src/manager.rs @@ -175,6 +175,9 @@ pub struct Vmm { #[cfg(target_arch = "aarch64")] /// Trait to represent GIC devices(GICv3, GICv3 ITS). pub gic_group: HashMap>, + #[cfg(target_arch = "riscv64")] + /// Trait to represent AIA devices(APLIC, IMSICs). + pub aia_group: HashMap>, #[cfg(target_arch = "x86_64")] /// Trait to represent kvm device. pub kvm: Option>, @@ -362,6 +365,23 @@ impl MigrationManager { locked_vmm.gic_group.insert(translate_id(id), gic); } + /// Register AIA device instance to vmm. + /// + /// # Arguments + /// + /// * `aia_desc` - The `DeviceStateDesc` of AIA instance. + /// * `aia` - The AIA device instance with MigrationHook trait. + #[cfg(target_arch = "riscv64")] + pub fn register_aia_instance(aia_desc: DeviceStateDesc, aia: Arc, id: &str) + where + T: MigrationHook + Sync + Send + 'static, + { + Self::register_device_desc(aia_desc); + + let mut locked_vmm = MIGRATION_MANAGER.vmm.write().unwrap(); + locked_vmm.aia_group.insert(translate_id(id), aia); + } + /// Register migration instance to vmm. /// /// # Arguments diff --git a/migration/src/protocol.rs b/migration/src/protocol.rs index 0d57220..0902eb0 100644 --- a/migration/src/protocol.rs +++ b/migration/src/protocol.rs @@ -393,6 +393,8 @@ impl Default for MigrationHeader { arch: [b'x', b'8', b'6', b'_', b'6', b'4', b'0', b'0'], #[cfg(target_arch = "aarch64")] arch: [b'a', b'a', b'r', b'c', b'h', b'6', b'4', b'0'], + #[cfg(target_arch = "riscv64")] + arch: [b'r', b'i', b's', b'c', b'v', b'6', b'4', b'0'], desc_len: 0, } } @@ -418,6 +420,8 @@ impl MigrationHeader { let current_arch = [b'x', b'8', b'6', b'_', b'6', b'4', b'0', b'0']; #[cfg(target_arch = "aarch64")] let current_arch = [b'a', b'a', b'r', b'c', b'h', b'6', b'4', b'0']; + #[cfg(target_arch = "riscv64")] + let current_arch = [b'r', b'i', b's', b'c', b'v', b'6', b'4', b'0']; if self.arch != current_arch { return Err(anyhow!(MigrationError::HeaderItemNotFit( "Arch".to_string() diff --git a/migration/src/snapshot.rs b/migration/src/snapshot.rs index 3a449ba..1126ff0 100644 --- a/migration/src/snapshot.rs +++ b/migration/src/snapshot.rs @@ -25,6 +25,7 @@ use util::unix::host_page_size; pub const SERIAL_SNAPSHOT_ID: &str = "serial"; pub const KVM_SNAPSHOT_ID: &str = "kvm"; +pub const AIA_SNAPSHOT_ID: &str = "aia"; pub const GICV3_SNAPSHOT_ID: &str = "gicv3"; pub const GICV3_ITS_SNAPSHOT_ID: &str = "gicv3_its"; pub const PL011_SNAPSHOT_ID: &str = "pl011"; @@ -233,6 +234,16 @@ impl MigrationManager { } } + #[cfg(target_arch = "riscv64")] + { + // Save AIA device state. + let aia_id = translate_id(AIA_SNAPSHOT_ID); + if let Some(aia) = locked_vmm.aia_group.get(&aia_id) { + aia.save_device(aia_id, fd) + .with_context(|| "Failed to save aia state")?; + } + } + Ok(()) } @@ -302,6 +313,18 @@ impl MigrationManager { } } + #[cfg(target_arch = "riscv64")] + { + // Restore AIA group state. + for _ in 0..locked_vmm.aia_group.len() { + let (aia_data, id) = Self::check_vm_state(fd, &snap_desc_db)?; + if let Some(aia) = locked_vmm.aia_group.get(&id) { + aia.restore_device(&aia_data) + .with_context(|| "Failed to restore aia state")?; + } + } + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 15eaefc..50807f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,9 @@ use anyhow::{bail, Context, Result}; use log::{error, info}; use thiserror::Error; -use machine::{LightMachine, MachineOps, StdMachine}; +#[cfg(not(target_arch = "riscv64"))] +use machine::StdMachine; +use machine::{type_init, LightMachine, MachineOps}; use machine_manager::{ cmdline::{check_api_channel, create_args_parser, create_vmconfig}, config::MachineType, @@ -71,9 +73,16 @@ fn main() -> ExitCode { } fn run() -> Result<()> { + type_init()?; + let cmd_args = create_args_parser().get_matches()?; if cmd_args.is_present("mod-test") { + let machine = cmd_args.value_of("machine").unwrap_or_default(); + let accel = cmd_args.value_of("accel").unwrap_or_default(); + if !machine.contains("accel=test") && accel.ne("test") { + bail!("MST can only use test accel!"); + } set_test_enabled(); } @@ -97,7 +106,6 @@ fn run() -> Result<()> { })); let mut vm_config: VmConfig = create_vmconfig(&cmd_args)?; - info!("VmConfig is {:?}", vm_config); match real_main(&cmd_args, &mut vm_config) { Ok(()) => { @@ -155,20 +163,21 @@ fn real_main(cmd_args: &arg_parser::ArgMatches, vm_config: &mut VmConfig) -> Res LightMachine::new(vm_config).with_context(|| "Failed to init MicroVM")?, )); MachineOps::realize(&vm, vm_config).with_context(|| "Failed to realize micro VM.")?; - EventLoop::set_manager(vm.clone(), None); + EventLoop::set_manager(vm.clone()); for listener in listeners { sockets.push(Socket::from_listener(listener, Some(vm.clone()))); } vm } + #[cfg(not(target_arch = "riscv64"))] MachineType::StandardVm => { let vm = Arc::new(Mutex::new( StdMachine::new(vm_config).with_context(|| "Failed to init StandardVM")?, )); MachineOps::realize(&vm, vm_config) .with_context(|| "Failed to realize standard VM.")?; - EventLoop::set_manager(vm.clone(), None); + EventLoop::set_manager(vm.clone()); if is_test_enabled() { let sock_path = cmd_args.value_of("mod-test"); @@ -188,27 +197,37 @@ fn real_main(cmd_args: &arg_parser::ArgMatches, vm_config: &mut VmConfig) -> Res vm } MachineType::None => { - if is_test_enabled() { - panic!("please specify machine type.") - } - let vm = Arc::new(Mutex::new( - StdMachine::new(vm_config).with_context(|| "Failed to init NoneVM")?, - )); - EventLoop::set_manager(vm.clone(), None); + #[cfg(not(target_arch = "riscv64"))] + { + if is_test_enabled() { + panic!("please specify machine type.") + } + let vm = Arc::new(Mutex::new( + StdMachine::new(vm_config).with_context(|| "Failed to init NoneVM")?, + )); + EventLoop::set_manager(vm.clone()); - for listener in listeners { - sockets.push(Socket::from_listener(listener, Some(vm.clone()))); + for listener in listeners { + sockets.push(Socket::from_listener(listener, Some(vm.clone()))); + } + vm + } + #[cfg(target_arch = "riscv64")] + { + panic!() } - vm } }; - let balloon_switch_on = vm_config.dev_name.get("balloon").is_some(); - if !cmd_args.is_present("disable-seccomp") { - vm.lock() - .unwrap() - .register_seccomp(balloon_switch_on) - .with_context(|| "Failed to register seccomp rules.")?; + #[cfg(not(target_arch = "riscv64"))] + { + let balloon_switch_on = vm_config.dev_name.get("balloon").is_some(); + if !cmd_args.is_present("disable-seccomp") { + vm.lock() + .unwrap() + .register_seccomp(balloon_switch_on) + .with_context(|| "Failed to register seccomp rules.")?; + } } for socket in sockets { diff --git a/tests/mod_test/src/libtest.rs b/tests/mod_test/src/libtest.rs index 9b1f6f6..ada68e0 100644 --- a/tests/mod_test/src/libtest.rs +++ b/tests/mod_test/src/libtest.rs @@ -355,7 +355,13 @@ pub fn test_init(extra_arg: Vec<&str>) -> TestState { let listener = init_socket(&test_socket); - let child = Command::new(binary_path) + let mut cmd = Command::new(binary_path); + + #[cfg(target_env = "ohos")] + cmd.args(["-disable-seccomp"]); + + let child = cmd + .args(["-accel", "test"]) .args(["-qmp", &format!("unix:{},server,nowait", qmp_socket)]) .args(["-mod-test", &test_socket]) .args(extra_arg) diff --git a/tests/mod_test/tests/pvpanic_test.rs b/tests/mod_test/tests/pvpanic_test.rs index 0445159..03dfc67 100644 --- a/tests/mod_test/tests/pvpanic_test.rs +++ b/tests/mod_test/tests/pvpanic_test.rs @@ -15,11 +15,11 @@ use std::fs; use std::path::Path; use std::rc::Rc; +use devices::misc::pvpanic::{PVPANIC_CRASHLOADED, PVPANIC_PANICKED}; use devices::pci::config::{ PCI_CLASS_SYSTEM_OTHER, PCI_DEVICE_ID_REDHAT_PVPANIC, PCI_SUBDEVICE_ID_QEMU, PCI_VENDOR_ID_REDHAT, PCI_VENDOR_ID_REDHAT_QUMRANET, }; -use machine_manager::config::{PVPANIC_CRASHLOADED, PVPANIC_PANICKED}; use mod_test::{ libdriver::{machine::TestStdMachine, pci::*}, libtest::{test_init, TestState, MACHINE_TYPE_ARG}, diff --git a/tests/mod_test/tests/usb_camera_test.rs b/tests/mod_test/tests/usb_camera_test.rs index 66d7598..b519e1c 100644 --- a/tests/mod_test/tests/usb_camera_test.rs +++ b/tests/mod_test/tests/usb_camera_test.rs @@ -33,6 +33,7 @@ enum FmtType { Yuy2 = 0, Rgb565, Mjpg, + Nv12, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -115,8 +116,10 @@ fn format_index_to_fmt(idx: u8) -> FmtType { FmtType::Yuy2 } else if idx == 2 { FmtType::Mjpg - } else { + } else if idx == 3 { FmtType::Rgb565 + } else { + FmtType::Nv12 } } @@ -193,6 +196,16 @@ fn check_frame_data(fmt: &FmtType, data: &[u8]) { let pos = data.len() - 2; assert_eq!(data[pos..], [0xff, 0xf9]); } + FmtType::Nv12 => { + let len = data.len(); + for i in 0..(len / 2) { + assert_eq!(data[i], 76); + } + for i in 0..(len / 4) { + let idx = len / 2 + i * 2; + assert_eq!(data[idx..idx + 2], [90, 255]); + } + } } } @@ -263,6 +276,8 @@ fn test_xhci_camera_basic() { check_frame(&mut xhci, slot_id, 2, 2, 3); // Rgb check_frame(&mut xhci, slot_id, 3, 3, 3); + // Nv12 + check_frame(&mut xhci, slot_id, 4, 1, 3); test_state.borrow_mut().stop(); } diff --git a/trace/src/lib.rs b/trace/src/lib.rs index 1501a50..32646d2 100644 --- a/trace/src/lib.rs +++ b/trace/src/lib.rs @@ -19,7 +19,7 @@ pub(crate) mod hitrace; feature = "trace_to_ftrace", all(target_env = "ohos", feature = "trace_to_hitrace") ))] -pub(crate) mod trace_scope; +pub mod trace_scope; use std::{ fmt, diff --git a/trace/src/trace_scope.rs b/trace/src/trace_scope.rs index 4a02101..a860ef8 100644 --- a/trace/src/trace_scope.rs +++ b/trace/src/trace_scope.rs @@ -20,12 +20,14 @@ use crate::ftrace::write_trace_marker; static mut TRACE_SCOPE_COUNTER: AtomicI32 = AtomicI32::new(i32::MIN); +#[derive(Clone)] pub enum Scope { Common(TraceScope), Asyn(TraceScopeAsyn), None, } +#[derive(Clone)] pub struct TraceScope {} impl TraceScope { @@ -63,6 +65,7 @@ impl Drop for TraceScope { } } +#[derive(Clone)] pub struct TraceScopeAsyn { value: String, id: i32, diff --git a/trace/trace_generator/src/lib.rs b/trace/trace_generator/src/lib.rs index 008263c..4d1a80a 100644 --- a/trace/trace_generator/src/lib.rs +++ b/trace/trace_generator/src/lib.rs @@ -216,6 +216,11 @@ pub fn gen_trace_scope_func(_input: TokenStream) -> TokenStream { } }; + let func_decl = match desc.enabled { + true => quote!(pub fn #func_name(asyn: bool, #func_args) -> trace_scope::Scope), + false => quote!(pub fn #func_name(asyn: bool, #func_args)), + }; + let message_args = match desc.args.is_empty() { true => quote!(), false => { @@ -258,7 +263,7 @@ pub fn gen_trace_scope_func(_input: TokenStream) -> TokenStream { all(target_env = "ohos", feature = "trace_to_hitrace") ))] #[inline(always)] - pub fn #func_name(asyn: bool, #func_args) -> trace_scope::Scope { + #func_decl { #func_body } diff --git a/trace/trace_info/acpi.toml b/trace/trace_info/acpi.toml new file mode 100644 index 0000000..9b4352a --- /dev/null +++ b/trace/trace_info/acpi.toml @@ -0,0 +1,23 @@ +[[events]] +name = "ged_inject_acpi_event" +args = "event: u32" +message = "acpi_sevent {}" +enabled = true + +[[events]] +name = "ged_read" +args = "event: u32" +message = "acpi_sevent {}" +enabled = true + +[[events]] +name = "power_read" +args = "reg_idx: u64, value: u32" +message = "reg_idx {} value {}" +enabled = true + +[[events]] +name = "power_status_read" +args = "regs: &dyn fmt::Debug" +message = "regs {:?}" +enabled = true diff --git a/trace/trace_info/camera.toml b/trace/trace_info/camera.toml index 4e0ee65..e1f044f 100644 --- a/trace/trace_info/camera.toml +++ b/trace/trace_info/camera.toml @@ -21,3 +21,15 @@ name = "camera_get_format_by_index" args = "format_index: u8, frame_index: u8, out: &dyn fmt::Debug" message = "V4l2 fmt {}, frm {}, info {:?}." enabled = true + +[[scopes]] +name = "ohcam_get_frame" +args = "offset: usize, len: usize" +message = "ohcam get frame offset {} len {}" +enabled = true + +[[scopes]] +name = "ohcam_next_frame" +args = "frame_id: u64" +message = "ohcam next frame {}" +enabled = true diff --git a/trace/trace_info/device_legacy.toml b/trace/trace_info/device_legacy.toml index 14ed943..42bbae0 100644 --- a/trace/trace_info/device_legacy.toml +++ b/trace/trace_info/device_legacy.toml @@ -201,3 +201,27 @@ name = "pflash_write_data" args = "offset: u64, size: usize, value: &[u8], counter: u32" message = "data offset: 0x{:04x}, size: {}, value: 0x{:x?}, counter: 0x{:04x}" enabled = true + +[[events]] +name = "fwcfg_select_entry" +args = "key: u16, key_name: &'static str, ret: i32" +message = "key_value {} key_name {:?} ret {}" +enabled = true + +[[events]] +name = "fwcfg_add_entry" +args = "key: u16, key_name: &'static str, data: Vec" +message = "key_value {} key_name {:?} data {:?}" +enabled = true + +[[events]] +name = "fwcfg_read_data" +args = "value: u64" +message = "value {}" +enabled = true + +[[events]] +name = "fwcfg_add_file" +args = "index: usize, filename: &str, data_len: usize" +message = "index {} filename {:?} data_len {}" +enabled = true diff --git a/trace/trace_info/memory.toml b/trace/trace_info/memory.toml new file mode 100644 index 0000000..4ff485a --- /dev/null +++ b/trace/trace_info/memory.toml @@ -0,0 +1,41 @@ +[[scopes]] +name = "address_space_read" +args = "addr: &dyn fmt::Debug, count: u64" +message = "Memory: flatview_read addr {:?}, count {}" +enabled = true + +[[scopes]] +name = "address_space_write" +args = "addr: &dyn fmt::Debug, count: u64" +message = "Memory: flatview_write addr {:?}, count {}" +enabled = true + +[[scopes]] +name = "address_space_read_direct" +args = "host_addr: u64, count: usize" +message = "Memory: address_space_read_direct host_addr {}, count {}" +enabled = true + +[[scopes]] +name = "address_space_write_direct" +args = "host_addr: u64, count: usize" +message = "Memory: address_space_write_direct host_addr {}, count {}" +enabled = true + +[[scopes]] +name = "address_update_topology" +args = "" +message = "Memory: update opology" +enabled = true + +[[scopes]] +name = "pre_alloc" +args = "size: u64" +message = "Memory: pre_alloc ram size is {}" +enabled = true + +[[scopes]] +name = "init_memory" +args = "" +message = "Memory: init memory" +enabled = true diff --git a/trace/trace_info/misc.toml b/trace/trace_info/misc.toml index 78ac9d1..897a6bc 100644 --- a/trace/trace_info/misc.toml +++ b/trace/trace_info/misc.toml @@ -27,3 +27,63 @@ name = "scream_setup_alsa_hwp" args = "name: &str, hwp: &dyn fmt::Debug" message = "scream {} setup hardware parameters: {:?}" enabled = true + +[[events]] +name = "oh_scream_render_init" +args = "context: &dyn fmt::Debug" +message = "context: {:?}" +enabled = true + +[[events]] +name = "oh_scream_render_destroy" +args = "" +message = "" +enabled = true + +[[events]] +name = "oh_scream_capture_init" +args = "context: &dyn fmt::Debug" +message = "context: {:?}" +enabled = true + +[[events]] +name = "oh_scream_capture_destroy" +args = "" +message = "" +enabled = true + +[[events]] +name = "oh_scream_on_write_data_cb" +args = "len: usize" +message = "len: {}" +enabled = true + +[[events]] +name = "oh_scream_on_read_data_cb" +args = "len: usize" +message = "len: {}" +enabled = true + +[[scopes]] +name = "ohaudio_render_process" +args = "data: &dyn fmt::Debug" +message = "audio data {:?} to render" +enabled = true + +[[scopes]] +name = "ohaudio_capturer_process" +args = "data: &dyn fmt::Debug" +message = "audio data {:?} to capture" +enabled = true + +[[scopes]] +name = "ohaudio_write_cb" +args = "to_copy: i32, len: i32" +message = "OH audio expect audio data {} bytes, we have {} bytes" +enabled = true + +[[scopes]] +name = "ohaudio_read_cb" +args = "len: i32" +message = "OH audio captured {} bytes" +enabled = true diff --git a/trace/trace_info/ui.toml b/trace/trace_info/ui.toml index 091c056..1df6455 100644 --- a/trace/trace_info/ui.toml +++ b/trace/trace_info/ui.toml @@ -219,3 +219,63 @@ name = "console_select" args = "con_id: &dyn fmt::Debug" message = "console id={:?}" enabled = true + +[[events]] +name = "oh_event_mouse_button" +args = "msg_btn: u32, action: u32" +message = "msg_btn={} action={}" +enabled = true + +[[events]] +name = "oh_event_mouse_motion" +args = "x: f64, y: f64" +message = "x={} y={}" +enabled = true + +[[events]] +name = "oh_event_keyboard" +args = "keycode: u16, key_action: u16" +message = "keycode={} key_action={}" +enabled = true + +[[events]] +name = "oh_event_windowinfo" +args = "width: u32, height: u32" +message = "width={} height={}" +enabled = true + +[[events]] +name = "oh_event_scroll" +args = "direction: u32" +message = "direction={}" +enabled = true + +[[events]] +name = "oh_event_ledstate" +args = "state: u32" +message = "state={}" +enabled = true + +[[events]] +name = "oh_event_focus" +args = "state: u32" +message = "state={}" +enabled = true + +[[events]] +name = "oh_event_greet" +args = "id: u64" +message = "token_id={}" +enabled = true + +[[events]] +name = "oh_event_unsupported_type" +args = "ty: &dyn fmt::Debug, size: u32" +message = "type={:?} body_size={}" +enabled = true + +[[scopes]] +name = "handle_msg" +args = "opcode: &dyn fmt::Debug" +message = "handle ohui {:?} message" +enabled = true diff --git a/trace/trace_info/usb.toml b/trace/trace_info/usb.toml index fc5699a..d94588e 100644 --- a/trace/trace_info/usb.toml +++ b/trace/trace_info/usb.toml @@ -221,7 +221,7 @@ message = "status={:?}" enabled = true [[events]] -name = "usb_xhci_flush_ep_transfer" +name = "usb_xhci_cancel_all_ep_transfers" args = "slotid: &dyn fmt::Debug, epid: &dyn fmt::Debug" message = "slotid={:?} epid={:?}" enabled = true @@ -465,3 +465,105 @@ name = "usb_host_req_complete" args = "bus_num: u8, addr: u8, packet: u64, status: &dyn fmt::Debug, actual_length: usize" message = "dev bus 0x{:X} addr 0x{:X}, packet 0x{:#X}, status {:?} actual length {}" enabled = true + +[[events]] +name = "usb_uas_handle_control" +args = "packet_id: u32, device_id: &str, req: &[u8]" +message = "USB {} packet received on UAS {} device, the request is {:?}." +enabled = true + +[[events]] +name = "usb_uas_handle_iu_command" +args = "device_id: &str, cdb: u8" +message = "UAS {} device handling IU with cdb[0] {}." +enabled = true + +[[events]] +name = "usb_uas_fill_sense" +args = "status: u8, iu_len: usize, sense_len: usize" +message = "UAS device is filling sense with status {:02} URB length {} sense length {}." +enabled = true + +[[events]] +name = "usb_uas_fill_fake_sense" +args = "status: u8, iu_len: usize, sense_len: usize" +message = "UAS device is filling fake sense with status {:02} URB length {} sense length {}." +enabled = true + +[[events]] +name = "usb_uas_fill_packet" +args = "iovec_size: usize" +message = "UAS device is filling USB packet with iovec of size {}." +enabled = true + +[[events]] +name = "usb_uas_try_start_next_transfer" +args = "device_id: &str, xfer_len: i32" +message = "UAS {} device is trying to start next transfer of length {}." +enabled = true + +[[events]] +name = "usb_uas_fill_data_ready" +args = "device_id: &str, data_ready_sent: bool" +message = "UAS {} device set data_ready_sent to {}." +enabled = true + +[[events]] +name = "usb_uas_start_next_transfer" +args = "device_id: &str, stream: usize" +message = "UAS {} device starting a transfer on stream {}." +enabled = true + +[[events]] +name = "usb_uas_handle_data" +args = "device_id: &str, endpoint: u8, stream: usize" +message = "UAS {} device handling data on endpoint {} and stream {}." +enabled = true + +[[events]] +name = "usb_uas_command_received" +args = "packet_id: u32, device_id: &str" +message = "USB {} command packet received on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_command_completed" +args = "packet_id: u32, device_id: &str" +message = "USB {} command packet completed on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_status_received" +args = "packet_id: u32, device_id: &str" +message = "USB {} status packet received on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_status_completed" +args = "packet_id: u32, device_id: &str" +message = "USB {} status packet completed on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_status_queued_async" +args = "packet_id: u32, device_id: &str" +message = "USB {} status packet queued async on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_data_received" +args = "packet_id: u32, device_id: &str" +message = "USB {} data packet received on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_data_completed" +args = "packet_id: u32, device_id: &str" +message = "USB {} data packet completed on UAS {} device." +enabled = true + +[[events]] +name = "usb_uas_data_queued_async" +args = "packet_id: u32, device_id: &str" +message = "USB {} data packet queued async on UAS {} device." +enabled = true diff --git a/trace/trace_info/virtio.toml b/trace/trace_info/virtio.toml index 1e7def4..1f8ea24 100644 --- a/trace/trace_info/virtio.toml +++ b/trace/trace_info/virtio.toml @@ -196,12 +196,6 @@ args = "vring: u64, event_idx: u16" message = "virtqueue {:#X} set avail event idx {}" enabled = true -[[events]] -name = "virtio_tpt_common" -args = "func: &str, id: &str" -message = "{} for device {}" -enabled = true - [[events]] name = "virtio_tpt_read_common_config" args = "id: &str, offset: u64" @@ -303,3 +297,21 @@ name = "vhost_delete_mem_range_failed" args = "" message = "Vhost: deleting mem region failed: not matched." enabled = true + +[[scopes]] +name = "auto_msg_evt_handler" +args = "" +message = "Balloon: handle auto balloon message" +enabled = true + +[[scopes]] +name = "reporting_evt_handler" +args = "" +message = "Balloon: handle fpr message" +enabled = true + +[[scopes]] +name = "process_balloon_queue" +args = "" +message = "Balloon: handle normal balloon message" +enabled = true diff --git a/ui/src/input.rs b/ui/src/input.rs index 59f2b93..d540c99 100644 --- a/ui/src/input.rs +++ b/ui/src/input.rs @@ -252,6 +252,23 @@ impl KeyBoardState { #[derive(Default)] struct LedState { kbd_led: u8, + sync: Option>, +} + +pub trait SyncLedstate: Send + Sync { + fn sync_to_host(&self, state: u8) { + debug!("ledstate in guest is {}", state); + } +} + +impl LedState { + fn register_led_sync(&mut self, sync: Arc) { + self.sync = Some(sync); + } + + fn unregister_led_sync(&mut self) { + self.sync = None; + } } #[derive(Default)] @@ -328,6 +345,14 @@ impl Inputs { } } +pub fn register_led_sync(sync: Arc) { + LED_STATE.lock().unwrap().register_led_sync(sync); +} + +pub fn unregister_led_sync() { + LED_STATE.lock().unwrap().unregister_led_sync(); +} + pub fn register_keyboard(device: &str, kbd: Arc>) { INPUTS.lock().unwrap().register_kbd(device, kbd); } @@ -465,6 +490,9 @@ pub fn get_kbd_led_state() -> u8 { pub fn set_kbd_led_state(state: u8) { LED_STATE.lock().unwrap().kbd_led = state; + if let Some(sync_cb) = LED_STATE.lock().unwrap().sync.as_ref() { + sync_cb.sync_to_host(state); + } } pub fn keyboard_modifier_get(key_mod: KeyboardModifier) -> bool { diff --git a/ui/src/ohui_srv/channel.rs b/ui/src/ohui_srv/channel.rs index 861a163..64d6e0a 100755 --- a/ui/src/ohui_srv/channel.rs +++ b/ui/src/ohui_srv/channel.rs @@ -10,119 +10,98 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use std::os::raw::c_void; +use std::io::{ErrorKind, Read, Write}; +use std::os::fd::AsRawFd; use std::os::unix::io::RawFd; -use std::sync::RwLock; -use anyhow::Result; -use libc::iovec; +use anyhow::{bail, Result}; use log::error; use util::byte_code::ByteCode; -use util::unix::UnixSock; +use util::socket::{SocketListener, SocketStream}; +use util::unix::limit_permission; pub struct OhUiChannel { - pub sock: RwLock, - pub path: String, + listener: SocketListener, + stream: Option, } impl OhUiChannel { - pub fn new(path: &str) -> Self { - OhUiChannel { - sock: RwLock::new(UnixSock::new(path)), - path: String::from(path), - } - } + pub fn new(path: &str) -> Result { + let listener = match SocketListener::bind_by_uds(path) { + Ok(l) => l, + Err(e) => bail!("Failed to create listener with path {}, {:?}", path, e), + }; + limit_permission(path).unwrap_or_else(|e| { + error!( + "Failed to limit permission for ohui-sock {}, err: {:?}", + path, e + ); + }); - pub fn bind(&self) -> Result<()> { - self.sock.write().unwrap().bind(true) + Ok(OhUiChannel { + listener, + stream: None, + }) } pub fn get_listener_raw_fd(&self) -> RawFd { - self.sock.read().unwrap().get_listener_raw_fd() + self.listener.as_raw_fd() } - pub fn get_stream_raw_fd(&self) -> RawFd { - self.sock.read().unwrap().get_stream_raw_fd() + pub fn get_stream_raw_fd(&self) -> Option { + self.stream.as_ref().map(|s| s.as_raw_fd()) } - pub fn set_nonblocking(&self, nb: bool) -> Result<()> { - self.sock.read().unwrap().set_nonblocking(nb) - } - - pub fn set_listener_nonblocking(&self, nb: bool) -> Result<()> { - self.sock.read().unwrap().listen_set_nonblocking(nb) - } - - pub fn accept(&self) -> Result<()> { - self.sock.write().unwrap().accept() + pub fn accept(&mut self) -> Result<()> { + self.stream = Some(self.listener.accept()?); + Ok(()) } - pub fn send(&self, data: *const u8, len: usize) -> Result { - let mut iovs = Vec::with_capacity(1); - iovs.push(iovec { - iov_base: data as *mut c_void, - iov_len: len, - }); - let ret = self.sock.read().unwrap().send_msg(&mut iovs, &[])?; - Ok(ret) + pub fn disconnect(&mut self) { + self.stream = None; } +} - pub fn send_by_obj(&self, obj: &T) -> Result<()> { - let slice = obj.as_bytes(); - let mut left = slice.len(); - let mut count = 0_usize; +pub fn recv_slice(stream: &mut dyn Read, data: &mut [u8]) -> Result { + let len = data.len(); + let mut ret = 0; - while left > 0 { - let buf = &slice[count..]; - match self.send(buf.as_ptr(), left) { - Ok(n) => { - left -= n; - count += n; - } - Err(e) => { - if std::io::Error::last_os_error().raw_os_error().unwrap() == libc::EAGAIN { - continue; - } - return Err(e); + while ret < len { + match stream.read(&mut data[ret..len]) { + Ok(0) => break, + Ok(n) => ret += n, + Err(e) => { + let ek = e.kind(); + if ek != ErrorKind::WouldBlock && ek != ErrorKind::Interrupted { + bail!("recv_slice: error occurred: {:?}", e); } + break; } } - Ok(()) } + Ok(ret) +} - pub fn recv_slice(&self, data: &mut [u8]) -> Result { - let len = data.len(); - if len == 0 { - return Ok(0); - } - let ret = self.recv(data.as_mut_ptr(), len); - match ret { - Ok(n) => Ok(n), +pub fn send_obj(stream: &mut dyn Write, obj: &T) -> Result<()> { + let slice = obj.as_bytes(); + let mut left = slice.len(); + let mut count = 0_usize; + + while left > 0 { + match stream.write(&slice[count..]) { + Ok(n) => { + left -= n; + count += n; + } Err(e) => { - if std::io::Error::last_os_error() - .raw_os_error() - .unwrap_or(libc::EIO) - != libc::EAGAIN - { - error!("recv_slice(): error occurred: {}", e); + let ek = e.kind(); + if ek == ErrorKind::WouldBlock || ek == ErrorKind::Interrupted { + continue; } - Ok(0) + bail!(e); } } } - - pub fn recv(&self, data: *mut u8, len: usize) -> Result { - let mut iovs = Vec::with_capacity(1); - iovs.push(iovec { - iov_base: data as *mut c_void, - iov_len: len, - }); - - let ret = self.sock.read().unwrap().recv_msg(&mut iovs, &mut []); - match ret { - Ok((n, _)) => Ok(n), - Err(e) => Err(e.into()), - } - } + Ok(()) } diff --git a/ui/src/ohui_srv/mod.rs b/ui/src/ohui_srv/mod.rs index 1907946..899d263 100755 --- a/ui/src/ohui_srv/mod.rs +++ b/ui/src/ohui_srv/mod.rs @@ -35,6 +35,7 @@ use crate::{ DisplayChangeListenerOperations, DisplayMouse, DisplaySurface, DISPLAY_UPDATE_INTERVAL_DEFAULT, }, + input::{register_led_sync, unregister_led_sync}, pixman::{bytes_per_pixel, get_image_data, ref_pixman_image, unref_pixman_image}, }; use address_space::FileBackend; @@ -51,7 +52,7 @@ use util::{ NotifierOperation, }, pixman::{pixman_format_code_t, pixman_image_t}, - unix::do_mmap, + unix::{do_mmap, limit_permission}, }; #[derive(Debug, Clone)] @@ -92,9 +93,9 @@ pub struct OhUiServer { // guest surface for framebuffer surface: RwLock, // transfer channel via unix sock - channel: Arc, + channel: Arc>, // message handler - msg_handler: OhUiMsgHandler, + msg_handler: Arc, // connected or not connected: AtomicBool, // iothread processing unix socket @@ -110,13 +111,13 @@ pub struct OhUiServer { } impl OhUiServer { - fn init_channel(path: &String) -> Result> { + fn init_channel(path: &String) -> Result>> { let file_path = Path::new(path.as_str()).join("ohui.sock"); let sock_file = file_path .to_str() .ok_or_else(|| anyhow!("init_channel: Failed to get str from {}", path))?; TempCleaner::add_path(sock_file.to_string()); - Ok(Arc::new(OhUiChannel::new(sock_file))) + Ok(Arc::new(Mutex::new(OhUiChannel::new(sock_file)?))) } fn init_fb_file(path: &String) -> Result<(Option, u64)> { @@ -126,6 +127,12 @@ impl OhUiServer { .ok_or_else(|| anyhow!("init_fb_file: Failed to get str from {}", path))?; let fb_backend = FileBackend::new_mem(fb_file, VIRTIO_GPU_ENABLE_BAR0_SIZE)?; TempCleaner::add_path(fb_file.to_string()); + limit_permission(fb_file).unwrap_or_else(|e| { + error!( + "Failed to limit permission for ohui-fb {}, err: {:?}", + fb_file, e + ); + }); let host_addr = do_mmap( &Some(fb_backend.file.as_ref()), @@ -146,6 +153,12 @@ impl OhUiServer { .ok_or_else(|| anyhow!("init_cursor_file: Failed to get str from {}", path))?; let cursor_backend = FileBackend::new_mem(cursor_file, CURSOR_SIZE)?; TempCleaner::add_path(cursor_file.to_string()); + limit_permission(cursor_file).unwrap_or_else(|e| { + error!( + "Failed to limit permission for ohui-cursor {}, err: {:?}", + cursor_file, e + ); + }); let cursorbuffer = do_mmap( &Some(cursor_backend.file.as_ref()), @@ -167,8 +180,8 @@ impl OhUiServer { Ok(OhUiServer { passthru: OnceCell::new(), surface: RwLock::new(GuestSurface::new()), - channel: channel.clone(), - msg_handler: OhUiMsgHandler::new(channel), + channel, + msg_handler: Arc::new(OhUiMsgHandler::new()), connected: AtomicBool::new(false), iothread: OnceCell::new(), cursorbuffer, @@ -185,8 +198,8 @@ impl OhUiServer { } #[inline(always)] - fn get_channel(&self) -> &OhUiChannel { - self.channel.as_ref() + fn get_channel(&self) -> Arc> { + self.channel.clone() } #[inline(always)] @@ -252,6 +265,14 @@ impl OhUiServer { #[inline(always)] fn set_connect(&self, conn: bool) { self.connected.store(conn, Ordering::Relaxed); + if conn { + self.msg_handler.update_sock(self.channel.clone()); + register_led_sync(self.msg_handler.clone()); + } else { + unregister_led_sync(); + self.channel.lock().unwrap().disconnect(); + self.msg_handler.reset(); + } } fn set_iothread(&self, iothread: Option) { @@ -353,7 +374,7 @@ impl DisplayChangeListenerOperations for OhUiServer { pub fn ohui_init(ohui_srv: Arc, cfg: &DisplayConfig) -> Result<()> { // set iothread - ohui_srv.set_iothread(cfg.ohui_config.iothread.clone()); + ohui_srv.set_iothread(cfg.iothread.clone()); // Register ohui interface let dcl = Arc::new(Mutex::new(DisplayChangeListener::new( None, @@ -386,7 +407,12 @@ impl OhUiTrans { } fn get_fd(&self) -> RawFd { - self.server.get_channel().get_stream_raw_fd() + self.server + .get_channel() + .lock() + .unwrap() + .get_stream_raw_fd() + .unwrap() } } @@ -431,8 +457,6 @@ impl OhUiListener { } fn handle_connection(&self) -> Result<()> { - // Set stream sock with nonblocking - self.server.get_channel().set_nonblocking(true)?; // Register OhUiTrans read notifier ohui_register_event(OhUiTrans::new(self.server.clone()), self.server.clone())?; self.server.set_connect(true); @@ -442,11 +466,15 @@ impl OhUiListener { } fn accept(&self) -> Result<()> { - self.server.get_channel().accept() + self.server.get_channel().lock().unwrap().accept() } fn get_fd(&self) -> RawFd { - self.server.get_channel().get_listener_raw_fd() + self.server + .get_channel() + .lock() + .unwrap() + .get_listener_raw_fd() } } @@ -493,11 +521,7 @@ fn ohui_register_event(e: T, srv: Arc) -> Re } fn ohui_start_listener(server: Arc) -> Result<()> { - // Bind and set listener nonblocking - let channel = server.get_channel(); - channel.bind()?; - channel.set_listener_nonblocking(true)?; - ohui_register_event(OhUiListener::new(server.clone()), server.clone())?; + ohui_register_event(OhUiListener::new(server.clone()), server)?; info!("Successfully start listener."); Ok(()) } diff --git a/ui/src/ohui_srv/msg.rs b/ui/src/ohui_srv/msg.rs index ed217f5..c359d71 100755 --- a/ui/src/ohui_srv/msg.rs +++ b/ui/src/ohui_srv/msg.rs @@ -132,6 +132,12 @@ pub struct LedstateEvent { impl ByteCode for LedstateEvent {} +impl LedstateEvent { + pub fn new(state: u32) -> Self { + LedstateEvent { state } + } +} + #[repr(C, packed)] #[derive(Debug, Default, Copy, Clone)] pub struct GreetEvent { diff --git a/ui/src/ohui_srv/msg_handle.rs b/ui/src/ohui_srv/msg_handle.rs index f00853b..1e085e5 100755 --- a/ui/src/ohui_srv/msg_handle.rs +++ b/ui/src/ohui_srv/msg_handle.rs @@ -11,21 +11,27 @@ // See the Mulan PSL v2 for more details. use std::collections::HashMap; +use std::os::fd::{FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex, RwLock}; -use anyhow::{anyhow, bail, Result}; -use log::{error, warn}; +use anyhow::{anyhow, bail, Context, Result}; +use log::error; use util::byte_code::ByteCode; -use super::{channel::OhUiChannel, msg::*}; +use super::{ + channel::{recv_slice, send_obj, OhUiChannel}, + msg::*, +}; use crate::{ console::{get_active_console, graphic_hardware_ui_info}, input::{ self, get_kbd_led_state, input_button, input_move_abs, input_point_sync, keyboard_update, - release_all_key, trigger_key, Axis, ABS_MAX, CAPS_LOCK_LED, INPUT_BUTTON_WHEEL_DOWN, - INPUT_BUTTON_WHEEL_LEFT, INPUT_BUTTON_WHEEL_RIGHT, INPUT_BUTTON_WHEEL_UP, INPUT_POINT_BACK, - INPUT_POINT_FORWARD, INPUT_POINT_LEFT, INPUT_POINT_MIDDLE, INPUT_POINT_RIGHT, - KEYCODE_CAPS_LOCK, KEYCODE_NUM_LOCK, KEYCODE_SCR_LOCK, NUM_LOCK_LED, SCROLL_LOCK_LED, + release_all_key, trigger_key, Axis, SyncLedstate, ABS_MAX, CAPS_LOCK_LED, + INPUT_BUTTON_WHEEL_DOWN, INPUT_BUTTON_WHEEL_LEFT, INPUT_BUTTON_WHEEL_RIGHT, + INPUT_BUTTON_WHEEL_UP, INPUT_POINT_BACK, INPUT_POINT_FORWARD, INPUT_POINT_LEFT, + INPUT_POINT_MIDDLE, INPUT_POINT_RIGHT, KEYCODE_CAPS_LOCK, KEYCODE_NUM_LOCK, + KEYCODE_SCR_LOCK, NUM_LOCK_LED, SCROLL_LOCK_LED, }, keycode::{DpyMod, KeyCode}, }; @@ -119,42 +125,54 @@ impl WindowState { } } +#[derive(Default)] pub struct OhUiMsgHandler { state: Mutex, hmcode2svcode: HashMap, - reader: Mutex, - writer: Mutex, + reader: Mutex>, + writer: Mutex>, +} + +impl SyncLedstate for OhUiMsgHandler { + fn sync_to_host(&self, state: u8) { + if let Some(writer) = self.writer.lock().unwrap().as_mut() { + let body = LedstateEvent::new(state as u32); + if let Err(e) = writer.send_message(EventType::Ledstate, &body) { + error!("sync_to_host: failed to send message with error {e}"); + } + } + } } impl OhUiMsgHandler { - pub fn new(channel: Arc) -> Self { + pub fn new() -> Self { OhUiMsgHandler { state: Mutex::new(WindowState::default()), hmcode2svcode: KeyCode::keysym_to_qkeycode(DpyMod::Ohui), - reader: Mutex::new(MsgReader::new(channel.clone())), - writer: Mutex::new(MsgWriter::new(channel)), + reader: Mutex::new(None), + writer: Mutex::new(None), } } + pub fn update_sock(&self, channel: Arc>) { + let fd = channel.lock().unwrap().get_stream_raw_fd().unwrap(); + *self.reader.lock().unwrap() = Some(MsgReader::new(fd)); + *self.writer.lock().unwrap() = Some(MsgWriter::new(fd)); + } + pub fn handle_msg(&self, token_id: Arc>) -> Result<()> { - let mut reader = self.reader.lock().unwrap(); + let mut locked_reader = self.reader.lock().unwrap(); + let reader = locked_reader + .as_mut() + .with_context(|| "handle_msg: no connection established")?; if !reader.recv()? { return Ok(()); } let hdr = &reader.header; - let body_size = hdr.size as usize; let event_type = hdr.event_type; - if body_size != event_msg_data_len(hdr.event_type) { - warn!( - "{:?} data len is wrong, we want {}, but receive {}", - event_type, - event_msg_data_len(hdr.event_type), - body_size - ); - reader.clear(); - return Ok(()); - } + let body_size = hdr.size as size; + trace::trace_scope_start!(handle_msg, args = (&event_type)); let body_bytes = reader.body.as_ref().unwrap(); if let Err(e) = match event_type { @@ -181,6 +199,7 @@ impl OhUiMsgHandler { } EventType::Focus => { let body = FocusEvent::from_bytes(&body_bytes[..]).unwrap(); + trace::oh_event_focus(body.state); if body.state == CLIENT_FOCUSOUT_EVENT { reader.clear(); release_all_key()?; @@ -194,6 +213,7 @@ impl OhUiMsgHandler { } EventType::Greet => { let body = GreetEvent::from_bytes(&body_bytes[..]).unwrap(); + trace::oh_event_greet(body.token_id); *token_id.write().unwrap() = body.token_id; Ok(()) } @@ -202,6 +222,7 @@ impl OhUiMsgHandler { "unsupported type {:?} and body size {}", event_type, body_size ); + trace::oh_event_unsupported_type(&event_type, body_size.try_into().unwrap()); Ok(()) } } { @@ -213,6 +234,7 @@ impl OhUiMsgHandler { fn handle_mouse_button(&self, mb: &MouseButtonEvent) -> Result<()> { let (msg_btn, action) = (mb.button, mb.btn_action); + trace::oh_event_mouse_button(msg_btn, action); let btn = match msg_btn { CLIENT_MOUSE_BUTTON_LEFT => INPUT_POINT_LEFT, CLIENT_MOUSE_BUTTON_RIGHT => INPUT_POINT_RIGHT, @@ -236,19 +258,17 @@ impl OhUiMsgHandler { hot_y: u32, size_per_pixel: u32, ) { - let body = HWCursorEvent::new(w, h, hot_x, hot_y, size_per_pixel); - if let Err(e) = self - .writer - .lock() - .unwrap() - .send_message(EventType::CursorDefine, &body) - { - error!("handle_cursor_define: failed to send message with error {e}"); + if let Some(writer) = self.writer.lock().unwrap().as_mut() { + let body = HWCursorEvent::new(w, h, hot_x, hot_y, size_per_pixel); + if let Err(e) = writer.send_message(EventType::CursorDefine, &body) { + error!("handle_cursor_define: failed to send message with error {e}"); + } } } // NOTE: we only support absolute position info now, that means usb-mouse does not work. fn handle_mouse_motion(&self, mm: &MouseMotionEvent) -> Result<()> { + trace::oh_event_mouse_motion(mm.x, mm.y); self.state.lock().unwrap().move_pointer(mm.x, mm.y) } @@ -263,6 +283,7 @@ impl OhUiMsgHandler { bail!("not supported keycode {}", hmkey); } }; + trace::oh_event_keyboard(keycode, ke.key_action); self.state .lock() .unwrap() @@ -280,6 +301,7 @@ impl OhUiMsgHandler { }; self.state.lock().unwrap().press_btn(dir)?; self.state.lock().unwrap().release_btn(dir)?; + trace::oh_event_scroll(dir); Ok(()) } @@ -295,6 +317,7 @@ impl OhUiMsgHandler { error!("handle_windowinfo failed with error {e}"); } } + trace::oh_event_windowinfo(wi.width, wi.height); } fn handle_ledstate(&self, led: &LedstateEvent) { @@ -302,75 +325,95 @@ impl OhUiMsgHandler { .lock() .unwrap() .update_host_ledstate(led.state as u8); + trace::oh_event_ledstate(led.state); } pub fn send_windowinfo(&self, w: u32, h: u32) { self.state.lock().unwrap().update_window_info(w, h); - let body = WindowInfoEvent::new(w, h); - self.writer - .lock() - .unwrap() - .send_message(EventType::WindowInfo, &body) - .unwrap(); + if let Some(writer) = self.writer.lock().unwrap().as_mut() { + let body = WindowInfoEvent::new(w, h); + if let Err(e) = writer.send_message(EventType::WindowInfo, &body) { + error!("send_windowinfo: failed to send message with error {e}"); + } + } } pub fn handle_dirty_area(&self, x: u32, y: u32, w: u32, h: u32) { - let body = FrameBufferDirtyEvent::new(x, y, w, h); - if let Err(e) = self - .writer - .lock() - .unwrap() - .send_message(EventType::FrameBufferDirty, &body) - { - error!("handle_dirty_area: failed to send message with error {e}"); + if let Some(writer) = self.writer.lock().unwrap().as_mut() { + let body = FrameBufferDirtyEvent::new(x, y, w, h); + if let Err(e) = writer.send_message(EventType::FrameBufferDirty, &body) { + error!("handle_dirty_area: failed to send message with error {e}"); + } } } + + pub fn reset(&self) { + *self.reader.lock().unwrap() = None; + *self.writer.lock().unwrap() = None; + } } struct MsgReader { - /// socket to read - channel: Arc, /// cache for header - pub header: EventMsgHdr, + header: EventMsgHdr, /// received byte size of header - pub header_ready: usize, + header_ready: usize, /// cache of body - pub body: Option>, + body: Option>, /// received byte size of body - pub body_ready: usize, + body_ready: usize, + /// UnixStream to read + sock: UnixStream, } impl MsgReader { - pub fn new(channel: Arc) -> Self { + pub fn new(fd: RawFd) -> Self { MsgReader { - channel, header: EventMsgHdr::default(), header_ready: 0, body: None, body_ready: 0, + // SAFETY: The fd is valid only when the new connection has been established + // and MsgReader instance would be destroyed when disconnected. + sock: unsafe { UnixStream::from_raw_fd(fd) }, } } pub fn recv(&mut self) -> Result { if self.recv_header()? { + self.check_header()?; return self.recv_body(); } Ok(false) } - pub fn clear(&mut self) { + fn clear(&mut self) { self.header_ready = 0; self.body_ready = 0; self.body = None; } + fn check_header(&mut self) -> Result<()> { + let expected_size = event_msg_data_len(self.header.event_type); + if expected_size != self.header.size as usize { + self.clear(); + bail!( + "{:?} data len is wrong, we want {}, but receive {}", + self.header.event_type as EventType, + expected_size, + self.header.size as usize, + ); + } + Ok(()) + } + fn recv_header(&mut self) -> Result { if self.header_ready == EVENT_MSG_HDR_SIZE as usize { return Ok(true); } let buf = self.header.as_mut_bytes(); - self.header_ready += self.channel.recv_slice(&mut buf[self.header_ready..])?; + self.header_ready += recv_slice(&mut self.sock, &mut buf[self.header_ready..])?; Ok(self.header_ready == EVENT_MSG_HDR_SIZE as usize) } @@ -392,22 +435,32 @@ impl MsgReader { unsafe { buf.set_len(body_size); } - self.body_ready += self.channel.recv_slice(&mut buf[self.body_ready..])?; + self.body_ready += recv_slice(&mut self.sock, &mut buf[self.body_ready..])?; Ok(self.body_ready == body_size) } } -struct MsgWriter(Arc); +struct MsgWriter { + sock: UnixStream, +} impl MsgWriter { - fn new(channel: Arc) -> Self { - MsgWriter(channel) + fn new(fd: RawFd) -> Self { + Self { + // SAFETY: The fd is valid only when the new connection has been established + // and MsgWriter instance would be destroyed when disconnected. + sock: unsafe { UnixStream::from_raw_fd(fd) }, + } } - fn send_message(&self, t: EventType, body: &T) -> Result<()> { + fn send_message( + &mut self, + t: EventType, + body: &T, + ) -> Result<()> { let hdr = EventMsgHdr::new(t); - self.0.send_by_obj(&hdr)?; - self.0.send_by_obj(body) + send_obj(&mut self.sock, &hdr)?; + send_obj(&mut self.sock, body) } } diff --git a/ui/src/vnc/client_io.rs b/ui/src/vnc/client_io.rs index 347e7d5..b4fa905 100644 --- a/ui/src/vnc/client_io.rs +++ b/ui/src/vnc/client_io.rs @@ -47,8 +47,8 @@ use crate::{ use util::{ bitmap::Bitmap, loop_context::{ - gen_delete_notifiers, read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, - NotifierOperation, + create_new_eventfd, gen_delete_notifiers, read_fd, EventNotifier, EventNotifierHelper, + NotifierCallback, NotifierOperation, }, }; @@ -392,8 +392,8 @@ impl ClientState { pub fn new(addr: String) -> Self { ClientState { addr, - disconn_evt: Arc::new(Mutex::new(EventFd::new(libc::EFD_NONBLOCK).unwrap())), - write_fd: Arc::new(Mutex::new(EventFd::new(libc::EFD_NONBLOCK).unwrap())), + disconn_evt: Arc::new(Mutex::new(create_new_eventfd().unwrap())), + write_fd: Arc::new(Mutex::new(create_new_eventfd().unwrap())), in_buffer: Arc::new(Mutex::new(BuffPool::new())), out_buffer: Arc::new(Mutex::new(BuffPool::new())), client_dpm: Arc::new(Mutex::new(DisplayMode::default())), diff --git a/ui/src/vnc/mod.rs b/ui/src/vnc/mod.rs index 9f0a7e9..ff2e48c 100644 --- a/ui/src/vnc/mod.rs +++ b/ui/src/vnc/mod.rs @@ -290,7 +290,7 @@ pub fn vnc_init(vnc: &Option, object: &ObjectConfig) -> Result<()> { None => return Ok(()), }; - let addr = format!("{}:{}", vnc_cfg.ip, vnc_cfg.port); + let addr = format!("{}:{}", vnc_cfg.addr.0, vnc_cfg.addr.1); let listener: TcpListener = match TcpListener::bind(addr.as_str()) { Ok(l) => l, Err(e) => { diff --git a/ui/src/vnc/server_io.rs b/ui/src/vnc/server_io.rs index 76af9e8..ac577df 100644 --- a/ui/src/vnc/server_io.rs +++ b/ui/src/vnc/server_io.rs @@ -221,7 +221,7 @@ impl SecurityType { // Tls configuration. if let Some(tls_cred) = object.tls_object.get(&vnc_cfg.tls_creds) { let tlscred = TlsCreds { - cred_type: tls_cred.cred_type.clone(), + cred_type: "x509".to_string(), dir: tls_cred.dir.clone(), endpoint: tls_cred.endpoint.clone(), verifypeer: tls_cred.verifypeer, diff --git a/util/Cargo.toml b/util/Cargo.toml index 5bf691e..8466ed4 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -11,7 +11,7 @@ license = "Mulan PSL v2" arc-swap = "1.6.0" thiserror = "1.0" anyhow = "1.0" -kvm-bindings = { version = "0.10.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } nix = { version = "0.26.2", default-features = false, features = ["poll", "term", "time", "signal", "fs", "feature"] } libc = "0.2" libloading = "0.7.4" @@ -28,5 +28,6 @@ trace = {path = "../trace"} default = [] usb_camera_v4l2 = ["dep:v4l2-sys-mit"] usb_camera_oh = [] +usb_host = [] scream_ohaudio = [] pixman = [] diff --git a/util/src/aio/mod.rs b/util/src/aio/mod.rs index d8d733d..a9427f6 100644 --- a/util/src/aio/mod.rs +++ b/util/src/aio/mod.rs @@ -32,6 +32,7 @@ use uring::IoUringContext; use vmm_sys_util::eventfd::EventFd; use super::link_list::{List, Node}; +use crate::loop_context::create_new_eventfd; use crate::num_ops::{round_down, round_up}; use crate::thread_pool::ThreadPool; use crate::unix::host_page_size; @@ -66,7 +67,7 @@ pub enum AioEngine { } impl FromStr for AioEngine { - type Err = (); + type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result { match s { @@ -74,7 +75,7 @@ impl FromStr for AioEngine { AIO_NATIVE => Ok(AioEngine::Native), AIO_IOURING => Ok(AioEngine::IoUring), AIO_THREADS => Ok(AioEngine::Threads), - _ => Err(()), + _ => Err(anyhow!("Unknown aio type")), } } } @@ -90,8 +91,9 @@ impl ToString for AioEngine { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum WriteZeroesState { + #[default] Off, On, Unmap, @@ -503,7 +505,7 @@ impl Aio { thread_pool: Option>, ) -> Result { let max_events: usize = 128; - let fd = EventFd::new(libc::EFD_NONBLOCK)?; + let fd = create_new_eventfd()?; let ctx: Option>> = if let Some(pool) = thread_pool { let threads_aio_ctx = ThreadsAioContext::new(max_events as u32, &fd, pool); match engine { @@ -812,8 +814,9 @@ fn iovec_is_zero(iovecs: &[Iovec]) -> bool { } pub fn iovecs_split(iovecs: Vec, mut size: u64) -> (Vec, Vec) { - let mut begin = Vec::new(); - let mut end = Vec::new(); + let len = iovecs.len(); + let mut begin: Vec = Vec::with_capacity(len); + let mut end: Vec = Vec::with_capacity(len); for iov in iovecs { if size == 0 { end.push(iov); diff --git a/util/src/aio/raw.rs b/util/src/aio/raw.rs index 69cb0ba..4654a7c 100644 --- a/util/src/aio/raw.rs +++ b/util/src/aio/raw.rs @@ -37,7 +37,8 @@ pub fn raw_read(fd: RawFd, buf: u64, size: usize, offset: usize) -> i64 { } if ret < 0 { error!( - "Failed to pread: buf{}, size{}, offset{}, errno{}.", + "Failed to pread: fd {} buf {:#x}, size {}, offset{:#x}, errno {}.", + fd, buf, size, offset, @@ -66,7 +67,8 @@ pub fn raw_readv(fd: RawFd, iovec: &[Iovec], offset: usize) -> i64 { } if ret < 0 { error!( - "Failed to preadv: offset{}, errno{}.", + "Failed to preadv: fd {} offset {:#x}, errno {}.", + fd, offset, nix::errno::errno(), ); @@ -93,7 +95,8 @@ pub fn raw_write(fd: RawFd, buf: u64, size: usize, offset: usize) -> i64 { } if ret < 0 { error!( - "Failed to pwrite: buf{}, size{}, offset{}, errno{}.", + "Failed to pwrite: fd {} buf {:#x}, size{}, offset {:#x}, errno {}.", + fd, buf, size, offset, @@ -122,7 +125,8 @@ pub fn raw_writev(fd: RawFd, iovec: &[Iovec], offset: usize) -> i64 { } if ret < 0 { error!( - "Failed to pwritev: offset{}, errno{}.", + "Failed to pwritev: fd {} offset {:#x}, errno {}.", + fd, offset, nix::errno::errno(), ); @@ -134,7 +138,7 @@ pub fn raw_datasync(fd: RawFd) -> i64 { // SAFETY: fd is valid. let ret = unsafe { i64::from(fdatasync(fd)) }; if ret < 0 { - error!("Failed to fdatasync: errno{}.", nix::errno::errno()); + error!("Failed to fdatasync: errno {}.", nix::errno::errno()); } ret } @@ -143,7 +147,7 @@ pub fn raw_discard(fd: RawFd, offset: usize, size: u64) -> i32 { let ret = do_fallocate(fd, FallocateMode::PunchHole, true, offset as u64, size); if ret < 0 && ret != -libc::ENOTSUP { - error!("Failed to fallocate for {}, errno {}.", fd, ret); + error!("Failed to fallocate for fd {}, errno {}.", fd, ret); } ret } diff --git a/util/src/device_tree.rs b/util/src/device_tree.rs index ebb50d0..b03cf14 100644 --- a/util/src/device_tree.rs +++ b/util/src/device_tree.rs @@ -27,6 +27,12 @@ pub const GIC_FDT_IRQ_TYPE_PPI: u32 = 1; pub const IRQ_TYPE_EDGE_RISING: u32 = 1; pub const IRQ_TYPE_LEVEL_HIGH: u32 = 4; +// PHANDEL definitions for RISC-V +pub const AIA_APLIC_PHANDLE: u32 = 1; +pub const AIA_IMSIC_PHANDLE: u32 = 2; +pub const PHANDLE_CPU: u32 = 3; +pub const INTC_PHANDLE_START: u32 = 256 + PHANDLE_CPU; + pub const FDT_MAX_SIZE: u32 = 0x1_0000; // Magic number in fdt header(big-endian). diff --git a/util/src/leak_bucket.rs b/util/src/leak_bucket.rs index 5dd65f2..cde308c 100644 --- a/util/src/leak_bucket.rs +++ b/util/src/leak_bucket.rs @@ -20,7 +20,7 @@ use log::error; use vmm_sys_util::eventfd::EventFd; use crate::clock::get_current_time; -use crate::loop_context::EventLoopContext; +use crate::loop_context::{create_new_eventfd, EventLoopContext}; use crate::time::NANOSECONDS_PER_SECOND; /// Used to improve the accuracy of bucket level. @@ -53,7 +53,7 @@ impl LeakBucket { level: 0, prev_time: get_current_time(), timer_started: false, - timer_wakeup: Arc::new(EventFd::new(libc::EFD_NONBLOCK)?), + timer_wakeup: Arc::new(create_new_eventfd()?), }) } diff --git a/util/src/lib.rs b/util/src/lib.rs index f861d6f..37bb3fa 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -17,7 +17,7 @@ pub mod byte_code; pub mod checksum; pub mod clock; pub mod daemonize; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub mod device_tree; pub mod edid; pub mod error; diff --git a/util/src/loop_context.rs b/util/src/loop_context.rs index 10e4cf7..957fdb4 100644 --- a/util/src/loop_context.rs +++ b/util/src/loop_context.rs @@ -13,14 +13,15 @@ use std::collections::BTreeMap; use std::fmt; use std::fmt::Debug; +use std::io::Error; use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Barrier, Mutex, RwLock}; use std::time::{Duration, Instant}; use anyhow::{anyhow, Context, Result}; -use libc::{c_void, read, EFD_NONBLOCK}; +use libc::{c_void, read, EFD_CLOEXEC, EFD_NONBLOCK}; use log::{error, warn}; use nix::errno::Errno; use nix::{ @@ -54,6 +55,10 @@ pub enum NotifierOperation { Park = 16, /// Resume a file descriptor from the event table Resume = 32, + /// Add events to current event table for a file descriptor + AddEvents = 64, + /// Delete events from current event table for a file descriptor + DeleteEvents = 128, } #[derive(Debug, PartialEq)] @@ -154,6 +159,10 @@ pub fn gen_delete_notifiers(fds: &[RawFd]) -> Vec { notifiers } +pub fn create_new_eventfd() -> Result { + EventFd::new(EFD_NONBLOCK | EFD_CLOEXEC) +} + /// EventLoop manager, advise continue running or stop running pub trait EventLoopManager: Send + Sync { fn loop_should_exit(&self) -> bool; @@ -215,6 +224,8 @@ pub struct EventLoopContext { pub thread_pool: Arc, /// Record VM clock state. pub clock_state: Arc>, + /// The io thread barrier. + pub thread_exit_barrier: Arc, } impl Drop for EventLoopContext { @@ -231,11 +242,11 @@ unsafe impl Send for EventLoopContext {} impl EventLoopContext { /// Constructs a new `EventLoopContext`. - pub fn new() -> Self { + pub fn new(thread_exit_barrier: Arc) -> Self { let mut ctx = EventLoopContext { epoll: Epoll::new().unwrap(), manager: None, - kick_event: EventFd::new(EFD_NONBLOCK).unwrap(), + kick_event: create_new_eventfd().unwrap(), kick_me: AtomicBool::new(false), kicked: AtomicBool::new(false), events: Arc::new(RwLock::new(BTreeMap::new())), @@ -245,6 +256,7 @@ impl EventLoopContext { timer_next_id: AtomicU64::new(0), thread_pool: Arc::new(ThreadPool::default()), clock_state: Arc::new(Mutex::new(ClockState::default())), + thread_exit_barrier, }; ctx.init_kick(); ctx @@ -466,6 +478,35 @@ impl EventLoopContext { Ok(()) } + fn update_events_for_fd(&mut self, event: &EventNotifier, add: bool) -> Result<()> { + let mut events_map = self.events.write().unwrap(); + match events_map.get_mut(&event.raw_fd) { + Some(notifier) => { + let new_events = if add { + event.event | notifier.event + } else { + !event.event & notifier.event + }; + if new_events != notifier.event { + self.epoll + .ctl( + ControlOperation::Modify, + notifier.raw_fd, + EpollEvent::new(new_events, &**notifier as *const _ as u64), + ) + .with_context(|| { + format!("Failed to add events, event fd: {}", notifier.raw_fd) + })?; + notifier.event = new_events; + } + } + _ => { + return Err(anyhow!(UtilError::NoRegisterFd(event.raw_fd))); + } + } + Ok(()) + } + /// update fds registered to `EventLoop` according to the operation type. /// /// # Arguments @@ -490,6 +531,12 @@ impl EventLoopContext { NotifierOperation::Resume => { self.resume_event(&en)?; } + NotifierOperation::AddEvents => { + self.update_events_for_fd(&en, true)?; + } + NotifierOperation::DeleteEvents => { + self.update_events_for_fd(&en, false)?; + } } } self.kick(); @@ -677,12 +724,6 @@ impl EventLoopContext { } } -impl Default for EventLoopContext { - fn default() -> Self { - Self::new() - } -} - pub fn read_fd(fd: RawFd) -> u64 { let mut value: u64 = 0; @@ -707,6 +748,7 @@ pub fn read_fd(fd: RawFd) -> u64 { #[cfg(test)] mod test { use std::os::unix::io::{AsRawFd, RawFd}; + use std::sync::Barrier; use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; @@ -755,7 +797,7 @@ mod test { #[test] fn basic_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let mut notifiers = Vec::new(); let fd1 = EventFd::new(EFD_NONBLOCK).unwrap(); let fd1_related = EventFd::new(EFD_NONBLOCK).unwrap(); @@ -783,7 +825,7 @@ mod test { #[test] fn parked_event_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let mut notifiers = Vec::new(); let fd1 = EventFd::new(EFD_NONBLOCK).unwrap(); let fd2 = EventFd::new(EFD_NONBLOCK).unwrap(); @@ -830,7 +872,7 @@ mod test { #[test] fn event_handler_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let mut notifiers = Vec::new(); let fd1 = EventFd::new(EFD_NONBLOCK).unwrap(); let fd1_related = EventFd::new(EFD_NONBLOCK).unwrap(); @@ -869,7 +911,7 @@ mod test { #[test] fn error_operation_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let fd1 = EventFd::new(EFD_NONBLOCK).unwrap(); let leisure_fd = EventFd::new(EFD_NONBLOCK).unwrap(); @@ -906,7 +948,7 @@ mod test { #[test] fn error_parked_operation_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let fd1 = EventFd::new(EFD_NONBLOCK).unwrap(); let fd2 = EventFd::new(EFD_NONBLOCK).unwrap(); @@ -941,7 +983,7 @@ mod test { #[test] fn fd_released_test() { - let mut mainloop = EventLoopContext::new(); + let mut mainloop = EventLoopContext::new(Arc::new(Barrier::new(1))); let fd = mainloop.create_event(); // In this case, fd is already closed. But program was wrote to ignore the error. diff --git a/util/src/ohos_binding/audio/mod.rs b/util/src/ohos_binding/audio/mod.rs index 7214e32..4dd7687 100755 --- a/util/src/ohos_binding/audio/mod.rs +++ b/util/src/ohos_binding/audio/mod.rs @@ -197,6 +197,7 @@ pub enum AudioProcessCb { ), } +#[derive(Debug)] pub struct AudioContext { stream_type: AudioStreamType, spec: AudioSpec, @@ -300,6 +301,10 @@ impl AudioContext { call_capi!(OH_AudioRenderer_Start(self.renderer)) } + pub fn flush_renderer(&self) -> Result<(), OAErr> { + call_capi!(OH_AudioRenderer_Flush(self.renderer)) + } + pub fn new(stream_type: AudioStreamType) -> Self { Self { stream_type, diff --git a/util/src/ohos_binding/camera.rs b/util/src/ohos_binding/camera.rs index 16cb7a3..e481e88 100644 --- a/util/src/ohos_binding/camera.rs +++ b/util/src/ohos_binding/camera.rs @@ -27,6 +27,8 @@ pub const CAMERA_FORMAT_YCBCR420: i32 = 2; #[allow(unused)] pub const CAMERA_FORMAT_RGB18888: i32 = 3; pub const CAMERA_FORMAT_YUV420SP: i32 = 1003; +pub const CAMERA_FORMAT_NV12: i32 = 1004; +pub const CAMERA_FORMAT_YUYV422: i32 = 1005; pub const CAMERA_FORMAT_MJPEG: i32 = 2000; #[derive(Clone)] @@ -108,6 +110,9 @@ impl OhCamera { ) -> Result<()> { // SAFETY: We call related API sequentially for specified ctx. unsafe { + if (self.capi.create_session)(self.ctx) != 0 { + bail!("OH Camera: failed to create session"); + } if (self.capi.pre_start)(self.ctx, buffer_proc, broken_proc) != 0 { bail!("OH Camera: failed to prestart camera stream"); } @@ -121,7 +126,6 @@ impl OhCamera { pub fn reset_camera(&self) { // SAFETY: We call related API sequentially for specified ctx. unsafe { - (self.capi.create_session)(self.ctx); (self.capi.init_cameras)(self.ctx); (self.capi.init_profiles)(self.ctx); } diff --git a/util/src/ohos_binding/hwf_adapter/mod.rs b/util/src/ohos_binding/hwf_adapter/mod.rs index ffa1145..2709c81 100644 --- a/util/src/ohos_binding/hwf_adapter/mod.rs +++ b/util/src/ohos_binding/hwf_adapter/mod.rs @@ -12,6 +12,8 @@ #[cfg(feature = "usb_camera_oh")] pub mod camera; +#[cfg(feature = "usb_host")] +pub mod usb; use std::ffi::OsStr; use std::sync::Arc; @@ -23,6 +25,8 @@ use once_cell::sync::Lazy; #[cfg(feature = "usb_camera_oh")] use camera::CamFuncTable; +#[cfg(feature = "usb_host")] +use usb::UsbFuncTable; static LIB_HWF_ADAPTER: Lazy = Lazy::new(|| // SAFETY: The dynamic library should be always existing. @@ -40,6 +44,8 @@ struct LibHwfAdapter { library: Library, #[cfg(feature = "usb_camera_oh")] camera: Arc, + #[cfg(feature = "usb_host")] + usb: Arc, } impl LibHwfAdapter { @@ -52,10 +58,17 @@ impl LibHwfAdapter { CamFuncTable::new(&library).with_context(|| "failed to init camera function table")?, ); + #[cfg(feature = "usb_host")] + let usb = Arc::new( + UsbFuncTable::new(&library).with_context(|| "failed to init usb function table")?, + ); + Ok(Self { library, #[cfg(feature = "usb_camera_oh")] camera, + #[cfg(feature = "usb_host")] + usb, }) } @@ -63,9 +76,19 @@ impl LibHwfAdapter { fn get_camera_api(&self) -> Arc { self.camera.clone() } + + #[cfg(feature = "usb_host")] + fn get_usb_api(&self) -> Arc { + self.usb.clone() + } } #[cfg(feature = "usb_camera_oh")] pub fn hwf_adapter_camera_api() -> Arc { LIB_HWF_ADAPTER.get_camera_api() } + +#[cfg(feature = "usb_host")] +pub fn hwf_adapter_usb_api() -> Arc { + LIB_HWF_ADAPTER.get_usb_api() +} diff --git a/util/src/ohos_binding/hwf_adapter/usb.rs b/util/src/ohos_binding/hwf_adapter/usb.rs new file mode 100644 index 0000000..abb3cc7 --- /dev/null +++ b/util/src/ohos_binding/hwf_adapter/usb.rs @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::os::raw::c_int; + +use anyhow::{Context, Result}; +use libloading::os::unix::Symbol as RawSymbol; +use libloading::Library; + +use crate::get_libfn; + +#[allow(non_snake_case)] +#[repr(C)] +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub struct OhusbDevice { + pub busNum: u8, + pub devAddr: u8, + pub fd: c_int, +} + +type OhusbOpenDeviceFn = unsafe extern "C" fn(*mut OhusbDevice) -> c_int; +type OhusbCloseDeviceFn = unsafe extern "C" fn(*mut OhusbDevice) -> c_int; + +pub struct UsbFuncTable { + pub open_device: RawSymbol, + pub close_device: RawSymbol, +} + +impl UsbFuncTable { + pub unsafe fn new(library: &Library) -> Result { + Ok(Self { + open_device: get_libfn!(library, OhusbOpenDeviceFn, OhusbOpenDevice), + close_device: get_libfn!(library, OhusbCloseDeviceFn, OhusbCloseDevice), + }) + } +} diff --git a/util/src/ohos_binding/mod.rs b/util/src/ohos_binding/mod.rs index 5f876ba..2e6a3cf 100644 --- a/util/src/ohos_binding/mod.rs +++ b/util/src/ohos_binding/mod.rs @@ -15,6 +15,8 @@ pub mod audio; #[cfg(feature = "usb_camera_oh")] pub mod camera; pub mod misc; +#[cfg(feature = "usb_host")] +pub mod usb; #[cfg(feature = "usb_camera_oh")] mod hwf_adapter; diff --git a/util/src/ohos_binding/usb.rs b/util/src/ohos_binding/usb.rs new file mode 100644 index 0000000..a8d227b --- /dev/null +++ b/util/src/ohos_binding/usb.rs @@ -0,0 +1,50 @@ +// Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +pub use super::hwf_adapter::usb::OhusbDevice; + +use std::sync::Arc; + +use anyhow::{bail, Result}; + +use super::hwf_adapter::hwf_adapter_usb_api; +use super::hwf_adapter::usb::UsbFuncTable; + +#[derive(Clone)] +pub struct OhUsb { + capi: Arc, +} + +impl OhUsb { + pub fn new() -> Result { + let capi = hwf_adapter_usb_api(); + Ok(Self { capi }) + } + + pub fn open_device(&self, dev_handle: *mut OhusbDevice) -> Result { + // SAFETY: We call related API sequentially for specified ctx. + let ret = unsafe { (self.capi.open_device)(dev_handle) }; + if ret < 0 { + bail!("OH USB: open device failed."); + } + Ok(ret) + } + + pub fn close_device(&self, dev_handle: *mut OhusbDevice) -> Result { + // SAFETY: We call related API sequentially for specified ctx. + let ret = unsafe { (self.capi.close_device)(dev_handle) }; + if ret < 0 { + bail!("OH USB: close device failed."); + } + Ok(ret) + } +} diff --git a/util/src/seccomp.rs b/util/src/seccomp.rs index 41206a3..d3033d4 100644 --- a/util/src/seccomp.rs +++ b/util/src/seccomp.rs @@ -24,6 +24,7 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` //! //! ## Examples //! @@ -54,6 +55,8 @@ //! let nr = libc::SYS_open; //! #[cfg(target_arch = "aarch64")] //! let nr = libc::SYS_openat; +//! #[cfg(target_arch = "riscv64")] +//! let nr = libc::SYS_openat; //! nr //! }; //! @@ -127,6 +130,8 @@ const SECCOMP_FILETER_FLAG_TSYNC: u32 = 1; const EM_X86_64: u32 = 62; #[cfg(target_arch = "aarch64")] const EM_AARCH64: u32 = 183; +#[cfg(target_arch = "riscv64")] +const EM_RISCV64: u32 = 243; const __AUDIT_ATCH_64BIT: u32 = 0x8000_0000; const __AUDIT_ARCH_LE: u32 = 0x4000_0000; #[cfg(target_arch = "x86_64")] @@ -135,6 +140,9 @@ const AUDIT_ARCH_X86_64: u32 = EM_X86_64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; #[cfg(target_arch = "aarch64")] /// See: https://elixir.bootlin.com/linux/v4.19.123/source/include/uapi/linux/audit.h#L376 const AUDIT_ARCH_AARCH64: u32 = EM_AARCH64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; +#[cfg(target_arch = "riscv64")] +/// See: https://elixir.bootlin.com/linux/v5.0/source/include/uapi/linux/audit.h#L376 +const AUDIT_ARCH_RISCV64: u32 = EM_RISCV64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; /// Compared operator in bpf filter rule. #[derive(Copy, Clone, PartialEq, Eq)] @@ -270,6 +278,8 @@ fn validate_architecture() -> Vec { bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_X86_64, 1, 0), #[cfg(target_arch = "aarch64")] bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_AARCH64, 1, 0), + #[cfg(target_arch = "riscv64")] + bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_RISCV64, 1, 0), bpf_stmt(BPF_RET + BPF_K, SECCOMP_RET_KILL), ] } @@ -494,6 +504,8 @@ mod tests { k: 0xC000_003E, #[cfg(target_arch = "aarch64")] k: 0xC000_00B7, + #[cfg(target_arch = "riscv64")] + k: 0xC000_00F3, }, // Ret kill SockFilter { @@ -518,6 +530,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Ret allow SockFilter { @@ -555,6 +569,8 @@ mod tests { k: 0xC000_003E, #[cfg(target_arch = "aarch64")] k: 0xC000_00B7, + #[cfg(target_arch = "riscv64")] + k: 0xC000_00F3, }, // Ret kill SockFilter { @@ -579,6 +595,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Load arg SockFilter { @@ -627,6 +645,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Load arg SockFilter { diff --git a/util/src/socket.rs b/util/src/socket.rs index 3b1290b..573b7f4 100644 --- a/util/src/socket.rs +++ b/util/src/socket.rs @@ -41,6 +41,13 @@ impl SocketStream { } => link_description.clone(), } } + + pub fn set_nonblocking(&mut self, nonblocking: bool) -> IoResult<()> { + match self { + SocketStream::Tcp { stream, .. } => stream.set_nonblocking(nonblocking), + SocketStream::Unix { stream, .. } => stream.set_nonblocking(nonblocking), + } + } } impl AsRawFd for SocketStream { @@ -132,6 +139,7 @@ impl SocketListener { match self { SocketListener::Tcp { listener, address } => { let (stream, sock_addr) = listener.accept()?; + stream.set_nonblocking(true)?; let peer_address = sock_addr.to_string(); let link_description = format!( "{{ protocol: tcp, address: {}, peer: {} }}", @@ -144,6 +152,7 @@ impl SocketListener { } SocketListener::Unix { listener, address } => { let (stream, _) = listener.accept()?; + stream.set_nonblocking(true)?; let link_description = format!("{{ protocol: unix, address: {} }}", address); Ok(SocketStream::Unix { link_description, diff --git a/util/src/test_helper.rs b/util/src/test_helper.rs index 7f69b42..47cb11b 100644 --- a/util/src/test_helper.rs +++ b/util/src/test_helper.rs @@ -16,25 +16,25 @@ use std::time::{Duration, Instant}; use once_cell::sync::{Lazy, OnceCell}; #[derive(Default, Clone, Copy)] -struct MsixMsg { - addr: u64, - data: u32, +pub struct MsixMsg { + pub addr: u64, + pub data: u32, } impl MsixMsg { - fn new(addr: u64, data: u32) -> Self { + pub fn new(addr: u64, data: u32) -> Self { MsixMsg { addr, data } } } #[derive(Default, Clone, Copy, Debug)] -struct IntxInfo { - irq: u32, - level: i8, +pub struct IntxInfo { + pub irq: u32, + pub level: i8, } impl IntxInfo { - fn new(irq: u32, level: i8) -> Self { + pub fn new(irq: u32, level: i8) -> Self { IntxInfo { irq, level } } } @@ -42,8 +42,8 @@ impl IntxInfo { static TEST_ENABLED: OnceCell = OnceCell::new(); static TEST_BASE_TIME: OnceCell = OnceCell::new(); static mut TEST_CLOCK: Option>> = None; -static TEST_MSIX_LIST: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); -static TEST_INTX_LIST: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +pub static TEST_MSIX_LIST: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +pub static TEST_INTX_LIST: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); pub fn set_test_enabled() { if let Err(_e) = TEST_ENABLED.set(true) { @@ -139,20 +139,6 @@ pub fn has_msix_msg(addr: u64, data: u32) -> bool { } } -pub fn trigger_intx(irq: u32, change: i8) { - let new_intx = IntxInfo::new(irq, change); - let mut intx_list_lock = TEST_INTX_LIST.lock().unwrap(); - - for intx in intx_list_lock.iter_mut() { - if intx.irq == new_intx.irq { - intx.level += new_intx.level; - return; - } - } - - intx_list_lock.push(new_intx); -} - pub fn query_intx(irq: u32) -> bool { let mut intx_list_lock = TEST_INTX_LIST.lock().unwrap(); for intx in intx_list_lock.iter_mut() { diff --git a/util/src/unix.rs b/util/src/unix.rs index d71e3c8..6a723ed 100644 --- a/util/src/unix.rs +++ b/util/src/unix.rs @@ -187,10 +187,11 @@ impl UnixSock { /// The listener accepts incoming client connections. pub fn accept(&mut self) -> Result<()> { - let (sock, _addr) = self + let listener = self .listener .as_ref() - .unwrap() + .with_context(|| "UnixSock is not bound")?; + let (sock, _addr) = listener .accept() .with_context(|| format!("Failed to accept the socket {}", self.path))?; self.sock = Some(sock); @@ -203,8 +204,12 @@ impl UnixSock { } pub fn server_connection_refuse(&mut self) -> Result<()> { + let listener = self + .listener + .as_ref() + .with_context(|| "UnixSock is not bound")?; // Refuse connection by finishing life cycle of stream fd from listener fd. - self.listener.as_ref().unwrap().accept().with_context(|| { + listener.accept().with_context(|| { format!( "Failed to accept the socket for refused connection {}", self.path @@ -224,18 +229,21 @@ impl UnixSock { } pub fn listen_set_nonblocking(&self, nonblocking: bool) -> Result<()> { - self.listener + let listener = self + .listener .as_ref() - .unwrap() + .with_context(|| "UnixSock is not bound")?; + listener .set_nonblocking(nonblocking) .with_context(|| "couldn't set nonblocking for unix sock listener") } pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()> { - self.sock + let sock = self + .sock .as_ref() - .unwrap() - .set_nonblocking(nonblocking) + .with_context(|| "UnixSock is not connected")?; + sock.set_nonblocking(nonblocking) .with_context(|| "couldn't set nonblocking") } @@ -287,7 +295,7 @@ impl UnixSock { /// # Errors /// /// The socket file descriptor is broken. - pub fn send_msg(&self, iovecs: &mut [iovec], out_fds: &[RawFd]) -> std::io::Result { + pub fn send_msg(&self, iovecs: &mut [iovec], out_fds: &[RawFd]) -> Result { // SAFETY: We checked the iovecs lens before. let iovecs_len = iovecs.len(); // SAFETY: We checked the out_fds lens before. @@ -331,17 +339,17 @@ impl UnixSock { msg.msg_controllen = cmsg_capacity as _; } - let write_count = - // SAFETY: msg parameters are valid. - unsafe { sendmsg(self.sock.as_ref().unwrap().as_raw_fd(), &msg, MSG_NOSIGNAL) }; + let sock = self + .sock + .as_ref() + .with_context(|| "UnixSock is not connected")?; + // SAFETY: msg parameters are valid. + let write_count = unsafe { sendmsg(sock.as_raw_fd(), &msg, MSG_NOSIGNAL) }; if write_count == -1 { - Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "Failed to send msg, err: {}", - std::io::Error::last_os_error() - ), + Err(anyhow!( + "Failed to send msg, err: {}", + std::io::Error::last_os_error() )) } else { Ok(write_count as usize) @@ -358,11 +366,7 @@ impl UnixSock { /// # Errors /// /// The socket file descriptor is broken. - pub fn recv_msg( - &self, - iovecs: &mut [iovec], - in_fds: &mut [RawFd], - ) -> std::io::Result<(usize, usize)> { + pub fn recv_msg(&self, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<(usize, usize)> { // SAFETY: We check the iovecs lens before. let iovecs_len = iovecs.len(); // SAFETY: We check the in_fds lens before. @@ -386,33 +390,25 @@ impl UnixSock { msg.msg_controllen = cmsg_capacity as _; } + let sock = self + .sock + .as_ref() + .with_context(|| "UnixSock is not connected")?; // SAFETY: msg parameters are valid. - let total_read = unsafe { - recvmsg( - self.sock.as_ref().unwrap().as_raw_fd(), - &mut msg, - MSG_WAITALL, - ) - }; + let total_read = unsafe { recvmsg(sock.as_raw_fd(), &mut msg, MSG_WAITALL) }; if total_read == -1 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "Failed to recv msg, err: {}", - std::io::Error::last_os_error() - ), - )); + bail!( + "Failed to recv msg, err: {}", + std::io::Error::last_os_error() + ); } if total_read == 0 && (msg.msg_controllen as u64) < size_of::() as u64 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "The length of control message is invalid, {} {}", - msg.msg_controllen, - size_of::() - ), - )); + bail!( + "The length of control message is invalid, {} {}", + msg.msg_controllen, + size_of::() + ); } let mut cmsg_ptr = msg.msg_control as *mut cmsghdr; diff --git a/vendor/memoffset-0.7.1/.cargo-checksum.json b/vendor/memoffset-0.7.1/.cargo-checksum.json deleted file mode 100644 index 60f2c09..0000000 --- a/vendor/memoffset-0.7.1/.cargo-checksum.json +++ /dev/null @@ -1 +0,0 @@ -{"files":{"Cargo.toml":"2122b76e5dff09497c7edf7f184155e456e44209c05e4f8abb01632be7241b56","LICENSE":"3234ac55816264ee7b6c7ee27efd61cf0a1fe775806870e3d9b4c41ea73c5cb1","README.md":"7a7935d96a1a40b56afeadca391c742f7ac3a6e0f1deab1d43430553f71b6d23","build.rs":"6d677e33a1c98d588c97ec7985d4d5c3b954683e0a73c3dc53d79db4fbb5e638","src/lib.rs":"e7976d295371a3c1e0cf31b0d50210cd6b1135caba3a5111403a97ec6175c0a2","src/offset_of.rs":"ea04e76e3ab1fa192618fffb0c6a047795c275f1deaf6c6617245badaba8660c","src/raw_field.rs":"ef54087d5f507c2b639a4f61f2881eb1e41a46e22191ffd0e23b2fe9e3f17c25","src/span_of.rs":"b900faef2b852b52c37c55a172c05c9144bfff7d84dbc06e943fb0453d68adfc"},"package":"5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"} \ No newline at end of file diff --git a/vendor/memoffset-0.7.1/Cargo.toml b/vendor/memoffset-0.7.1/Cargo.toml deleted file mode 100644 index 1677446..0000000 --- a/vendor/memoffset-0.7.1/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -name = "memoffset" -version = "0.7.1" -authors = ["Gilad Naaman "] -description = "offset_of functionality for Rust structs." -readme = "README.md" -keywords = [ - "mem", - "offset", - "offset_of", - "offsetof", -] -categories = ["no-std"] -license = "MIT" -repository = "https://github.com/Gilnaa/memoffset" - -[dev-dependencies.doc-comment] -version = "0.3" - -[build-dependencies.autocfg] -version = "1" - -[features] -default = [] -unstable_const = [] diff --git a/vendor/memoffset-0.7.1/LICENSE b/vendor/memoffset-0.7.1/LICENSE deleted file mode 100644 index 61f6081..0000000 --- a/vendor/memoffset-0.7.1/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017 Gilad Naaman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/memoffset-0.7.1/README.md b/vendor/memoffset-0.7.1/README.md deleted file mode 100644 index e297b33..0000000 --- a/vendor/memoffset-0.7.1/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# memoffset # - -[![](https://img.shields.io/crates/v/memoffset.svg)](https://crates.io/crates/memoffset) - -C-Like `offset_of` functionality for Rust structs. - -Introduces the following macros: - * `offset_of!` for obtaining the offset of a member of a struct. - * `offset_of_tuple!` for obtaining the offset of a member of a tuple. (Requires Rust 1.20+) - * `span_of!` for obtaining the range that a field, or fields, span. - -`memoffset` works under `no_std` environments. - -## Usage ## -Add the following dependency to your `Cargo.toml`: - -```toml -[dependencies] -memoffset = "0.7" -``` - -These versions will compile fine with rustc versions greater or equal to 1.19. - -## Examples ## -```rust -use memoffset::{offset_of, span_of}; - -#[repr(C, packed)] -struct Foo { - a: u32, - b: u32, - c: [u8; 5], - d: u32, -} - -fn main() { - assert_eq!(offset_of!(Foo, b), 4); - assert_eq!(offset_of!(Foo, d), 4+4+5); - - assert_eq!(span_of!(Foo, a), 0..4); - assert_eq!(span_of!(Foo, a .. c), 0..8); - assert_eq!(span_of!(Foo, a ..= c), 0..13); - assert_eq!(span_of!(Foo, ..= d), 0..17); - assert_eq!(span_of!(Foo, b ..), 4..17); -} -``` - -## Feature flags ## - -### Usage in constants ### -`memoffset` has **experimental** support for compile-time `offset_of!` on a nightly compiler. - -In order to use it, you must enable the `unstable_const` crate feature and several compiler features. - -Cargo.toml: -```toml -[dependencies.memoffset] -version = "0.7" -features = ["unstable_const"] -``` - -Your crate root: (`lib.rs`/`main.rs`) -```rust,ignore -#![feature(const_ptr_offset_from, const_refs_to_cell)] -``` diff --git a/vendor/memoffset-0.7.1/build.rs b/vendor/memoffset-0.7.1/build.rs deleted file mode 100644 index 0604c19..0000000 --- a/vendor/memoffset-0.7.1/build.rs +++ /dev/null @@ -1,22 +0,0 @@ -extern crate autocfg; - -fn main() { - let ac = autocfg::new(); - - // Check for a minimum version for a few features - if ac.probe_rustc_version(1, 20) { - println!("cargo:rustc-cfg=tuple_ty"); - } - if ac.probe_rustc_version(1, 31) { - println!("cargo:rustc-cfg=allow_clippy"); - } - if ac.probe_rustc_version(1, 36) { - println!("cargo:rustc-cfg=maybe_uninit"); - } - if ac.probe_rustc_version(1, 40) { - println!("cargo:rustc-cfg=doctests"); - } - if ac.probe_rustc_version(1, 51) { - println!("cargo:rustc-cfg=raw_ref_macros"); - } -} diff --git a/vendor/memoffset-0.7.1/src/lib.rs b/vendor/memoffset-0.7.1/src/lib.rs deleted file mode 100644 index d80ff17..0000000 --- a/vendor/memoffset-0.7.1/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2017 Gilad Naaman -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -//! A crate used for calculating offsets of struct members and their spans. -//! -//! This functionality currently can not be used in compile time code such as `const` or `const fn` definitions. -//! -//! ## Examples -//! ``` -//! use memoffset::{offset_of, span_of}; -//! -//! #[repr(C, packed)] -//! struct HelpMeIAmTrappedInAStructFactory { -//! help_me_before_they_: [u8; 15], -//! a: u32 -//! } -//! -//! fn main() { -//! assert_eq!(offset_of!(HelpMeIAmTrappedInAStructFactory, a), 15); -//! assert_eq!(span_of!(HelpMeIAmTrappedInAStructFactory, a), 15..19); -//! assert_eq!(span_of!(HelpMeIAmTrappedInAStructFactory, help_me_before_they_ .. a), 0..15); -//! } -//! ``` -//! -//! This functionality can be useful, for example, for checksum calculations: -//! -//! ```ignore -//! #[repr(C, packed)] -//! struct Message { -//! header: MessageHeader, -//! fragment_index: u32, -//! fragment_count: u32, -//! payload: [u8; 1024], -//! checksum: u16 -//! } -//! -//! let checksum_range = &raw[span_of!(Message, header..checksum)]; -//! let checksum = crc16(checksum_range); -//! ``` - -#![no_std] -#![cfg_attr( - feature = "unstable_const", - feature(const_ptr_offset_from, const_refs_to_cell) -)] - -#[macro_use] -#[cfg(doctests)] -#[cfg(doctest)] -extern crate doc_comment; -#[cfg(doctests)] -#[cfg(doctest)] -doctest!("../README.md"); - -/// Hidden module for things the macros need to access. -#[doc(hidden)] -pub mod __priv { - #[doc(hidden)] - pub use core::mem; - #[doc(hidden)] - pub use core::ptr; - - /// Use type inference to obtain the size of the pointee (without actually using the pointer). - #[doc(hidden)] - pub fn size_of_pointee(_ptr: *const T) -> usize { - mem::size_of::() - } -} - -#[macro_use] -mod raw_field; -#[macro_use] -mod offset_of; -#[macro_use] -mod span_of; diff --git a/vendor/memoffset-0.7.1/src/offset_of.rs b/vendor/memoffset-0.7.1/src/offset_of.rs deleted file mode 100644 index d070181..0000000 --- a/vendor/memoffset-0.7.1/src/offset_of.rs +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2017 Gilad Naaman -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -/// Macro to create a local `base_ptr` raw pointer of the given type, avoiding UB as -/// much as is possible currently. -#[cfg(maybe_uninit)] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__let_base_ptr { - ($name:ident, $type:ty) => { - // No UB here, and the pointer does not dangle, either. - // But we have to make sure that `uninit` lives long enough, - // so it has to be in the same scope as `$name`. That's why - // `let_base_ptr` declares a variable (several, actually) - // instead of returning one. - let uninit = $crate::__priv::mem::MaybeUninit::<$type>::uninit(); - let $name: *const $type = uninit.as_ptr(); - }; -} -#[cfg(not(maybe_uninit))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__let_base_ptr { - ($name:ident, $type:ty) => { - // No UB right here, but we will later dereference this pointer to - // offset into a field, and that is UB because the pointer is dangling. - let $name = $crate::__priv::mem::align_of::<$type>() as *const $type; - }; -} - -/// Macro to compute the distance between two pointers. -#[cfg(feature = "unstable_const")] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset_offset_from_unsafe { - ($field:expr, $base:expr) => {{ - let field = $field; // evaluate $field outside the `unsafe` block - let base = $base; // evaluate $base outside the `unsafe` block - // Compute offset, with unstable `offset_from` for const-compatibility. - // (Requires the pointers to not dangle, but we already need that for `raw_field!` anyway.) - unsafe { (field as *const u8).offset_from(base as *const u8) as usize } - }}; -} -#[cfg(not(feature = "unstable_const"))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset_offset_from_unsafe { - ($field:expr, $base:expr) => { - // Compute offset. - ($field as usize) - ($base as usize) - }; -} - -/// Calculates the offset of the specified field from the start of the named struct. -/// -/// ## Examples -/// ``` -/// use memoffset::offset_of; -/// -/// #[repr(C, packed)] -/// struct Foo { -/// a: u32, -/// b: u64, -/// c: [u8; 5] -/// } -/// -/// fn main() { -/// assert_eq!(offset_of!(Foo, a), 0); -/// assert_eq!(offset_of!(Foo, b), 4); -/// } -/// ``` -/// -/// ## Notes -/// Rust's ABI is unstable, and [type layout can be changed with each -/// compilation](https://doc.rust-lang.org/reference/type-layout.html). -/// -/// Using `offset_of!` with a `repr(Rust)` struct will return the correct offset of the -/// specified `field` for a particular compilation, but the exact value may change -/// based on the compiler version, concrete struct type, time of day, or rustc's mood. -/// -/// As a result, the value should not be retained and used between different compilations. -#[macro_export(local_inner_macros)] -macro_rules! offset_of { - ($parent:path, $field:tt) => {{ - // Get a base pointer (non-dangling if rustc supports `MaybeUninit`). - _memoffset__let_base_ptr!(base_ptr, $parent); - // Get field pointer. - let field_ptr = raw_field!(base_ptr, $parent, $field); - // Compute offset. - _memoffset_offset_from_unsafe!(field_ptr, base_ptr) - }}; -} - -/// Calculates the offset of the specified field from the start of the tuple. -/// -/// ## Examples -/// ``` -/// use memoffset::offset_of_tuple; -/// -/// fn main() { -/// assert!(offset_of_tuple!((u8, u32), 1) >= 0, "Tuples do not have a defined layout"); -/// } -/// ``` -#[cfg(tuple_ty)] -#[macro_export(local_inner_macros)] -macro_rules! offset_of_tuple { - ($parent:ty, $field:tt) => {{ - // Get a base pointer (non-dangling if rustc supports `MaybeUninit`). - _memoffset__let_base_ptr!(base_ptr, $parent); - // Get field pointer. - let field_ptr = raw_field_tuple!(base_ptr, $parent, $field); - // Compute offset. - _memoffset_offset_from_unsafe!(field_ptr, base_ptr) - }}; -} - -/// Calculates the offset of the specified union member from the start of the union. -/// -/// ## Examples -/// ``` -/// use memoffset::offset_of_union; -/// -/// #[repr(C, packed)] -/// union Foo { -/// foo32: i32, -/// foo64: i64, -/// } -/// -/// fn main() { -/// assert!(offset_of_union!(Foo, foo64) == 0); -/// } -/// ``` -/// -/// ## Note -/// Due to macro_rules limitations, this macro will accept structs with a single field as well as unions. -/// This is not a stable guarantee, and future versions of this crate might fail -/// on any use of this macro with a struct, without a semver bump. -#[macro_export(local_inner_macros)] -macro_rules! offset_of_union { - ($parent:path, $field:tt) => {{ - // Get a base pointer (non-dangling if rustc supports `MaybeUninit`). - _memoffset__let_base_ptr!(base_ptr, $parent); - // Get field pointer. - let field_ptr = raw_field_union!(base_ptr, $parent, $field); - // Compute offset. - _memoffset_offset_from_unsafe!(field_ptr, base_ptr) - }}; -} - -#[cfg(test)] -mod tests { - #[test] - fn offset_simple() { - #[repr(C)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!(offset_of!(Foo, a), 0); - assert_eq!(offset_of!(Foo, b), 4); - assert_eq!(offset_of!(Foo, c), 8); - } - - #[test] - #[cfg_attr(miri, ignore)] // this creates unaligned references - fn offset_simple_packed() { - #[repr(C, packed)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!(offset_of!(Foo, a), 0); - assert_eq!(offset_of!(Foo, b), 4); - assert_eq!(offset_of!(Foo, c), 6); - } - - #[test] - fn tuple_struct() { - #[repr(C)] - struct Tup(i32, i32); - - assert_eq!(offset_of!(Tup, 0), 0); - assert_eq!(offset_of!(Tup, 1), 4); - } - - #[test] - fn offset_union() { - // Since we're specifying repr(C), all fields are supposed to be at offset 0 - #[repr(C)] - union Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!(offset_of_union!(Foo, a), 0); - assert_eq!(offset_of_union!(Foo, b), 0); - assert_eq!(offset_of_union!(Foo, c), 0); - } - - #[test] - fn path() { - mod sub { - #[repr(C)] - pub struct Foo { - pub x: u32, - } - } - - assert_eq!(offset_of!(sub::Foo, x), 0); - } - - #[test] - fn inside_generic_method() { - struct Pair(T, U); - - fn foo(_: Pair) -> usize { - offset_of!(Pair, 1) - } - - assert_eq!(foo(Pair(0, 0)), 4); - } - - #[cfg(tuple_ty)] - #[test] - fn test_tuple_offset() { - let f = (0i32, 0.0f32, 0u8); - let f_ptr = &f as *const _; - let f1_ptr = &f.1 as *const _; - - assert_eq!( - f1_ptr as usize - f_ptr as usize, - offset_of_tuple!((i32, f32, u8), 1) - ); - } - - #[test] - fn test_raw_field() { - #[repr(C)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - let f: Foo = Foo { - a: 0, - b: [0, 0], - c: 0, - }; - let f_ptr = &f as *const _; - assert_eq!(f_ptr as usize + 0, raw_field!(f_ptr, Foo, a) as usize); - assert_eq!(f_ptr as usize + 4, raw_field!(f_ptr, Foo, b) as usize); - assert_eq!(f_ptr as usize + 8, raw_field!(f_ptr, Foo, c) as usize); - } - - #[cfg(tuple_ty)] - #[test] - fn test_raw_field_tuple() { - let t = (0u32, 0u8, false); - let t_ptr = &t as *const _; - let t_addr = t_ptr as usize; - - assert_eq!( - &t.0 as *const _ as usize - t_addr, - raw_field_tuple!(t_ptr, (u32, u8, bool), 0) as usize - t_addr - ); - assert_eq!( - &t.1 as *const _ as usize - t_addr, - raw_field_tuple!(t_ptr, (u32, u8, bool), 1) as usize - t_addr - ); - assert_eq!( - &t.2 as *const _ as usize - t_addr, - raw_field_tuple!(t_ptr, (u32, u8, bool), 2) as usize - t_addr - ); - } - - #[test] - fn test_raw_field_union() { - #[repr(C)] - union Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - let f = Foo { a: 0 }; - let f_ptr = &f as *const _; - assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, a) as usize); - assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, b) as usize); - assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, c) as usize); - } - - #[cfg(feature = "unstable_const")] - #[test] - fn const_offset() { - #[repr(C)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!([0; offset_of!(Foo, b)].len(), 4); - } - - #[cfg(feature = "unstable_const")] - #[test] - fn const_offset_interior_mutable() { - #[repr(C)] - struct Foo { - a: u32, - b: core::cell::Cell, - } - - assert_eq!([0; offset_of!(Foo, b)].len(), 4); - } - - #[cfg(feature = "unstable_const")] - #[test] - fn const_fn_offset() { - const fn test_fn() -> usize { - #[repr(C)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - offset_of!(Foo, b) - } - - assert_eq!([0; test_fn()].len(), 4); - } -} diff --git a/vendor/memoffset-0.7.1/src/raw_field.rs b/vendor/memoffset-0.7.1/src/raw_field.rs deleted file mode 100644 index e16df9f..0000000 --- a/vendor/memoffset-0.7.1/src/raw_field.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (c) 2020 Gilad Naaman, Ralf Jung -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -/// `addr_of!`, or just ref-then-cast when that is not available. -#[cfg(raw_ref_macros)] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__addr_of { - ($path:expr) => {{ - $crate::__priv::ptr::addr_of!($path) - }}; -} -#[cfg(not(raw_ref_macros))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__addr_of { - ($path:expr) => {{ - // This is UB because we create an intermediate reference to uninitialized memory. - // Nothing we can do about that without `addr_of!` though. - &$path as *const _ - }}; -} - -/// Deref-coercion protection macro. -/// -/// Prevents complilation if the specified field name is not a part of the -/// struct definition. -/// -/// ```compile_fail -/// use memoffset::_memoffset__field_check; -/// -/// struct Foo { -/// foo: i32, -/// } -/// -/// type BoxedFoo = Box; -/// -/// _memoffset__field_check!(BoxedFoo, foo); -/// ``` -#[cfg(allow_clippy)] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check { - ($type:path, $field:tt) => { - // Make sure the field actually exists. This line ensures that a - // compile-time error is generated if $field is accessed through a - // Deref impl. - #[allow(clippy::unneeded_field_pattern)] - let $type { $field: _, .. }; - }; -} -#[cfg(not(allow_clippy))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check { - ($type:path, $field:tt) => { - // Make sure the field actually exists. This line ensures that a - // compile-time error is generated if $field is accessed through a - // Deref impl. - let $type { $field: _, .. }; - }; -} - -/// Deref-coercion protection macro. -/// -/// Prevents complilation if the specified type is not a tuple. -/// -/// ```compile_fail -/// use memoffset::_memoffset__field_check_tuple; -/// -/// _memoffset__field_check_tuple!(i32, 0); -/// ``` -#[cfg(allow_clippy)] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check_tuple { - ($type:ty, $field:tt) => { - // Make sure the type argument is a tuple - #[allow(clippy::unneeded_wildcard_pattern)] - let (_, ..): $type; - }; -} -#[cfg(not(allow_clippy))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check_tuple { - ($type:ty, $field:tt) => { - // Make sure the type argument is a tuple - let (_, ..): $type; - }; -} - -/// Deref-coercion protection macro for unions. -/// Unfortunately accepts single-field structs as well, which is not ideal, -/// but ultimately pretty harmless. -/// -/// ```compile_fail -/// use memoffset::_memoffset__field_check_union; -/// -/// union Foo { -/// variant_a: i32, -/// } -/// -/// type BoxedFoo = Box; -/// -/// _memoffset__field_check_union!(BoxedFoo, variant_a); -/// ``` -#[cfg(allow_clippy)] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check_union { - ($type:path, $field:tt) => { - // Make sure the field actually exists. This line ensures that a - // compile-time error is generated if $field is accessed through a - // Deref impl. - #[allow(clippy::unneeded_wildcard_pattern)] - // rustc1.19 requires unsafe here for the pattern; not needed in newer versions - #[allow(unused_unsafe)] - unsafe { - let $type { $field: _ }; - } - }; -} -#[cfg(not(allow_clippy))] -#[macro_export] -#[doc(hidden)] -macro_rules! _memoffset__field_check_union { - ($type:path, $field:tt) => { - // Make sure the field actually exists. This line ensures that a - // compile-time error is generated if $field is accessed through a - // Deref impl. - // rustc1.19 requires unsafe here for the pattern; not needed in newer versions - #[allow(unused_unsafe)] - unsafe { - let $type { $field: _ }; - } - }; -} - -/// Computes a const raw pointer to the given field of the given base pointer -/// to the given parent type. -/// -/// The `base` pointer *must not* be dangling, but it *may* point to -/// uninitialized memory. -#[macro_export(local_inner_macros)] -macro_rules! raw_field { - ($base:expr, $parent:path, $field:tt) => {{ - _memoffset__field_check!($parent, $field); - let base = $base; // evaluate $base outside the `unsafe` block - - // Get the field address. - // Crucially, we know that this will not trigger a deref coercion because - // of the field check we did above. - #[allow(unused_unsafe)] // for when the macro is used in an unsafe block - unsafe { - _memoffset__addr_of!((*(base as *const $parent)).$field) - } - }}; -} - -/// Computes a const raw pointer to the given field of the given base pointer -/// to the given parent tuple typle. -/// -/// The `base` pointer *must not* be dangling, but it *may* point to -/// uninitialized memory. -#[cfg(tuple_ty)] -#[macro_export(local_inner_macros)] -macro_rules! raw_field_tuple { - ($base:expr, $parent:ty, $field:tt) => {{ - _memoffset__field_check_tuple!($parent, $field); - let base = $base; // evaluate $base outside the `unsafe` block - - // Get the field address. - // Crucially, we know that this will not trigger a deref coercion because - // of the field check we did above. - #[allow(unused_unsafe)] // for when the macro is used in an unsafe block - unsafe { - _memoffset__addr_of!((*(base as *const $parent)).$field) - } - }}; -} - -/// Computes a const raw pointer to the given field of the given base pointer -/// to the given parent tuple typle. -/// -/// The `base` pointer *must not* be dangling, but it *may* point to -/// uninitialized memory. -/// -/// ## Note -/// This macro is the same as `raw_field`, except for a different Deref-coercion check that -/// supports unions. -/// Due to macro_rules limitations, this check will accept structs with a single field as well as unions. -/// This is not a stable guarantee, and future versions of this crate might fail -/// on any use of this macro with a struct, without a semver bump. -#[macro_export(local_inner_macros)] -macro_rules! raw_field_union { - ($base:expr, $parent:path, $field:tt) => {{ - _memoffset__field_check_union!($parent, $field); - let base = $base; // evaluate $base outside the `unsafe` block - - // Get the field address. - // Crucially, we know that this will not trigger a deref coercion because - // of the field check we did above. - #[allow(unused_unsafe)] // for when the macro is used in an unsafe block - unsafe { - _memoffset__addr_of!((*(base as *const $parent)).$field) - } - }}; -} diff --git a/vendor/memoffset-0.7.1/src/span_of.rs b/vendor/memoffset-0.7.1/src/span_of.rs deleted file mode 100644 index 89fccce..0000000 --- a/vendor/memoffset-0.7.1/src/span_of.rs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) 2017 Gilad Naaman -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -/// Reexport for `local_inner_macros`; see -/// . -#[doc(hidden)] -#[macro_export] -macro_rules! _memoffset__compile_error { - ($($inner:tt)*) => { - compile_error! { $($inner)* } - } -} - -/// Produces a range instance representing the sub-slice containing the specified member. -/// -/// This macro provides 2 forms of differing functionalities. -/// -/// The first form is identical to the appearance of the `offset_of!` macro. -/// -/// ```ignore -/// span_of!(Struct, member) -/// ``` -/// -/// The second form of `span_of!` returns a sub-slice which starts at one field, and ends at another. -/// The general pattern of this form is: -/// -/// ```ignore -/// // Exclusive -/// span_of!(Struct, member_a .. member_b) -/// // Inclusive -/// span_of!(Struct, member_a ..= member_b) -/// -/// // Open-ended ranges -/// span_of!(Struct, .. end) -/// span_of!(Struct, start ..) -/// ``` -/// -/// ### Note -/// This macro uses recursion in order to resolve the range expressions, so there is a limit to -/// the complexity of the expression. -/// In order to raise the limit, the compiler's recursion limit should be lifted. -/// -/// ### Safety -/// The inter-field form mentioned above assumes that the first field is positioned before the -/// second. -/// This is only guarenteed for `repr(C)` structs. -/// Usage with `repr(Rust)` structs may yield unexpected results, like downward-going ranges, -/// spans that include unexpected fields, empty spans, or spans that include *unexpected* padding bytes. -/// -/// ## Examples -/// ``` -/// use memoffset::span_of; -/// -/// #[repr(C)] -/// struct Florp { -/// a: u32 -/// } -/// -/// #[repr(C)] -/// struct Blarg { -/// x: [u32; 2], -/// y: [u8; 56], -/// z: Florp, -/// egg: [[u8; 4]; 4] -/// } -/// -/// fn main() { -/// assert_eq!(0..84, span_of!(Blarg, ..)); -/// assert_eq!(0..8, span_of!(Blarg, .. y)); -/// assert_eq!(0..64, span_of!(Blarg, ..= y)); -/// assert_eq!(0..8, span_of!(Blarg, x)); -/// assert_eq!(8..84, span_of!(Blarg, y ..)); -/// assert_eq!(0..8, span_of!(Blarg, x .. y)); -/// assert_eq!(0..64, span_of!(Blarg, x ..= y)); -/// } -/// ``` -#[macro_export(local_inner_macros)] -macro_rules! span_of { - (@helper $root:ident, [] ..=) => { - _memoffset__compile_error!("Expected a range, found '..='") - }; - (@helper $root:ident, [] ..) => { - _memoffset__compile_error!("Expected a range, found '..'") - }; - // No explicit begin for range. - (@helper $root:ident, $parent:path, [] ..) => {{ - ($root as usize, - $root as usize + $crate::__priv::size_of_pointee($root)) - }}; - (@helper $root:ident, $parent:path, [] ..= $end:tt) => {{ - let end = raw_field!($root, $parent, $end); - ($root as usize, end as usize + $crate::__priv::size_of_pointee(end)) - }}; - (@helper $root:ident, $parent:path, [] .. $end:tt) => {{ - ($root as usize, raw_field!($root, $parent, $end) as usize) - }}; - // Explicit begin and end for range. - (@helper $root:ident, $parent:path, # $begin:tt [] ..= $end:tt) => {{ - let begin = raw_field!($root, $parent, $begin); - let end = raw_field!($root, $parent, $end); - (begin as usize, end as usize + $crate::__priv::size_of_pointee(end)) - }}; - (@helper $root:ident, $parent:path, # $begin:tt [] .. $end:tt) => {{ - (raw_field!($root, $parent, $begin) as usize, - raw_field!($root, $parent, $end) as usize) - }}; - // No explicit end for range. - (@helper $root:ident, $parent:path, # $begin:tt [] ..) => {{ - (raw_field!($root, $parent, $begin) as usize, - $root as usize + $crate::__priv::size_of_pointee($root)) - }}; - (@helper $root:ident, $parent:path, # $begin:tt [] ..=) => {{ - _memoffset__compile_error!( - "Found inclusive range to the end of a struct. Did you mean '..' instead of '..='?") - }}; - // Just one field. - (@helper $root:ident, $parent:path, # $field:tt []) => {{ - let field = raw_field!($root, $parent, $field); - (field as usize, field as usize + $crate::__priv::size_of_pointee(field)) - }}; - // Parsing. - (@helper $root:ident, $parent:path, $(# $begin:tt)+ [] $tt:tt $($rest:tt)*) => {{ - span_of!(@helper $root, $parent, $(#$begin)* #$tt [] $($rest)*) - }}; - (@helper $root:ident, $parent:path, [] $tt:tt $($rest:tt)*) => {{ - span_of!(@helper $root, $parent, #$tt [] $($rest)*) - }}; - - // Entry point. - ($sty:path, $($exp:tt)+) => ({ - // Get a base pointer. - _memoffset__let_base_ptr!(root, $sty); - let base = root as usize; - let (begin, end) = span_of!(@helper root, $sty, [] $($exp)*); - begin-base..end-base - }); -} - -#[cfg(test)] -mod tests { - use core::mem; - - #[test] - fn span_simple() { - #[repr(C)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!(span_of!(Foo, a), 0..4); - assert_eq!(span_of!(Foo, b), 4..6); - assert_eq!(span_of!(Foo, c), 8..8 + 8); - } - - #[test] - #[cfg_attr(miri, ignore)] // this creates unaligned references - fn span_simple_packed() { - #[repr(C, packed)] - struct Foo { - a: u32, - b: [u8; 2], - c: i64, - } - - assert_eq!(span_of!(Foo, a), 0..4); - assert_eq!(span_of!(Foo, b), 4..6); - assert_eq!(span_of!(Foo, c), 6..6 + 8); - } - - #[test] - fn span_forms() { - #[repr(C)] - struct Florp { - a: u32, - } - - #[repr(C)] - struct Blarg { - x: u64, - y: [u8; 56], - z: Florp, - egg: [[u8; 4]; 5], - } - - // Love me some brute force - assert_eq!(0..8, span_of!(Blarg, x)); - assert_eq!(64..68, span_of!(Blarg, z)); - assert_eq!(68..mem::size_of::(), span_of!(Blarg, egg)); - - assert_eq!(8..64, span_of!(Blarg, y..z)); - assert_eq!(0..64, span_of!(Blarg, x..=y)); - } - - #[test] - fn ig_test() { - #[repr(C)] - struct Member { - foo: u32, - } - - #[repr(C)] - struct Test { - x: u64, - y: [u8; 56], - z: Member, - egg: [[u8; 4]; 4], - } - - assert_eq!(span_of!(Test, ..x), 0..0); - assert_eq!(span_of!(Test, ..=x), 0..8); - assert_eq!(span_of!(Test, ..y), 0..8); - assert_eq!(span_of!(Test, ..=y), 0..64); - assert_eq!(span_of!(Test, ..z), 0..64); - assert_eq!(span_of!(Test, ..=z), 0..68); - assert_eq!(span_of!(Test, ..egg), 0..68); - assert_eq!(span_of!(Test, ..=egg), 0..84); - assert_eq!(span_of!(Test, ..), 0..mem::size_of::()); - assert_eq!( - span_of!(Test, x..), - offset_of!(Test, x)..mem::size_of::() - ); - assert_eq!( - span_of!(Test, y..), - offset_of!(Test, y)..mem::size_of::() - ); - - assert_eq!( - span_of!(Test, z..), - offset_of!(Test, z)..mem::size_of::() - ); - assert_eq!( - span_of!(Test, egg..), - offset_of!(Test, egg)..mem::size_of::() - ); - assert_eq!( - span_of!(Test, x..y), - offset_of!(Test, x)..offset_of!(Test, y) - ); - assert_eq!( - span_of!(Test, x..=y), - offset_of!(Test, x)..offset_of!(Test, y) + mem::size_of::<[u8; 56]>() - ); - } -} diff --git a/vfio/Cargo.toml b/vfio/Cargo.toml index bede7fa..f5e0ecc 100644 --- a/vfio/Cargo.toml +++ b/vfio/Cargo.toml @@ -10,7 +10,7 @@ description = "Virtual function I/O" byteorder = "1.4.3" thiserror = "1.0" anyhow = "1.0" -kvm-bindings = { version = "0.10.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "0.10.0", features = [ "fam-wrappers" ] } kvm-ioctls = "0.19.1" libc = "0.2" log = "0.4" @@ -22,3 +22,4 @@ hypervisor = { path = "../hypervisor"} machine_manager = { path = "../machine_manager" } util = { path = "../util" } devices = { path = "../devices" } +clap = { version = "=4.1.4", default-features = false, features = ["std", "derive"] } diff --git a/vfio/src/lib.rs b/vfio/src/lib.rs index 49c77af..3d2705a 100644 --- a/vfio/src/lib.rs +++ b/vfio/src/lib.rs @@ -22,15 +22,17 @@ pub use vfio_dev::{ VFIO_GROUP_GET_DEVICE_FD, VFIO_GROUP_GET_STATUS, VFIO_GROUP_SET_CONTAINER, VFIO_IOMMU_MAP_DMA, VFIO_IOMMU_UNMAP_DMA, VFIO_SET_IOMMU, }; -pub use vfio_pci::VfioPciDevice; +pub use vfio_pci::{VfioConfig, VfioPciDevice}; use std::collections::HashMap; use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; +use anyhow::Result; use kvm_ioctls::DeviceFd; use once_cell::sync::Lazy; +use devices::pci::register_pcidevops_type; use vfio_dev::VfioGroup; pub static KVM_DEVICE_FD: Lazy>> = Lazy::new(|| Mutex::new(None)); @@ -38,3 +40,7 @@ pub static CONTAINERS: Lazy>>>> = Lazy::new(|| Mutex::new(HashMap::new())); pub static GROUPS: Lazy>>> = Lazy::new(|| Mutex::new(HashMap::new())); + +pub fn vfio_register_pcidevops_type() -> Result<()> { + register_pcidevops_type::() +} diff --git a/vfio/src/vfio_pci.rs b/vfio/src/vfio_pci.rs index d354098..ebf028e 100644 --- a/vfio/src/vfio_pci.rs +++ b/vfio/src/vfio_pci.rs @@ -17,6 +17,7 @@ use std::sync::{Arc, Mutex, Weak}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; +use clap::{ArgAction, Parser}; use log::error; use vfio_bindings::bindings::vfio; use vmm_sys_util::eventfd::EventFd; @@ -43,12 +44,34 @@ use devices::pci::{ pci_ext_cap_next, pci_ext_cap_ver, PciBus, PciDevBase, PciDevOps, }; use devices::{pci::MsiVector, Device, DeviceBase}; +use machine_manager::config::{get_pci_df, parse_bool, valid_id}; +use util::loop_context::create_new_eventfd; use util::num_ops::ranges_overlap; use util::unix::host_page_size; const PCI_NUM_BARS: u8 = 6; const PCI_ROM_SLOT: u8 = 6; +#[derive(Parser, Default, Debug)] +#[command(no_binary_name(true))] +#[clap(group = clap::ArgGroup::new("path").args(&["host", "sysfsdev"]).multiple(false).required(true))] +pub struct VfioConfig { + #[arg(long, value_parser = ["vfio-pci"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long, value_parser = valid_id)] + pub host: Option, + #[arg(long)] + pub bus: String, + #[arg(long)] + pub sysfsdev: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + #[arg(long, value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: Option, +} + struct MsixTable { table_bar: u8, table_offset: u64, @@ -512,7 +535,7 @@ impl VfioPciDevice { let mut locked_gsi_routes = cloned_gsi_routes.lock().unwrap(); let gsi_route = locked_gsi_routes.get_mut(vector as usize).unwrap(); if gsi_route.irq_fd.is_none() { - let irq_fd = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let irq_fd = create_new_eventfd().unwrap(); gsi_route.irq_fd = Some(Arc::new(irq_fd)); } let irq_fd = gsi_route.irq_fd.clone(); @@ -700,7 +723,7 @@ impl VfioPciDevice { fn vfio_enable_msix(&mut self) -> Result<()> { let mut gsi_routes = self.gsi_msi_routes.lock().unwrap(); if gsi_routes.len() == 0 { - let irq_fd = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let irq_fd = create_new_eventfd().unwrap(); let gsi_route = GsiMsiRoute { irq_fd: Some(Arc::new(irq_fd)), gsi: -1, @@ -1009,3 +1032,38 @@ fn get_irq_rawfds(gsi_msi_routes: &[GsiMsiRoute], start: u32, count: u32) -> Vec } rawfds } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_vfio_config_cmdline_parser() { + // Test1: right. + let vfio_cmd1 = "vfio-pci,host=0000:1a:00.3,id=net,bus=pcie.0,addr=0x5,multifunction=on"; + let result = VfioConfig::try_parse_from(str_slip_to_clap(vfio_cmd1, true, false)); + assert!(result.is_ok()); + let vfio_config = result.unwrap(); + assert_eq!(vfio_config.host, Some("0000:1a:00.3".to_string())); + assert_eq!(vfio_config.id, "net"); + assert_eq!(vfio_config.bus, "pcie.0"); + assert_eq!(vfio_config.addr, (5, 0)); + assert_eq!(vfio_config.multifunction, Some(true)); + + // Test2: Missing bus/addr. + let vfio_cmd2 = "vfio-pci,host=0000:1a:00.3,id=net"; + let result = VfioConfig::try_parse_from(str_slip_to_clap(vfio_cmd2, true, false)); + assert!(result.is_err()); + + // Test3: `host` conflicts with `sysfsdev`. + let vfio_cmd3 = "vfio-pci,host=0000:1a:00.3,sysfsdev=/sys/bus/pci/devices/0000:00:02.0,id=net,bus=pcie.0,addr=0x5"; + let result = VfioConfig::try_parse_from(str_slip_to_clap(vfio_cmd3, true, false)); + assert!(result.is_err()); + + // Test4: Missing host/sysfsdev. + let vfio_cmd4 = "vfio-pci,id=net,bus=pcie.0,addr=0x1.0x2"; + let result = VfioConfig::try_parse_from(str_slip_to_clap(vfio_cmd4, true, false)); + assert!(result.is_err()); + } +} diff --git a/virtio/src/device/balloon.rs b/virtio/src/device/balloon.rs index fd6347a..33f89d3 100644 --- a/virtio/src/device/balloon.rs +++ b/virtio/src/device/balloon.rs @@ -612,11 +612,12 @@ impl BalloonIoHandler { /// if `req_type` is `BALLOON_INFLATE_EVENT`, then inflate the balloon, otherwise, deflate the /// balloon. fn process_balloon_queue(&mut self, req_type: bool) -> Result<()> { + trace::trace_scope_start!(process_balloon_queue); let queue = if req_type { trace::virtio_receive_request("Balloon".to_string(), "to inflate".to_string()); &self.inf_queue } else { - trace::virtio_receive_request("Balloon".to_string(), "to inflate".to_string()); + trace::virtio_receive_request("Balloon".to_string(), "to deflate".to_string()); &self.def_queue }; let mut locked_queue = queue.lock().unwrap(); @@ -648,6 +649,7 @@ impl BalloonIoHandler { } fn reporting_evt_handler(&mut self) -> Result<()> { + trace::trace_scope_start!(reporting_evt_handler); let queue = self .report_queue .as_ref() @@ -682,6 +684,7 @@ impl BalloonIoHandler { } fn auto_msg_evt_handler(&mut self) -> Result<()> { + trace::trace_scope_start!(auto_msg_evt_handler); let queue = self .msg_queue .as_ref() @@ -882,8 +885,10 @@ impl EventNotifierHelper for BalloonIoHandler { } #[derive(Parser, Debug, Clone, Default)] -#[command(name = "balloon")] +#[command(no_binary_name(true))] pub struct BalloonConfig { + #[arg(long)] + pub classtype: String, #[arg(long, value_parser = valid_id)] pub id: String, #[arg(long)] diff --git a/virtio/src/device/block.rs b/virtio/src/device/block.rs index a7719cf..9b14a88 100644 --- a/virtio/src/device/block.rs +++ b/virtio/src/device/block.rs @@ -21,26 +21,30 @@ use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; +use clap::Parser; use log::{error, warn}; use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; use crate::{ - check_config_space_rw, gpa_hva_iovec_map, iov_discard_back, iov_discard_front, iov_to_buf, - read_config_default, report_virtio_error, virtio_has_feature, Element, Queue, VirtioBase, - VirtioDevice, VirtioError, VirtioInterrupt, VirtioInterruptType, VIRTIO_BLK_F_DISCARD, - VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX, - VIRTIO_BLK_F_WRITE_ZEROES, VIRTIO_BLK_ID_BYTES, VIRTIO_BLK_S_IOERR, VIRTIO_BLK_S_OK, - VIRTIO_BLK_S_UNSUPP, VIRTIO_BLK_T_DISCARD, VIRTIO_BLK_T_FLUSH, VIRTIO_BLK_T_GET_ID, - VIRTIO_BLK_T_IN, VIRTIO_BLK_T_OUT, VIRTIO_BLK_T_WRITE_ZEROES, + check_config_space_rw, gpa_hva_iovec_map_by_cache, iov_discard_back, iov_discard_front, + iov_to_buf_by_cache, read_config_default, report_virtio_error, virtio_has_feature, Element, + Queue, VirtioBase, VirtioDevice, VirtioError, VirtioInterrupt, VirtioInterruptType, + VIRTIO_BLK_F_DISCARD, VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_RO, + VIRTIO_BLK_F_SEG_MAX, VIRTIO_BLK_F_WRITE_ZEROES, VIRTIO_BLK_ID_BYTES, VIRTIO_BLK_S_IOERR, + VIRTIO_BLK_S_OK, VIRTIO_BLK_S_UNSUPP, VIRTIO_BLK_T_DISCARD, VIRTIO_BLK_T_FLUSH, + VIRTIO_BLK_T_GET_ID, VIRTIO_BLK_T_IN, VIRTIO_BLK_T_OUT, VIRTIO_BLK_T_WRITE_ZEROES, VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_VERSION_1, VIRTIO_TYPE_BLOCK, }; -use address_space::{AddressSpace, GuestAddress}; +use address_space::{AddressSpace, GuestAddress, RegionCache}; use block_backend::{ create_block_backend, remove_block_backend, BlockDriverOps, BlockIoErrorCallback, BlockProperty, BlockStatus, }; -use machine_manager::config::{BlkDevConfig, ConfigCheck, DriveFile, VmConfig}; +use machine_manager::config::{ + get_pci_df, parse_bool, valid_block_device_virtqueue_size, valid_id, ConfigCheck, ConfigError, + DriveConfig, DriveFile, VmConfig, DEFAULT_VIRTQUEUE_SIZE, MAX_VIRTIO_QUEUE, +}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper, EventLoop}; use migration::{ migration::Migratable, DeviceStateDesc, FieldDesc, MigrationHook, MigrationManager, @@ -54,7 +58,8 @@ use util::aio::{ use util::byte_code::ByteCode; use util::leak_bucket::LeakBucket; use util::loop_context::{ - read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, + create_new_eventfd, read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, + NotifierOperation, }; use util::offset_of; @@ -76,6 +81,8 @@ const MAX_NUM_MERGE_BYTES: u64 = i32::MAX as u64; const MAX_ITERATION_PROCESS_QUEUE: u16 = 10; /// Max number sectors of per request. const MAX_REQUEST_SECTORS: u32 = u32::MAX >> SECTOR_SHIFT; +/// Max length of serial number. +const MAX_SERIAL_NUM_LEN: usize = 20; type SenderConfig = ( Option>>>, @@ -86,6 +93,70 @@ type SenderConfig = ( bool, ); +fn valid_serial(s: &str) -> Result { + if s.len() > MAX_SERIAL_NUM_LEN { + return Err(anyhow!(ConfigError::StringLengthTooLong( + "device serial number".to_string(), + MAX_SERIAL_NUM_LEN, + ))); + } + Ok(s.to_string()) +} + +#[derive(Parser, Debug, Clone)] +#[command(no_binary_name(true))] +pub struct VirtioBlkDevConfig { + #[arg(long, value_parser = ["virtio-blk-pci", "virtio-blk-device"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool)] + pub multifunction: Option, + #[arg(long)] + pub drive: String, + #[arg(long)] + pub bootindex: Option, + #[arg(long, alias = "num-queues", value_parser = clap::value_parser!(u16).range(1..=MAX_VIRTIO_QUEUE as i64))] + pub num_queues: Option, + #[arg(long)] + pub iothread: Option, + #[arg(long, alias = "queue-size", default_value = "256", value_parser = valid_block_device_virtqueue_size)] + pub queue_size: u16, + #[arg(long, value_parser = valid_serial)] + pub serial: Option, +} + +impl Default for VirtioBlkDevConfig { + fn default() -> Self { + Self { + classtype: "".to_string(), + id: "".to_string(), + bus: None, + addr: None, + multifunction: None, + drive: "".to_string(), + num_queues: Some(1), + bootindex: None, + iothread: None, + queue_size: DEFAULT_VIRTQUEUE_SIZE, + serial: None, + } + } +} + +impl ConfigCheck for VirtioBlkDevConfig { + fn check(&self) -> Result<()> { + if self.serial.is_some() { + valid_serial(&self.serial.clone().unwrap())?; + } + Ok(()) + } +} + fn get_serial_num_config(serial_num: &str) -> Vec { let mut id_bytes = vec![0; VIRTIO_BLK_ID_BYTES as usize]; let bytes_to_copy = cmp::min(serial_num.len(), VIRTIO_BLK_ID_BYTES as usize); @@ -200,7 +271,12 @@ struct Request { } impl Request { - fn new(handler: &BlockIoHandler, elem: &mut Element, status: &mut u8) -> Result { + fn new( + handler: &BlockIoHandler, + cache: &Option, + elem: &mut Element, + status: &mut u8, + ) -> Result { if elem.out_iovec.is_empty() || elem.in_iovec.is_empty() { bail!( "Missed header for block request: out {} in {} desc num {}", @@ -211,8 +287,9 @@ impl Request { } let mut out_header = RequestOutHeader::default(); - iov_to_buf( + iov_to_buf_by_cache( &handler.mem_space, + cache, &elem.out_iovec, out_header.as_mut_bytes(), ) @@ -268,7 +345,8 @@ impl Request { } .with_context(|| "Empty data for block request")?; - let (data_len, iovec) = gpa_hva_iovec_map(data_iovec, &handler.mem_space)?; + let (data_len, iovec) = + gpa_hva_iovec_map_by_cache(data_iovec, &handler.mem_space, cache)?; request.data_len = data_len; request.iovec = iovec; } @@ -581,7 +659,8 @@ impl BlockIoHandler { // Init and put valid request into request queue. let mut status = VIRTIO_BLK_S_OK; - let req = Request::new(self, &mut elem, &mut status)?; + let cache = queue.vring.get_cache(); + let req = Request::new(self, cache, &mut elem, &mut status)?; if status != VIRTIO_BLK_S_OK { let aiocompletecb = AioCompleteCb::new( self.queue.clone(), @@ -955,7 +1034,9 @@ pub struct Block { /// Virtio device base property. base: VirtioBase, /// Configuration of the block device. - blk_cfg: BlkDevConfig, + blk_cfg: VirtioBlkDevConfig, + /// Configuration of the block device's drive. + drive_cfg: DriveConfig, /// Config space of the block device. config_space: VirtioBlkConfig, /// BLock backend opened by the block device. @@ -978,14 +1059,16 @@ pub struct Block { impl Block { pub fn new( - blk_cfg: BlkDevConfig, + blk_cfg: VirtioBlkDevConfig, + drive_cfg: DriveConfig, drive_files: Arc>>, ) -> Block { - let queue_num = blk_cfg.queues as usize; + let queue_num = blk_cfg.num_queues.unwrap_or(1) as usize; let queue_size = blk_cfg.queue_size; Self { base: VirtioBase::new(VIRTIO_TYPE_BLOCK, queue_num, queue_size), blk_cfg, + drive_cfg, req_align: 1, buf_align: 1, drive_files, @@ -999,11 +1082,11 @@ impl Block { // seg_max = queue_size - 2: 32bits self.config_space.seg_max = self.queue_size_max() as u32 - 2; - if self.blk_cfg.queues > 1 { - self.config_space.num_queues = self.blk_cfg.queues; + if self.blk_cfg.num_queues.unwrap_or(1) > 1 { + self.config_space.num_queues = self.blk_cfg.num_queues.unwrap_or(1); } - if self.blk_cfg.discard { + if self.drive_cfg.discard { // Just support one segment per request. self.config_space.max_discard_seg = 1; // The default discard alignment is 1 sector. @@ -1011,7 +1094,7 @@ impl Block { self.config_space.max_discard_sectors = MAX_REQUEST_SECTORS; } - if self.blk_cfg.write_zeroes != WriteZeroesState::Off { + if self.drive_cfg.write_zeroes != WriteZeroesState::Off { // Just support one segment per request. self.config_space.max_write_zeroes_seg = 1; self.config_space.max_write_zeroes_sectors = MAX_REQUEST_SECTORS; @@ -1058,35 +1141,36 @@ impl VirtioDevice for Block { ); } - if !self.blk_cfg.path_on_host.is_empty() { + if !self.drive_cfg.path_on_host.is_empty() { let drive_files = self.drive_files.lock().unwrap(); - let file = VmConfig::fetch_drive_file(&drive_files, &self.blk_cfg.path_on_host)?; - let alignments = VmConfig::fetch_drive_align(&drive_files, &self.blk_cfg.path_on_host)?; + let file = VmConfig::fetch_drive_file(&drive_files, &self.drive_cfg.path_on_host)?; + let alignments = + VmConfig::fetch_drive_align(&drive_files, &self.drive_cfg.path_on_host)?; self.req_align = alignments.0; self.buf_align = alignments.1; - let drive_id = VmConfig::get_drive_id(&drive_files, &self.blk_cfg.path_on_host)?; + let drive_id = VmConfig::get_drive_id(&drive_files, &self.drive_cfg.path_on_host)?; let mut thread_pool = None; - if self.blk_cfg.aio != AioEngine::Off { + if self.drive_cfg.aio != AioEngine::Off { thread_pool = Some(EventLoop::get_ctx(None).unwrap().thread_pool.clone()); } let aio = Aio::new( Arc::new(BlockIoHandler::complete_func), - self.blk_cfg.aio, + self.drive_cfg.aio, thread_pool, )?; let conf = BlockProperty { id: drive_id, - format: self.blk_cfg.format, + format: self.drive_cfg.format, iothread: self.blk_cfg.iothread.clone(), - direct: self.blk_cfg.direct, + direct: self.drive_cfg.direct, req_align: self.req_align, buf_align: self.buf_align, - discard: self.blk_cfg.discard, - write_zeroes: self.blk_cfg.write_zeroes, - l2_cache_size: self.blk_cfg.l2_cache_size, - refcount_cache_size: self.blk_cfg.refcount_cache_size, + discard: self.drive_cfg.discard, + write_zeroes: self.drive_cfg.write_zeroes, + l2_cache_size: self.drive_cfg.l2_cache_size, + refcount_cache_size: self.drive_cfg.refcount_cache_size, }; let backend = create_block_backend(file, aio, conf)?; let disk_size = backend.lock().unwrap().disk_size()?; @@ -1110,16 +1194,16 @@ impl VirtioDevice for Block { | 1_u64 << VIRTIO_F_RING_EVENT_IDX | 1_u64 << VIRTIO_BLK_F_FLUSH | 1_u64 << VIRTIO_BLK_F_SEG_MAX; - if self.blk_cfg.read_only { + if self.drive_cfg.readonly { self.base.device_features |= 1_u64 << VIRTIO_BLK_F_RO; }; - if self.blk_cfg.queues > 1 { + if self.blk_cfg.num_queues.unwrap_or(1) > 1 { self.base.device_features |= 1_u64 << VIRTIO_BLK_F_MQ; } - if self.blk_cfg.discard { + if self.drive_cfg.discard { self.base.device_features |= 1_u64 << VIRTIO_BLK_F_DISCARD; } - if self.blk_cfg.write_zeroes != WriteZeroesState::Off { + if self.drive_cfg.write_zeroes != WriteZeroesState::Off { self.base.device_features |= 1_u64 << VIRTIO_BLK_F_WRITE_ZEROES; } self.build_device_config_space(); @@ -1130,7 +1214,7 @@ impl VirtioDevice for Block { fn unrealize(&mut self) -> Result<()> { MigrationManager::unregister_device_instance(BlockState::descriptor(), &self.blk_cfg.id); let drive_files = self.drive_files.lock().unwrap(); - let drive_id = VmConfig::get_drive_id(&drive_files, &self.blk_cfg.path_on_host)?; + let drive_id = VmConfig::get_drive_id(&drive_files, &self.drive_cfg.path_on_host)?; remove_block_backend(&drive_id); Ok(()) } @@ -1166,7 +1250,7 @@ impl VirtioDevice for Block { continue; } let (sender, receiver) = channel(); - let update_evt = Arc::new(EventFd::new(libc::EFD_NONBLOCK)?); + let update_evt = Arc::new(create_new_eventfd()?); let driver_features = self.base.driver_features; let handler = BlockIoHandler { queue: queue.clone(), @@ -1176,20 +1260,20 @@ impl VirtioDevice for Block { req_align: self.req_align, buf_align: self.buf_align, disk_sectors: self.disk_sectors, - direct: self.blk_cfg.direct, - serial_num: self.blk_cfg.serial_num.clone(), + direct: self.drive_cfg.direct, + serial_num: self.blk_cfg.serial.clone(), driver_features, receiver, update_evt: update_evt.clone(), device_broken: self.base.broken.clone(), interrupt_cb: interrupt_cb.clone(), iothread: self.blk_cfg.iothread.clone(), - leak_bucket: match self.blk_cfg.iops { + leak_bucket: match self.drive_cfg.iops { Some(iops) => Some(LeakBucket::new(iops)?), None => None, }, - discard: self.blk_cfg.discard, - write_zeroes: self.blk_cfg.write_zeroes, + discard: self.drive_cfg.discard, + write_zeroes: self.drive_cfg.write_zeroes, }; let notifiers = EventNotifierHelper::internal_notifiers(Arc::new(Mutex::new(handler))); @@ -1236,18 +1320,28 @@ impl VirtioDevice for Block { Ok(()) } - fn update_config(&mut self, dev_config: Option>) -> Result<()> { - let is_plug = dev_config.is_some(); - if let Some(conf) = dev_config { - self.blk_cfg = conf + // configs[0]: DriveConfig. configs[1]: VirtioBlkDevConfig. + fn update_config(&mut self, configs: Vec>) -> Result<()> { + let mut is_plug = false; + if configs.len() == 2 { + self.drive_cfg = configs[0] + .as_any() + .downcast_ref::() + .unwrap() + .clone(); + self.blk_cfg = configs[1] .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .clone(); // microvm type block device don't support multiple queue. - self.blk_cfg.queues = QUEUE_NUM_BLK as u16; - } else { + self.blk_cfg.num_queues = Some(QUEUE_NUM_BLK as u16); + is_plug = true; + } else if configs.is_empty() { self.blk_cfg = Default::default(); + self.drive_cfg = Default::default(); + } else { + bail!("Invalid update configs."); } if !is_plug { @@ -1296,8 +1390,8 @@ impl VirtioDevice for Block { self.req_align, self.buf_align, self.disk_sectors, - self.blk_cfg.serial_num.clone(), - self.blk_cfg.direct, + self.blk_cfg.serial.clone(), + self.drive_cfg.direct, )) .with_context(|| VirtioError::ChannelSend("image fd".to_string()))?; } @@ -1354,7 +1448,9 @@ mod tests { use super::*; use crate::*; use address_space::{AddressSpace, GuestAddress, HostMemMapping, Region}; - use machine_manager::config::{IothreadConfig, VmConfig, DEFAULT_VIRTQUEUE_SIZE}; + use machine_manager::config::{ + str_slip_to_clap, IothreadConfig, VmConfig, DEFAULT_VIRTQUEUE_SIZE, + }; const QUEUE_NUM_BLK: usize = 1; const CONFIG_SPACE_SIZE: usize = 60; @@ -1390,11 +1486,36 @@ mod tests { fn init_default_block() -> Block { Block::new( - BlkDevConfig::default(), + VirtioBlkDevConfig::default(), + DriveConfig::default(), Arc::new(Mutex::new(HashMap::new())), ) } + #[test] + fn test_virtio_block_config_cmdline_parser() { + // Test1: Right. + let blk_cmd1 = "virtio-blk-pci,id=rootfs,bus=pcie.0,addr=0x1.0x2,drive=rootfs,serial=111111,num-queues=4"; + let blk_config = + VirtioBlkDevConfig::try_parse_from(str_slip_to_clap(blk_cmd1, true, false)).unwrap(); + assert_eq!(blk_config.id, "rootfs"); + assert_eq!(blk_config.bus.unwrap(), "pcie.0"); + assert_eq!(blk_config.addr.unwrap(), (1, 2)); + assert_eq!(blk_config.serial.unwrap(), "111111"); + assert_eq!(blk_config.num_queues.unwrap(), 4); + + // Test2: Default values. + assert_eq!(blk_config.queue_size, DEFAULT_VIRTQUEUE_SIZE); + + // Test3: Illegal values. + let blk_cmd3 = "virtio-blk-pci,id=rootfs,bus=pcie.0,addr=0x1.0x2,drive=rootfs,serial=111111,num-queues=33"; + let result = VirtioBlkDevConfig::try_parse_from(str_slip_to_clap(blk_cmd3, true, false)); + assert!(result.is_err()); + let blk_cmd3 = "virtio-blk-pci,id=rootfs,drive=rootfs,serial=111111111111111111111111111111111111111111111111111111111111111111111"; + let result = VirtioBlkDevConfig::try_parse_from(str_slip_to_clap(blk_cmd3, true, false)); + assert!(result.is_err()); + } + // Use different input parameters to verify block `new()` and `realize()` functionality. #[test] fn test_block_init() { @@ -1410,16 +1531,16 @@ mod tests { assert!(block.senders.is_empty()); // Realize block device: create TempFile as backing file. - block.blk_cfg.read_only = true; - block.blk_cfg.direct = false; + block.drive_cfg.readonly = true; + block.drive_cfg.direct = false; let f = TempFile::new().unwrap(); - block.blk_cfg.path_on_host = f.as_path().to_str().unwrap().to_string(); + block.drive_cfg.path_on_host = f.as_path().to_str().unwrap().to_string(); VmConfig::add_drive_file( &mut block.drive_files.lock().unwrap(), "", - &block.blk_cfg.path_on_host, - block.blk_cfg.read_only, - block.blk_cfg.direct, + &block.drive_cfg.path_on_host, + block.drive_cfg.readonly, + block.drive_cfg.direct, ) .unwrap(); assert!(block.realize().is_ok()); @@ -1541,25 +1662,26 @@ mod tests { // spawn io thread let io_conf = IothreadConfig { + classtype: "iothread".to_string(), id: thread_name.clone(), }; EventLoop::object_init(&Some(vec![io_conf])).unwrap(); let mut block = init_default_block(); let file = TempFile::new().unwrap(); - block.blk_cfg.path_on_host = file.as_path().to_str().unwrap().to_string(); - block.blk_cfg.direct = false; + block.drive_cfg.path_on_host = file.as_path().to_str().unwrap().to_string(); + block.drive_cfg.direct = false; // config iothread and iops block.blk_cfg.iothread = Some(thread_name); - block.blk_cfg.iops = Some(100); + block.drive_cfg.iops = Some(100); VmConfig::add_drive_file( &mut block.drive_files.lock().unwrap(), "", - &block.blk_cfg.path_on_host, - block.blk_cfg.read_only, - block.blk_cfg.direct, + &block.drive_cfg.path_on_host, + block.drive_cfg.readonly, + block.drive_cfg.direct, ) .unwrap(); diff --git a/virtio/src/device/gpu.rs b/virtio/src/device/gpu.rs index 97ca67c..2d138ed 100644 --- a/virtio/src/device/gpu.rs +++ b/virtio/src/device/gpu.rs @@ -18,6 +18,7 @@ use std::sync::{Arc, Mutex, Weak}; use std::{ptr, vec}; use anyhow::{anyhow, bail, Context, Result}; +use clap::{ArgAction, Parser}; use log::{error, info, warn}; use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; @@ -36,7 +37,7 @@ use crate::{ VIRTIO_GPU_RESP_OK_EDID, VIRTIO_GPU_RESP_OK_NODATA, VIRTIO_TYPE_GPU, }; use address_space::{AddressSpace, FileBackend, GuestAddress}; -use machine_manager::config::{GpuDevConfig, DEFAULT_VIRTQUEUE_SIZE, VIRTIO_GPU_MAX_OUTPUTS}; +use machine_manager::config::{get_pci_df, valid_id, DEFAULT_VIRTQUEUE_SIZE}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use migration_derive::ByteCode; use ui::console::{ @@ -72,6 +73,49 @@ const VIRTIO_GPU_RES_WIN_FRAMEBUF: u32 = 0x80000000; const VIRTIO_GPU_RES_EFI_FRAMEBUF: u32 = 0x40000000; const VIRTIO_GPU_RES_FRAMEBUF: u32 = VIRTIO_GPU_RES_WIN_FRAMEBUF | VIRTIO_GPU_RES_EFI_FRAMEBUF; +/// The maximum number of outputs. +const VIRTIO_GPU_MAX_OUTPUTS: usize = 16; +/// The default maximum memory 256M. +const VIRTIO_GPU_DEFAULT_MAX_HOSTMEM: u64 = 0x10000000; + +#[derive(Parser, Clone, Debug, Default)] +#[command(no_binary_name(true))] +pub struct GpuDevConfig { + #[arg(long, value_parser = ["virtio-gpu-pci"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: String, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + #[arg(long, alias = "max_outputs", default_value="1", value_parser = clap::value_parser!(u32).range(1..=VIRTIO_GPU_MAX_OUTPUTS as i64))] + pub max_outputs: u32, + #[arg(long, default_value="true", action = ArgAction::Append)] + pub edid: bool, + #[arg(long, default_value = "1024")] + pub xres: u32, + #[arg(long, default_value = "768")] + pub yres: u32, + // The default max_hostmem is 256M. + #[arg(long, alias = "max_hostmem", default_value="268435456", value_parser = clap::value_parser!(u64).range(1..))] + pub max_hostmem: u64, + #[arg(long, alias = "enable_bar0", default_value="false", action = ArgAction::Append)] + pub enable_bar0: bool, +} + +impl GpuDevConfig { + pub fn check(&self) { + if self.max_hostmem < VIRTIO_GPU_DEFAULT_MAX_HOSTMEM { + warn!( + "max_hostmem should >= {}, allocating less than it may cause \ + the GPU to fail to start or refresh.", + VIRTIO_GPU_DEFAULT_MAX_HOSTMEM + ); + } + } +} + #[derive(Debug)] struct GpuResource { resource_id: u32, @@ -1182,6 +1226,12 @@ impl GpuIoHandler { scanout.width = info_set_scanout.rect.width; scanout.height = info_set_scanout.rect.height; + if (self.driver_features & (1 << VIRTIO_GPU_F_EDID)) == 0 + && (info_set_scanout.resource_id & VIRTIO_GPU_RES_WIN_FRAMEBUF) != 0 + { + self.change_run_stage()?; + } + self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req) } @@ -1843,3 +1893,50 @@ impl VirtioDevice for Gpu { result } } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_parse_virtio_gpu_pci_cmdline() { + // Test1: Right. + let gpu_cmd = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=5,edid=false,\ + xres=2048,yres=800,enable_bar0=true,max_hostmem=268435457"; + let gpu_cfg = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd, true, false)).unwrap(); + assert_eq!(gpu_cfg.id, "gpu_1"); + assert_eq!(gpu_cfg.bus, "pcie.0"); + assert_eq!(gpu_cfg.addr, (4, 0)); + assert_eq!(gpu_cfg.max_outputs, 5); + assert_eq!(gpu_cfg.xres, 2048); + assert_eq!(gpu_cfg.yres, 800); + assert_eq!(gpu_cfg.edid, false); + assert_eq!(gpu_cfg.max_hostmem, 268435457); + assert_eq!(gpu_cfg.enable_bar0, true); + + // Test2: Default. + let gpu_cmd2 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0"; + let gpu_cfg = + GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd2, true, false)).unwrap(); + assert_eq!(gpu_cfg.max_outputs, 1); + assert_eq!(gpu_cfg.xres, 1024); + assert_eq!(gpu_cfg.yres, 768); + assert_eq!(gpu_cfg.edid, true); + assert_eq!(gpu_cfg.max_hostmem, VIRTIO_GPU_DEFAULT_MAX_HOSTMEM); + assert_eq!(gpu_cfg.enable_bar0, false); + + // Test3/4: max_outputs is illegal. + let gpu_cmd3 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=17"; + let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd3, true, false)); + assert!(result.is_err()); + let gpu_cmd4 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=0"; + let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd4, true, false)); + assert!(result.is_err()); + + // Test5: max_hostmem is illegal. + let gpu_cmd5 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_hostmem=0"; + let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd5, true, false)); + assert!(result.is_err()); + } +} diff --git a/virtio/src/device/net.rs b/virtio/src/device/net.rs index 4e605fa..391a6cd 100644 --- a/virtio/src/device/net.rs +++ b/virtio/src/device/net.rs @@ -46,7 +46,7 @@ use crate::{ use address_space::{AddressSpace, RegionCache}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use machine_manager::{ - config::{ConfigCheck, NetworkInterfaceConfig}, + config::{ConfigCheck, NetDevcfg, NetworkInterfaceConfig}, event_loop::EventLoop, }; use migration::{ @@ -57,7 +57,8 @@ use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; use util::loop_context::gen_delete_notifiers; use util::loop_context::{ - read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, + create_new_eventfd, read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, + NotifierOperation, }; use util::num_ops::str_to_num; use util::tap::{ @@ -672,30 +673,33 @@ impl EventNotifierHelper for NetCtrlHandler { } struct TxVirtio { + tap_full: bool, queue: Arc>, queue_evt: Arc, } impl TxVirtio { fn new(queue: Arc>, queue_evt: Arc) -> Self { - TxVirtio { queue, queue_evt } + TxVirtio { + tap_full: false, + queue, + queue_evt, + } } } struct RxVirtio { - queue_full: bool, + queue_avail: bool, queue: Arc>, queue_evt: Arc, - recv_evt: Arc, } impl RxVirtio { - fn new(queue: Arc>, queue_evt: Arc, recv_evt: Arc) -> Self { + fn new(queue: Arc>, queue_evt: Arc) -> Self { RxVirtio { - queue_full: false, + queue_avail: false, queue, queue_evt, - recv_evt, } } } @@ -792,7 +796,11 @@ impl NetIoHandler { .pop_avail(&self.mem_space, self.driver_features) .with_context(|| "Failed to pop avail ring for net rx")?; if elem.desc_num == 0 { - self.rx.queue_full = true; + queue + .vring + .suppress_queue_notify(&self.mem_space, self.driver_features, false) + .with_context(|| "Failed to enable rx queue notify")?; + self.rx.queue_avail = false; break; } else if elem.in_iovec.is_empty() { bail!("The length of in iovec is 0"); @@ -863,9 +871,9 @@ impl NetIoHandler { rx_packets += 1; if rx_packets >= self.queue_size { self.rx - .recv_evt + .queue_evt .write(1) - .with_context(|| "Failed to trigger tap queue event".to_string())?; + .with_context(|| "Failed to trigger rx queue event".to_string())?; break; } } @@ -925,10 +933,12 @@ impl NetIoHandler { }; if tap_fd != -1 && self.send_packets(tap_fd, &iovecs) == -1 { queue.vring.push_back(); - self.tx.queue_evt.write(1).with_context(|| { - "Failed to trigger tx queue event when writev blocked".to_string() - })?; - return Ok(()); + queue + .vring + .suppress_queue_notify(&self.mem_space, self.driver_features, true) + .with_context(|| "Failed to suppress tx queue notify")?; + self.tx.tap_full = true; + break; } queue @@ -959,6 +969,50 @@ impl NetIoHandler { Ok(()) } + fn tap_fd_handler(net_io: &mut Self) -> Vec { + let mut notifiers = Vec::new(); + + if !net_io.is_listening && (net_io.rx.queue_avail || net_io.tx.tap_full) { + notifiers.push(EventNotifier::new( + NotifierOperation::Resume, + net_io.tap_fd, + None, + EventSet::empty(), + Vec::new(), + )); + net_io.is_listening = true; + } + + if !net_io.is_listening { + return notifiers; + } + + // NOTE: We want to poll for OUT event when the tap is full, and for IN event when the + // virtio queue is available. + let tap_events = match (net_io.rx.queue_avail, net_io.tx.tap_full) { + (true, true) => EventSet::OUT | EventSet::IN | EventSet::EDGE_TRIGGERED, + (false, true) => EventSet::OUT | EventSet::EDGE_TRIGGERED, + (true, false) => EventSet::IN | EventSet::EDGE_TRIGGERED, + (false, false) => EventSet::empty(), + }; + + let tap_operation = if tap_events.is_empty() { + net_io.is_listening = false; + NotifierOperation::Park + } else { + NotifierOperation::Modify + }; + + notifiers.push(EventNotifier::new( + tap_operation, + net_io.tap_fd, + None, + tap_events, + Vec::new(), + )); + notifiers + } + fn update_evt_handler(net_io: &Arc>) -> Vec { let mut locked_net_io = net_io.lock().unwrap(); locked_net_io.tap = match locked_net_io.receiver.recv() { @@ -977,7 +1031,6 @@ impl NetIoHandler { let mut notifiers_fds = vec![ locked_net_io.update_evt.as_raw_fd(), locked_net_io.rx.queue_evt.as_raw_fd(), - locked_net_io.rx.recv_evt.as_raw_fd(), locked_net_io.tx.queue_evt.as_raw_fd(), ]; if old_tap_fd != -1 { @@ -1055,30 +1108,40 @@ impl EventNotifierHelper for NetIoHandler { return None; } - if let Err(ref e) = locked_net_io.rx.recv_evt.write(1) { - error!("Failed to trigger tap receive event, {:?}", e); + locked_net_io.rx.queue_avail = true; + let mut locked_queue = locked_net_io.rx.queue.lock().unwrap(); + + if let Err(ref err) = locked_queue.vring.suppress_queue_notify( + &locked_net_io.mem_space, + locked_net_io.driver_features, + true, + ) { + error!("Failed to suppress rx queue notify: {:?}", err); report_virtio_error( locked_net_io.interrupt_cb.clone(), locked_net_io.driver_features, &locked_net_io.device_broken, ); + return None; + }; + + drop(locked_queue); + + if let Err(ref err) = locked_net_io.handle_rx() { + error!("Failed to handle receive queue event: {:?}", err); + report_virtio_error( + locked_net_io.interrupt_cb.clone(), + locked_net_io.driver_features, + &locked_net_io.device_broken, + ); + return None; } - if let Some(tap) = locked_net_io.tap.as_ref() { - if !locked_net_io.is_listening { - let notifier = vec![EventNotifier::new( - NotifierOperation::Resume, - tap.as_raw_fd(), - None, - EventSet::IN | EventSet::EDGE_TRIGGERED, - Vec::new(), - )]; - locked_net_io.is_listening = true; - locked_net_io.rx.queue_full = false; - return Some(notifier); - } + if locked_net_io.tap.is_some() { + Some(NetIoHandler::tap_fd_handler(&mut locked_net_io)) + } else { + None } - None }); let rx_fd = locked_net_io.rx.queue_evt.as_raw_fd(); notifiers.push(build_event_notifier( @@ -1096,6 +1159,7 @@ impl EventNotifierHelper for NetIoHandler { if locked_net_io.device_broken.load(Ordering::SeqCst) { return None; } + if let Err(ref e) = locked_net_io.handle_tx() { error!("Failed to handle tx(tx event) for net, {:?}", e); report_virtio_error( @@ -1104,7 +1168,12 @@ impl EventNotifierHelper for NetIoHandler { &locked_net_io.device_broken, ); } - None + + if locked_net_io.tap.is_some() { + Some(NetIoHandler::tap_fd_handler(&mut locked_net_io)) + } else { + None + } }); let tx_fd = locked_net_io.tx.queue_evt.as_raw_fd(); notifiers.push(build_event_notifier( @@ -1117,50 +1186,62 @@ impl EventNotifierHelper for NetIoHandler { // Register event notifier for tap. let cloned_net_io = net_io.clone(); if let Some(tap) = locked_net_io.tap.as_ref() { - let handler: Rc = Rc::new(move |_, _| { + let handler: Rc = Rc::new(move |events: EventSet, _| { let mut locked_net_io = cloned_net_io.lock().unwrap(); if locked_net_io.device_broken.load(Ordering::SeqCst) { return None; } - if let Err(ref e) = locked_net_io.handle_rx() { - error!("Failed to handle rx(tap event), {:?}", e); - report_virtio_error( - locked_net_io.interrupt_cb.clone(), + if events.contains(EventSet::OUT) { + locked_net_io.tx.tap_full = false; + let mut locked_queue = locked_net_io.tx.queue.lock().unwrap(); + + if let Err(ref err) = locked_queue.vring.suppress_queue_notify( + &locked_net_io.mem_space, locked_net_io.driver_features, - &locked_net_io.device_broken, - ); - return None; + false, + ) { + error!("Failed to enable tx queue notify: {:?}", err); + report_virtio_error( + locked_net_io.interrupt_cb.clone(), + locked_net_io.driver_features, + &locked_net_io.device_broken, + ); + return None; + }; + + drop(locked_queue); + + if let Err(ref e) = locked_net_io.handle_tx() { + error!("Failed to handle tx(tx event) for net, {:?}", e); + report_virtio_error( + locked_net_io.interrupt_cb.clone(), + locked_net_io.driver_features, + &locked_net_io.device_broken, + ); + } } - if let Some(tap) = locked_net_io.tap.as_ref() { - if locked_net_io.rx.queue_full && locked_net_io.is_listening { - let notifier = vec![EventNotifier::new( - NotifierOperation::Park, - tap.as_raw_fd(), - None, - EventSet::IN | EventSet::EDGE_TRIGGERED, - Vec::new(), - )]; - locked_net_io.is_listening = false; - return Some(notifier); + if events.contains(EventSet::IN) { + if let Err(ref err) = locked_net_io.handle_rx() { + error!("Failed to handle receive queue event: {:?}", err); + report_virtio_error( + locked_net_io.interrupt_cb.clone(), + locked_net_io.driver_features, + &locked_net_io.device_broken, + ); + return None; } } - None + + Some(NetIoHandler::tap_fd_handler(&mut locked_net_io)) }); let tap_fd = tap.as_raw_fd(); notifiers.push(build_event_notifier( tap_fd, Some(handler.clone()), NotifierOperation::AddShared, - EventSet::IN | EventSet::EDGE_TRIGGERED, - )); - let recv_evt_fd = locked_net_io.rx.recv_evt.as_raw_fd(); - notifiers.push(build_event_notifier( - recv_evt_fd, - Some(handler), - NotifierOperation::AddShared, - EventSet::IN | EventSet::EDGE_TRIGGERED, + EventSet::OUT | EventSet::IN | EventSet::EDGE_TRIGGERED, )); } @@ -1190,6 +1271,8 @@ pub struct Net { base: VirtioBase, /// Configuration of the network device. net_cfg: NetworkInterfaceConfig, + /// Configuration of the network device. + netdev_cfg: NetDevcfg, /// Virtio net configurations. config_space: Arc>, /// Tap device opened. @@ -1203,9 +1286,9 @@ pub struct Net { } impl Net { - pub fn new(net_cfg: NetworkInterfaceConfig) -> Self { + pub fn new(net_cfg: NetworkInterfaceConfig, netdev_cfg: NetDevcfg) -> Self { let queue_num = if net_cfg.mq { - (net_cfg.queues + 1) as usize + (netdev_cfg.queues + 1) as usize } else { QUEUE_NUM_NET }; @@ -1214,6 +1297,7 @@ impl Net { Self { base: VirtioBase::new(VIRTIO_TYPE_NET, queue_num, queue_size), net_cfg, + netdev_cfg, ..Default::default() } } @@ -1397,11 +1481,11 @@ impl VirtioDevice for Net { ); } - let queue_pairs = self.net_cfg.queues / 2; - if !self.net_cfg.host_dev_name.is_empty() { - self.taps = create_tap(None, Some(&self.net_cfg.host_dev_name), queue_pairs) + let queue_pairs = self.netdev_cfg.queues / 2; + if !self.netdev_cfg.ifname.is_empty() { + self.taps = create_tap(None, Some(&self.netdev_cfg.ifname), queue_pairs) .with_context(|| "Failed to open tap with file path")?; - } else if let Some(fds) = self.net_cfg.tap_fds.as_mut() { + } else if let Some(fds) = self.netdev_cfg.tap_fds.as_mut() { let mut created_fds = 0; if let Some(taps) = &self.taps { for (index, tap) in taps.iter().enumerate() { @@ -1444,7 +1528,7 @@ impl VirtioDevice for Net { let mut locked_config = self.config_space.lock().unwrap(); - let queue_pairs = self.net_cfg.queues / 2; + let queue_pairs = self.netdev_cfg.queues / 2; if self.net_cfg.mq && (VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN..=VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX) .contains(&queue_pairs) @@ -1561,10 +1645,9 @@ impl VirtioDevice for Net { .with_context(|| "Failed to set tap offload")?; } - let update_evt = Arc::new(EventFd::new(libc::EFD_NONBLOCK)?); - let recv_evt = Arc::new(EventFd::new(libc::EFD_NONBLOCK)?); + let update_evt = Arc::new(create_new_eventfd()?); let mut handler = NetIoHandler { - rx: RxVirtio::new(rx_queue, rx_queue_evt, recv_evt), + rx: RxVirtio::new(rx_queue, rx_queue_evt), tx: TxVirtio::new(tx_queue, tx_queue_evt), tap: self.taps.as_ref().map(|t| t[index].clone()), tap_fd: -1, @@ -1596,9 +1679,15 @@ impl VirtioDevice for Net { Ok(()) } - fn update_config(&mut self, dev_config: Option>) -> Result<()> { - if let Some(conf) = dev_config { - self.net_cfg = conf + // configs[0]: NetDevcfg. configs[1]: NetworkInterfaceConfig. + fn update_config(&mut self, dev_config: Vec>) -> Result<()> { + if !dev_config.is_empty() { + self.netdev_cfg = dev_config[0] + .as_any() + .downcast_ref::() + .unwrap() + .clone(); + self.net_cfg = dev_config[1] .as_any() .downcast_ref::() .unwrap() @@ -1703,16 +1792,16 @@ mod tests { #[test] fn test_net_init() { // test net new method - let mut net = Net::new(NetworkInterfaceConfig::default()); + let mut net = Net::new(NetworkInterfaceConfig::default(), NetDevcfg::default()); assert_eq!(net.base.device_features, 0); assert_eq!(net.base.driver_features, 0); assert_eq!(net.taps.is_none(), true); assert_eq!(net.senders.is_none(), true); assert_eq!(net.net_cfg.mac.is_none(), true); - assert_eq!(net.net_cfg.tap_fds.is_none(), true); - assert_eq!(net.net_cfg.vhost_type.is_none(), true); - assert_eq!(net.net_cfg.vhost_fds.is_none(), true); + assert_eq!(net.netdev_cfg.tap_fds.is_none(), true); + assert!(net.netdev_cfg.vhost_type().is_none()); + assert_eq!(net.netdev_cfg.vhost_fds.is_none(), true); // test net realize method net.realize().unwrap(); @@ -1864,7 +1953,7 @@ mod tests { #[test] fn test_iothread() { - let mut net = Net::new(NetworkInterfaceConfig::default()); + let mut net = Net::new(NetworkInterfaceConfig::default(), NetDevcfg::default()); net.net_cfg.iothread = Some("iothread".to_string()); if let Err(err) = net.realize() { let err_msg = format!( diff --git a/virtio/src/device/rng.rs b/virtio/src/device/rng.rs index c441052..9f1007e 100644 --- a/virtio/src/device/rng.rs +++ b/virtio/src/device/rng.rs @@ -18,7 +18,8 @@ use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use log::error; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; @@ -30,7 +31,7 @@ use crate::{ }; use address_space::AddressSpace; use machine_manager::{ - config::{RngConfig, DEFAULT_VIRTQUEUE_SIZE}, + config::{get_pci_df, valid_id, ConfigError, RngObjConfig, DEFAULT_VIRTQUEUE_SIZE}, event_loop::EventLoop, event_loop::{register_event_helper, unregister_event_helper}, }; @@ -46,6 +47,62 @@ use util::loop_context::{ const QUEUE_NUM_RNG: usize = 1; const RNG_SIZE_MAX: u32 = 1 << 20; +const MIN_BYTES_PER_SEC: u64 = 64; +const MAX_BYTES_PER_SEC: u64 = 1_000_000_000; + +/// Config structure for virtio-rng. +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct RngConfig { + #[arg(long, value_parser = ["virtio-rng-device", "virtio-rng-pci"])] + pub classtype: String, + #[arg(long, default_value = "", value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub rng: String, + #[arg(long, alias = "max-bytes")] + pub max_bytes: Option, + #[arg(long)] + pub period: Option, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long)] + pub multifunction: Option, +} + +impl RngConfig { + pub fn bytes_per_sec(&self) -> Result> { + if self.max_bytes.is_some() != self.period.is_some() { + bail!("\"max_bytes\" and \"period\" should be configured or not configured Simultaneously."); + } + + if let Some(max) = self.max_bytes { + let peri = self.period.unwrap(); + let mul = max + .checked_mul(1000) + .with_context(|| format!("Illegal max-bytes arguments: {:?}", max))?; + let bytes_per_sec = mul + .checked_div(peri) + .with_context(|| format!("Illegal period arguments: {:?}", peri))?; + + if !(MIN_BYTES_PER_SEC..=MAX_BYTES_PER_SEC).contains(&bytes_per_sec) { + return Err(anyhow!(ConfigError::IllegalValue( + "The bytes per second of rng device".to_string(), + MIN_BYTES_PER_SEC, + true, + MAX_BYTES_PER_SEC, + true, + ))); + } + + return Ok(Some(bytes_per_sec)); + } + Ok(None) + } +} + fn get_req_data_size(in_iov: &[ElemIovec]) -> Result { let mut size = 0_u32; for iov in in_iov { @@ -216,34 +273,37 @@ pub struct RngState { pub struct Rng { /// Virtio device base property. base: VirtioBase, - /// Configuration of virtio rng device + /// Configuration of virtio rng device. rng_cfg: RngConfig, + /// Configuration of rng-random. + rngobj_cfg: RngObjConfig, /// The file descriptor of random number generator random_file: Option, } impl Rng { - pub fn new(rng_cfg: RngConfig) -> Self { + pub fn new(rng_cfg: RngConfig, rngobj_cfg: RngObjConfig) -> Self { Rng { base: VirtioBase::new(VIRTIO_TYPE_RNG, QUEUE_NUM_RNG, DEFAULT_VIRTQUEUE_SIZE), rng_cfg, + rngobj_cfg, ..Default::default() } } fn check_random_file(&self) -> Result<()> { - let path = Path::new(&self.rng_cfg.random_file); + let path = Path::new(&self.rngobj_cfg.filename); if !path.exists() { bail!( "The path of random file {} is not existed", - self.rng_cfg.random_file + self.rngobj_cfg.filename ); } if !path.metadata().unwrap().file_type().is_char_device() { bail!( "The type of random file {} is not a character special file", - self.rng_cfg.random_file + self.rngobj_cfg.filename ); } @@ -263,7 +323,7 @@ impl VirtioDevice for Rng { fn realize(&mut self) -> Result<()> { self.check_random_file() .with_context(|| "Failed to check random file")?; - let file = File::open(&self.rng_cfg.random_file) + let file = File::open(&self.rngobj_cfg.filename) .with_context(|| "Failed to open file of random number generator")?; self.random_file = Some(file); self.init_config_features()?; @@ -308,7 +368,7 @@ impl VirtioDevice for Rng { .unwrap() .try_clone() .with_context(|| "Failed to clone random file for virtio rng")?, - leak_bucket: match self.rng_cfg.bytes_per_sec { + leak_bucket: match self.rng_cfg.bytes_per_sec()? { Some(bps) => Some(LeakBucket::new(bps)?), None => None, }, @@ -361,7 +421,7 @@ mod tests { use super::*; use crate::*; use address_space::{AddressSpace, GuestAddress, HostMemMapping, Region}; - use machine_manager::config::{RngConfig, DEFAULT_VIRTQUEUE_SIZE}; + use machine_manager::config::{str_slip_to_clap, VmConfig, DEFAULT_VIRTQUEUE_SIZE}; const VIRTQ_DESC_F_NEXT: u16 = 0x01; const VIRTQ_DESC_F_WRITE: u16 = 0x02; @@ -393,21 +453,60 @@ mod tests { sys_space } + #[test] + fn test_rng_config_cmdline_parse() { + // Test1: Right rng-random. + let mut vm_config = VmConfig::default(); + assert!(vm_config + .add_object("rng-random,id=objrng0,filename=/path/to/random_file") + .is_ok()); + let rngobj_cfg = vm_config.object.rng_object.remove("objrng0").unwrap(); + assert_eq!(rngobj_cfg.filename, "/path/to/random_file"); + + // Test2: virtio-rng-device + let rng_cmd = "virtio-rng-device,rng=objrng0"; + let rng_config = RngConfig::try_parse_from(str_slip_to_clap(rng_cmd, true, false)).unwrap(); + assert_eq!(rng_config.bytes_per_sec().unwrap(), None); + assert_eq!(rng_config.multifunction, None); + + // Test3: virtio-rng-pci. + let rng_cmd = "virtio-rng-pci,bus=pcie.0,addr=0x1,rng=objrng0,max-bytes=1234,period=1000"; + let rng_config = RngConfig::try_parse_from(str_slip_to_clap(rng_cmd, true, false)).unwrap(); + assert_eq!(rng_config.bytes_per_sec().unwrap(), Some(1234)); + assert_eq!(rng_config.bus.unwrap(), "pcie.0"); + assert_eq!(rng_config.addr.unwrap(), (1, 0)); + + // Test4: Illegal max-bytes/period. + let rng_cmd = "virtio-rng-device,rng=objrng0,max-bytes=63,period=1000"; + let rng_config = RngConfig::try_parse_from(str_slip_to_clap(rng_cmd, true, false)).unwrap(); + assert!(rng_config.bytes_per_sec().is_err()); + + let rng_cmd = "virtio-rng-device,rng=objrng0,max-bytes=1000000001,period=1000"; + let rng_config = RngConfig::try_parse_from(str_slip_to_clap(rng_cmd, true, false)).unwrap(); + assert!(rng_config.bytes_per_sec().is_err()); + } + #[test] fn test_rng_init() { - let file = TempFile::new().unwrap(); - let random_file = file.as_path().to_str().unwrap().to_string(); + let rngobj_config = RngObjConfig { + classtype: "rng-random".to_string(), + id: "rng0".to_string(), + filename: "".to_string(), + }; let rng_config = RngConfig { - id: "".to_string(), - random_file: random_file.clone(), - bytes_per_sec: Some(64), + classtype: "virtio-rng-pci".to_string(), + rng: "rng0".to_string(), + max_bytes: Some(64), + period: Some(1000), + bus: Some("pcie.0".to_string()), + addr: Some((3, 0)), + ..Default::default() }; - let rng = Rng::new(rng_config); + let rng = Rng::new(rng_config, rngobj_config); assert!(rng.random_file.is_none()); assert_eq!(rng.base.driver_features, 0_u64); assert_eq!(rng.base.device_features, 0_u64); - assert_eq!(rng.rng_cfg.random_file, random_file); - assert_eq!(rng.rng_cfg.bytes_per_sec, Some(64)); + assert_eq!(rng.rng_cfg.bytes_per_sec().unwrap().unwrap(), 64); assert_eq!(rng.queue_num(), QUEUE_NUM_RNG); assert_eq!(rng.queue_size_max(), DEFAULT_VIRTQUEUE_SIZE); @@ -416,18 +515,9 @@ mod tests { #[test] fn test_rng_features() { - let random_file = TempFile::new() - .unwrap() - .as_path() - .to_str() - .unwrap() - .to_string(); - let rng_config = RngConfig { - id: "".to_string(), - random_file, - bytes_per_sec: Some(64), - }; - let mut rng = Rng::new(rng_config); + let rng_config = RngConfig::default(); + let rngobj_cfg = RngObjConfig::default(); + let mut rng = Rng::new(rng_config, rngobj_cfg); // If the device feature is 0, all driver features are not supported. rng.base.device_features = 0; diff --git a/virtio/src/device/scsi_cntlr.rs b/virtio/src/device/scsi_cntlr.rs index d3d6cf4..8f301df 100644 --- a/virtio/src/device/scsi_cntlr.rs +++ b/virtio/src/device/scsi_cntlr.rs @@ -17,6 +17,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; +use clap::Parser; use log::{error, info, warn}; use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; @@ -32,17 +33,23 @@ use devices::ScsiBus::{ ScsiBus, ScsiRequest, ScsiRequestOps, ScsiSense, ScsiXferMode, CHECK_CONDITION, EMULATE_SCSI_OPS, SCSI_CMD_BUF_SIZE, SCSI_SENSE_INVALID_OPCODE, }; -use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; -use machine_manager::{ - config::{ScsiCntlrConfig, VIRTIO_SCSI_MAX_LUN, VIRTIO_SCSI_MAX_TARGET}, - event_loop::EventLoop, +use machine_manager::config::{ + get_pci_df, parse_bool, valid_block_device_virtqueue_size, valid_id, MAX_VIRTIO_QUEUE, }; +use machine_manager::event_loop::{register_event_helper, unregister_event_helper, EventLoop}; use util::aio::Iovec; use util::byte_code::ByteCode; use util::loop_context::{ read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation, }; +/// According to Virtio Spec. +/// Max_channel should be 0. +/// Max_target should be less than or equal to 255. +const VIRTIO_SCSI_MAX_TARGET: u16 = 255; +/// Max_lun should be less than or equal to 16383 (2^14 - 1). +const VIRTIO_SCSI_MAX_LUN: u32 = 16383; + /// Virtio Scsi Controller has 1 ctrl queue, 1 event queue and at least 1 cmd queue. const SCSI_CTRL_QUEUE_NUM: usize = 1; const SCSI_EVENT_QUEUE_NUM: usize = 1; @@ -88,6 +95,27 @@ const VIRTIO_SCSI_S_BAD_TARGET: u8 = 3; /// with a response equal to VIRTIO_SCSI_S_FAILURE. const VIRTIO_SCSI_S_FAILURE: u8 = 9; +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct ScsiCntlrConfig { + #[arg(long, value_parser = ["virtio-scsi-pci"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: String, + #[arg(long, value_parser = get_pci_df)] + pub addr: (u8, u8), + #[arg(long, value_parser = parse_bool)] + pub multifunction: Option, + #[arg(long, alias = "num-queues", value_parser = clap::value_parser!(u32).range(1..=MAX_VIRTIO_QUEUE as i64))] + pub num_queues: Option, + #[arg(long)] + pub iothread: Option, + #[arg(long, alias = "queue-size", default_value = "256", value_parser = valid_block_device_virtqueue_size)] + pub queue_size: u16, +} + #[repr(C, packed)] #[derive(Copy, Clone, Debug, Default)] struct VirtioScsiConfig { @@ -121,7 +149,8 @@ pub struct ScsiCntlr { impl ScsiCntlr { pub fn new(config: ScsiCntlrConfig) -> ScsiCntlr { // Note: config.queues <= MAX_VIRTIO_QUEUE(32). - let queue_num = config.queues as usize + SCSI_CTRL_QUEUE_NUM + SCSI_EVENT_QUEUE_NUM; + let queue_num = + config.num_queues.unwrap() as usize + SCSI_CTRL_QUEUE_NUM + SCSI_EVENT_QUEUE_NUM; let queue_size = config.queue_size; Self { @@ -164,16 +193,15 @@ impl VirtioDevice for ScsiCntlr { } fn init_config_features(&mut self) -> Result<()> { - self.config_space.num_queues = self.config.queues; self.config_space.max_sectors = 0xFFFF_u32; // cmd_per_lun: maximum number of linked commands can be sent to one LUN. 32bit. self.config_space.cmd_per_lun = 128; // seg_max: queue size - 2, 32 bit. self.config_space.seg_max = self.queue_size_max() as u32 - 2; self.config_space.max_target = VIRTIO_SCSI_MAX_TARGET; - self.config_space.max_lun = VIRTIO_SCSI_MAX_LUN as u32; + self.config_space.max_lun = VIRTIO_SCSI_MAX_LUN; // num_queues: request queues number. - self.config_space.num_queues = self.config.queues; + self.config_space.num_queues = self.config.num_queues.unwrap(); self.base.device_features |= (1_u64 << VIRTIO_F_VERSION_1) | (1_u64 << VIRTIO_F_RING_EVENT_IDX) @@ -965,3 +993,55 @@ pub fn scsi_cntlr_create_scsi_bus( locked_scsi_cntlr.bus = Some(Arc::new(Mutex::new(bus))); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_scsi_cntlr_config_cmdline_parser() { + // Test1: Right. + let cmdline1 = "virtio-scsi-pci,id=scsi0,bus=pcie.0,addr=0x3,multifunction=on,iothread=iothread1,num-queues=3,queue-size=128"; + let device_cfg = + ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline1, true, false)).unwrap(); + assert_eq!(device_cfg.id, "scsi0"); + assert_eq!(device_cfg.bus, "pcie.0"); + assert_eq!(device_cfg.addr, (3, 0)); + assert_eq!(device_cfg.multifunction, Some(true)); + assert_eq!(device_cfg.iothread.unwrap(), "iothread1"); + assert_eq!(device_cfg.num_queues.unwrap(), 3); + assert_eq!(device_cfg.queue_size, 128); + + // Test2: Default value. + let cmdline2 = "virtio-scsi-pci,id=scsi0,bus=pcie.0,addr=0x3.0x1"; + let device_cfg = + ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline2, true, false)).unwrap(); + assert_eq!(device_cfg.addr, (3, 1)); + assert_eq!(device_cfg.multifunction, None); + assert_eq!(device_cfg.num_queues, None); + assert_eq!(device_cfg.queue_size, 256); + + // Test3: Illegal value. + let cmdline3 = "virtio-scsi-pci,id=scsi0,bus=pcie.0,addr=0x3.0x1,num-queues=33"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + let cmdline3 = "virtio-scsi-pci,id=scsi0,bus=pcie.0,addr=0x3.0x1,queue-size=1025"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + let cmdline3 = "virtio-scsi-pci,id=scsi0,bus=pcie.0,addr=0x3.0x1,queue-size=65"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false)); + assert!(result.is_err()); + + // Test4: Missing necessary parameters. + let cmdline4 = "virtio-scsi-pci,id=scsi0"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "virtio-scsi-pci,bus=pcie.0,addr=0x3.0x1"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + let cmdline4 = "virtio-scsi-pci,id=scsi0,addr=0x3.0x1"; + let result = ScsiCntlrConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false)); + assert!(result.is_err()); + } +} diff --git a/virtio/src/device/serial.rs b/virtio/src/device/serial.rs index 7c6864f..102a3ed 100644 --- a/virtio/src/device/serial.rs +++ b/virtio/src/device/serial.rs @@ -20,18 +20,19 @@ use std::{cmp, usize}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; use log::{error, info, warn}; +use machine_manager::config::ChardevConfig; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; use crate::{ - gpa_hva_iovec_map, iov_discard_front, iov_to_buf, read_config_default, report_virtio_error, - Element, Queue, VirtioBase, VirtioDevice, VirtioError, VirtioInterrupt, VirtioInterruptType, + gpa_hva_iovec_map, iov_to_buf, read_config_default, report_virtio_error, Element, Queue, + VirtioBase, VirtioDevice, VirtioError, VirtioInterrupt, VirtioInterruptType, VIRTIO_CONSOLE_F_MULTIPORT, VIRTIO_CONSOLE_F_SIZE, VIRTIO_F_VERSION_1, VIRTIO_TYPE_CONSOLE, }; use address_space::AddressSpace; use chardev_backend::chardev::{Chardev, ChardevNotifyDevice, ChardevStatus, InputReceiver}; use machine_manager::{ - config::{ChardevType, VirtioSerialInfo, VirtioSerialPort, DEFAULT_VIRTQUEUE_SIZE}, + config::{ChardevType, VirtioSerialInfo, VirtioSerialPortCfg, DEFAULT_VIRTQUEUE_SIZE}, event_loop::EventLoop, event_loop::{register_event_helper, unregister_event_helper}, }; @@ -263,8 +264,8 @@ impl VirtioDevice for Serial { 0 => 0, 1 => continue, _ => queue_id - 1, - }; - let port = find_port_by_nr(&self.ports, nr as u32); + } as u32; + let port = find_port_by_nr(&self.ports, nr); let handler = SerialPortHandler { input_queue: queues[queue_id * 2].clone(), input_queue_evt: queue_evts[queue_id * 2].clone(), @@ -275,6 +276,7 @@ impl VirtioDevice for Serial { driver_features: self.base.driver_features, device_broken: self.base.broken.clone(), port: port.clone(), + nr, }; let handler_h = Arc::new(Mutex::new(handler)); let notifiers = EventNotifierHelper::internal_notifiers(handler_h.clone()); @@ -353,17 +355,21 @@ pub struct SerialPort { } impl SerialPort { - pub fn new(port_cfg: VirtioSerialPort) -> Self { + pub fn new(port_cfg: VirtioSerialPortCfg, chardev_cfg: ChardevConfig) -> Self { // Console is default host connected. And pty chardev has opened by default in realize() // function. - let host_connected = port_cfg.is_console || port_cfg.chardev.backend == ChardevType::Pty; + let is_console = matches!(port_cfg.classtype.as_str(), "virtconsole"); + let mut host_connected = is_console; + if let ChardevType::Pty { .. } = chardev_cfg.classtype { + host_connected = true; + } SerialPort { name: Some(port_cfg.id), paused: false, - chardev: Arc::new(Mutex::new(Chardev::new(port_cfg.chardev))), - nr: port_cfg.nr, - is_console: port_cfg.is_console, + chardev: Arc::new(Mutex::new(Chardev::new(chardev_cfg))), + nr: port_cfg.nr.unwrap(), + is_console, guest_connected: false, host_connected, ctrl_handler: None, @@ -412,6 +418,7 @@ struct SerialPortHandler { /// Virtio serial device is broken or not. device_broken: Arc, port: Option>>, + nr: u32, } /// Handler for queues which are used for control. @@ -431,7 +438,7 @@ impl SerialPortHandler { fn output_handle(&mut self) { trace::virtio_receive_request("Serial".to_string(), "to IO".to_string()); self.output_handle_internal().unwrap_or_else(|e| { - error!("Port handle output error: {:?}", e); + error!("Port {} handle output error: {:?}", self.nr, e); report_virtio_error( self.interrupt_cb.clone(), self.driver_features, @@ -453,6 +460,14 @@ impl SerialPortHandler { let mut queue_lock = self.output_queue.lock().unwrap(); loop { + if let Some(port) = self.port.as_ref() { + let locked_port = port.lock().unwrap(); + let locked_cdev = locked_port.chardev.lock().unwrap(); + if locked_cdev.outbuf_is_full() { + break; + } + } + let elem = queue_lock .vring .pop_avail(&self.mem_space, self.driver_features)?; @@ -462,23 +477,24 @@ impl SerialPortHandler { // Discard requests when there is no port using this queue. Popping elements without // processing means discarding the request. - if self.port.is_some() { - let mut iovec = elem.out_iovec; - let mut iovec_size = Element::iovec_size(&iovec); - while iovec_size > 0 { - let mut buffer = [0_u8; BUF_SIZE]; - let size = iov_to_buf(&self.mem_space, &iovec, &mut buffer)? as u64; - - self.write_chardev_msg(&buffer, size as usize); - - iovec = iov_discard_front(&mut iovec, size) - .unwrap_or_default() - .to_vec(); - // Safety: iovec follows the iov_discard_front operation and - // iovec_size always equals Element::iovec_size(&iovec). - iovec_size -= size; - trace::virtio_serial_output_data(iovec_size, size); + if let Some(port) = self.port.as_ref() { + let iovec = elem.out_iovec; + let iovec_size = Element::iovec_size(&iovec); + let mut buf = vec![0u8; iovec_size as usize]; + let size = iov_to_buf(&self.mem_space, &iovec, &mut buf[..])? as u64; + + let locked_port = port.lock().unwrap(); + if locked_port.host_connected { + if let Err(e) = locked_port + .chardev + .lock() + .unwrap() + .fill_outbuf(buf, Some(self.output_queue_evt.clone())) + { + error!("Failed to append elem buffer to chardev with error {:?}", e); + } } + trace::virtio_serial_output_data(iovec_size, size); } queue_lock @@ -509,30 +525,6 @@ impl SerialPortHandler { Ok(()) } - fn write_chardev_msg(&self, buffer: &[u8], write_len: usize) { - let port_locked = self.port.as_ref().unwrap().lock().unwrap(); - // Discard output buffer if this port's chardev is not connected. - if !port_locked.host_connected { - return; - } - - if let Some(output) = &mut port_locked.chardev.lock().unwrap().output { - let mut locked_output = output.lock().unwrap(); - // To do: - // If the buffer is not fully written to chardev, the incomplete part will be discarded. - // This may occur when chardev is abnormal. Consider optimizing this logic in the - // future. - if let Err(e) = locked_output.write_all(&buffer[..write_len]) { - error!("Failed to write msg to chardev: {:?}", e); - } - if let Err(e) = locked_output.flush() { - error!("Failed to flush msg to chardev: {:?}", e); - } - } else { - error!("Failed to get output fd"); - }; - } - fn get_input_avail_bytes(&mut self, max_size: usize) -> usize { let port = self.port.as_ref(); if port.is_none() || !port.unwrap().lock().unwrap().guest_connected { @@ -552,7 +544,10 @@ impl SerialPortHandler { { Ok(n) => n, Err(_) => { - warn!("error occurred while getting available bytes of vring"); + warn!( + "error occurred while port {} getting available bytes of vring", + self.nr + ); 0 } } @@ -697,7 +692,7 @@ impl EventNotifierHelper for SerialPortHandler { impl InputReceiver for SerialPortHandler { fn receive(&mut self, buffer: &[u8]) { self.input_handle_internal(buffer).unwrap_or_else(|e| { - error!("Port handle input error: {:?}", e); + error!("Port {} handle input error: {:?}", self.nr, e); report_virtio_error( self.interrupt_cb.clone(), self.driver_features, @@ -1015,18 +1010,15 @@ impl ChardevNotifyDevice for SerialPort { mod tests { pub use super::*; - use machine_manager::config::PciBdf; - #[test] fn test_set_driver_features() { let mut serial = Serial::new(VirtioSerialInfo { + classtype: "virtio-serial-pci".to_string(), id: "serial".to_string(), - pci_bdf: Some(PciBdf { - bus: "pcie.0".to_string(), - addr: (0, 0), - }), - multifunction: false, + multifunction: Some(false), max_ports: 31, + bus: Some("pcie.0".to_string()), + addr: Some((0, 0)), }); // If the device feature is 0, all driver features are not supported. @@ -1089,13 +1081,12 @@ mod tests { fn test_read_config() { let max_ports: u8 = 31; let serial = Serial::new(VirtioSerialInfo { + classtype: "virtio-serial-pci".to_string(), id: "serial".to_string(), - pci_bdf: Some(PciBdf { - bus: "pcie.0".to_string(), - addr: (0, 0), - }), - multifunction: false, + multifunction: Some(false), max_ports: max_ports as u32, + bus: Some("pcie.0".to_string()), + addr: Some((0, 0)), }); // The offset of configuration that needs to be read exceeds the maximum. diff --git a/virtio/src/lib.rs b/virtio/src/lib.rs index 9ef9dde..33edacf 100644 --- a/virtio/src/lib.rs +++ b/virtio/src/lib.rs @@ -24,6 +24,7 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` pub mod device; pub mod error; @@ -33,11 +34,11 @@ mod queue; mod transport; pub use device::balloon::*; -pub use device::block::{Block, BlockState, VirtioBlkConfig}; +pub use device::block::{Block, BlockState, VirtioBlkConfig, VirtioBlkDevConfig}; #[cfg(feature = "virtio_gpu")] pub use device::gpu::*; pub use device::net::*; -pub use device::rng::{Rng, RngState}; +pub use device::rng::{Rng, RngConfig, RngState}; pub use device::scsi_cntlr as ScsiCntlr; pub use device::serial::{find_port_by_nr, get_max_nr, Serial, SerialPort, VirtioSerialState}; pub use error::VirtioError; @@ -57,7 +58,9 @@ use anyhow::{anyhow, bail, Context, Result}; use log::{error, warn}; use vmm_sys_util::eventfd::EventFd; -use address_space::AddressSpace; +use address_space::{AddressSpace, RegionCache}; +use devices::pci::register_pcidevops_type; +use devices::sysbus::register_sysbusdevops_type; use machine_manager::config::ConfigCheck; use migration_derive::ByteCode; use util::aio::{mem_to_buf, Iovec}; @@ -729,8 +732,9 @@ pub trait VirtioDevice: Send + AsAny { /// /// # Arguments /// - /// * `_file_path` - The related backend file path. - fn update_config(&mut self, _dev_config: Option>) -> Result<()> { + /// * `_configs` - The related configs for device. + /// eg: DriveConfig and VirtioBlkDevConfig for virtio blk device. + fn update_config(&mut self, _configs: Vec>) -> Result<()> { bail!("Unsupported to update configuration") } @@ -789,12 +793,21 @@ pub fn report_virtio_error( /// Read iovec to buf and return the read number of bytes. pub fn iov_to_buf(mem_space: &AddressSpace, iovec: &[ElemIovec], buf: &mut [u8]) -> Result { + iov_to_buf_by_cache(mem_space, &None, iovec, buf) +} + +pub fn iov_to_buf_by_cache( + mem_space: &AddressSpace, + cache: &Option, + iovec: &[ElemIovec], + buf: &mut [u8], +) -> Result { let mut start: usize = 0; let mut end: usize = 0; for iov in iovec { let mut addr_map = Vec::new(); - mem_space.get_address_map(iov.addr, iov.len as u64, &mut addr_map)?; + mem_space.get_address_map(cache, iov.addr, iov.len as u64, &mut addr_map)?; for addr in addr_map.into_iter() { end = cmp::min(start + addr.iov_len as usize, buf.len()); mem_to_buf(&mut buf[start..end], addr.iov_base)?; @@ -838,14 +851,30 @@ pub fn iov_discard_back(iovec: &mut [ElemIovec], mut size: u64) -> Option<&mut [ fn gpa_hva_iovec_map( gpa_elemiovec: &[ElemIovec], mem_space: &AddressSpace, +) -> Result<(u64, Vec)> { + gpa_hva_iovec_map_by_cache(gpa_elemiovec, mem_space, &None) +} + +fn gpa_hva_iovec_map_by_cache( + gpa_elemiovec: &[ElemIovec], + mem_space: &AddressSpace, + cache: &Option, ) -> Result<(u64, Vec)> { let mut iov_size = 0; let mut hva_iovec = Vec::with_capacity(gpa_elemiovec.len()); for elem in gpa_elemiovec.iter() { - mem_space.get_address_map(elem.addr, elem.len as u64, &mut hva_iovec)?; + mem_space.get_address_map(cache, elem.addr, elem.len as u64, &mut hva_iovec)?; iov_size += elem.len as u64; } Ok((iov_size, hva_iovec)) } + +pub fn virtio_register_sysbusdevops_type() -> Result<()> { + register_sysbusdevops_type::() +} + +pub fn virtio_register_pcidevops_type() -> Result<()> { + register_pcidevops_type::() +} diff --git a/virtio/src/queue/mod.rs b/virtio/src/queue/mod.rs index 7f581a0..1e612b2 100644 --- a/virtio/src/queue/mod.rs +++ b/virtio/src/queue/mod.rs @@ -21,6 +21,7 @@ use vmm_sys_util::eventfd::EventFd; use address_space::{AddressSpace, GuestAddress, RegionCache}; use machine_manager::config::DEFAULT_VIRTQUEUE_SIZE; +use util::loop_context::create_new_eventfd; /// Split Virtqueue. pub const QUEUE_TYPE_SPLIT_VRING: u16 = 1; @@ -226,7 +227,7 @@ impl NotifyEventFds { pub fn new(queue_num: usize) -> Self { let mut events = Vec::new(); for _i in 0..queue_num { - events.push(Arc::new(EventFd::new(libc::EFD_NONBLOCK).unwrap())); + events.push(Arc::new(create_new_eventfd().unwrap())); } NotifyEventFds { events } diff --git a/virtio/src/transport/virtio_mmio.rs b/virtio/src/transport/virtio_mmio.rs index 260c0de..77d0ca0 100644 --- a/virtio/src/transport/virtio_mmio.rs +++ b/virtio/src/transport/virtio_mmio.rs @@ -15,7 +15,7 @@ use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; -use log::{debug, error, warn}; +use log::{debug, error, info, warn}; use vmm_sys_util::eventfd::EventFd; use crate::error::VirtioError; @@ -26,13 +26,14 @@ use crate::{ QUEUE_TYPE_PACKED_VRING, VIRTIO_F_RING_PACKED, VIRTIO_MMIO_INT_CONFIG, VIRTIO_MMIO_INT_VRING, }; use address_space::{AddressRange, AddressSpace, GuestAddress, RegionIoEventFd}; -use devices::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType, SysRes}; +use devices::sysbus::{SysBus, SysBusDevBase, SysBusDevOps, SysBusDevType}; use devices::{Device, DeviceBase}; #[cfg(target_arch = "x86_64")] use machine_manager::config::{BootSource, Param}; use migration::{DeviceStateDesc, FieldDesc, MigrationHook, MigrationManager, StateTransfer}; use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; +use util::loop_context::create_new_eventfd; /// Registers of virtio-mmio device refer to Virtio Spec. /// Magic value - Read Only. @@ -105,7 +106,7 @@ impl HostNotifyInfo { fn new(queue_num: usize) -> Self { let mut events = Vec::new(); for _i in 0..queue_num { - events.push(Arc::new(EventFd::new(libc::EFD_NONBLOCK).unwrap())); + events.push(Arc::new(create_new_eventfd().unwrap())); } HostNotifyInfo { events } @@ -134,14 +135,22 @@ pub struct VirtioMmioDevice { } impl VirtioMmioDevice { - pub fn new(mem_space: &Arc, device: Arc>) -> Self { + pub fn new( + mem_space: &Arc, + name: String, + device: Arc>, + ) -> Self { let device_clone = device.clone(); let queue_num = device_clone.lock().unwrap().queue_num(); VirtioMmioDevice { base: SysBusDevBase { + base: DeviceBase { + id: name, + hotpluggable: false, + }, dev_type: SysBusDevType::VirtioMmio, - interrupt_evt: Some(Arc::new(EventFd::new(libc::EFD_NONBLOCK).unwrap())), + interrupt_evt: Some(Arc::new(create_new_eventfd().unwrap())), ..Default::default() }, device, @@ -161,7 +170,7 @@ impl VirtioMmioDevice { if region_base >= sysbus.mmio_region.1 { bail!("Mmio region space exhausted."); } - self.set_sys_resource(sysbus, region_base, region_size)?; + self.set_sys_resource(sysbus, region_base, region_size, "VirtioMmio")?; self.assign_interrupt_cb(); self.device .lock() @@ -170,7 +179,7 @@ impl VirtioMmioDevice { .with_context(|| "Failed to realize virtio.")?; let dev = Arc::new(Mutex::new(self)); - sysbus.attach_device(&dev, region_base, region_size, "VirtioMmio")?; + sysbus.attach_device(&dev)?; #[cfg(target_arch = "x86_64")] bs.lock().unwrap().kernel_cmdline.push(Param { @@ -188,7 +197,7 @@ impl VirtioMmioDevice { /// Activate the virtio device, this function is called by vcpu thread when frontend /// virtio driver is ready and write `DRIVER_OK` to backend. fn activate(&mut self) -> Result<()> { - trace::virtio_tpt_common("activate", &self.base.base.id); + info!("func: activate, id: {:?}", &self.base.base.id); let mut locked_dev = self.device.lock().unwrap(); let queue_num = locked_dev.queue_num(); let queue_type = locked_dev.queue_type(); @@ -409,10 +418,8 @@ impl SysBusDevOps for VirtioMmioDevice { Ok(v) => v, Err(ref e) => { error!( - "Failed to read mmio register {}, type: {}, {:?}", - offset, - self.device.lock().unwrap().device_type(), - e, + "Failed to read mmio register {:#x}, device: {}, {:?}", + offset, self.base.base.id, e, ); return false; } @@ -427,9 +434,9 @@ impl SysBusDevOps for VirtioMmioDevice { .read_config(offset - 0x100, data) { error!( - "Failed to read virtio-dev config space {} type: {} {:?}", + "Failed to read virtio-dev config space {:#x} device: {}, {:?}", offset - 0x100, - self.device.lock().unwrap().device_type(), + self.base.base.id, e, ); return false; @@ -437,9 +444,8 @@ impl SysBusDevOps for VirtioMmioDevice { } _ => { warn!( - "Failed to read mmio register: overflows, offset is 0x{:x}, type: {}", - offset, - self.device.lock().unwrap().device_type(), + "Failed to read mmio register: overflows, offset is {:#x}, device: {}", + offset, self.base.base.id ); } }; @@ -454,10 +460,8 @@ impl SysBusDevOps for VirtioMmioDevice { let value = LittleEndian::read_u32(data); if let Err(ref e) = self.write_common_config(offset, value) { error!( - "Failed to write mmio register {}, type: {}, {:?}", - offset, - self.device.lock().unwrap().device_type(), - e, + "Failed to write mmio register {:#x}, device: {}, {:?}", + offset, self.base.base.id, e, ); return false; } @@ -474,9 +478,8 @@ impl SysBusDevOps for VirtioMmioDevice { drop(locked_dev); if let Err(ref e) = self.activate() { error!( - "Failed to activate dev, type: {}, {:?}", - self.device.lock().unwrap().device_type(), - e, + "Failed to activate dev, device: {}, {:?}", + self.base.base.id, e, ); return false; } @@ -488,26 +491,25 @@ impl SysBusDevOps for VirtioMmioDevice { if locked_device.check_device_status(CONFIG_STATUS_DRIVER, CONFIG_STATUS_FAILED) { if let Err(ref e) = locked_device.write_config(offset - 0x100, data) { error!( - "Failed to write virtio-dev config space {}, type: {}, {:?}", + "Failed to write virtio-dev config space {:#x}, device: {}, {:?}", offset - 0x100, - locked_device.device_type(), + self.base.base.id, e, ); return false; } } else { - error!("Failed to write virtio-dev config space: driver is not ready 0x{:X}, type: {}", + error!("Failed to write virtio-dev config space: driver is not ready {:#x}, device: {}", locked_device.device_status(), - locked_device.device_type(), + self.base.base.id, ); return false; } } _ => { warn!( - "Failed to write mmio register: overflows, offset is 0x{:x} type: {}", - offset, - self.device.lock().unwrap().device_type(), + "Failed to write mmio register: overflows, offset is {:#x} device: {}", + offset, self.base.base.id, ); return false; } @@ -528,10 +530,6 @@ impl SysBusDevOps for VirtioMmioDevice { } ret } - - fn get_sys_resource_mut(&mut self) -> Option<&mut SysRes> { - Some(&mut self.base.res) - } } impl acpi::AmlBuilder for VirtioMmioDevice { @@ -596,7 +594,7 @@ impl MigrationHook for VirtioMmioDevice { } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use crate::{ check_config_space_rw, read_config_default, VirtioBase, QUEUE_TYPE_SPLIT_VRING, @@ -604,7 +602,7 @@ mod tests { }; use address_space::{AddressSpace, GuestAddress, HostMemMapping, Region}; - fn address_space_init() -> Arc { + pub fn address_space_init() -> Arc { let root = Region::init_container_region(1 << 36, "sysmem"); let sys_space = AddressSpace::new(root, "sysmem", None).unwrap(); let host_mmap = Arc::new( @@ -699,12 +697,22 @@ mod tests { } } - #[test] - fn test_virtio_mmio_device_new() { + fn virtio_mmio_test_init() -> (Arc>, VirtioMmioDevice) { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); + let sys_space = address_space_init(); - let virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let virtio_mmio_device = VirtioMmioDevice::new( + &sys_space, + "test_virtio_mmio_device".to_string(), + virtio_device.clone(), + ); + + (virtio_device, virtio_mmio_device) + } + #[test] + fn test_virtio_mmio_device_new() { + let (virtio_device, virtio_mmio_device) = virtio_mmio_test_init(); let locked_device = virtio_device.lock().unwrap(); assert_eq!(locked_device.device_activated(), false); assert_eq!( @@ -721,9 +729,7 @@ mod tests { #[test] fn test_virtio_mmio_device_read_01() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // read the register of magic value @@ -780,9 +786,7 @@ mod tests { #[test] fn test_virtio_mmio_device_read_02() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // read the register representing max size of the queue @@ -872,9 +876,7 @@ mod tests { #[test] fn test_virtio_mmio_device_read_03() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // read the configuration atomic value @@ -920,9 +922,7 @@ mod tests { #[test] fn test_virtio_mmio_device_write_01() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // write the selector for device features @@ -1034,9 +1034,7 @@ mod tests { #[test] fn test_virtio_mmio_device_write_02() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // write the ready status of queue @@ -1101,9 +1099,7 @@ mod tests { #[test] fn test_virtio_mmio_device_write_03() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); // write the low 32bit of queue's descriptor table address @@ -1226,9 +1222,7 @@ mod tests { #[test] fn test_virtio_mmio_device_write_04() { - let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_space = address_space_init(); - let mut virtio_mmio_device = VirtioMmioDevice::new(&sys_space, virtio_device.clone()); + let (virtio_device, mut virtio_mmio_device) = virtio_mmio_test_init(); let addr = GuestAddress(0); virtio_mmio_device.assign_interrupt_cb(); diff --git a/virtio/src/transport/virtio_pci.rs b/virtio/src/transport/virtio_pci.rs index aa27db4..111ae3d 100644 --- a/virtio/src/transport/virtio_pci.rs +++ b/virtio/src/transport/virtio_pci.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, Mutex, Weak}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; -use log::{debug, error, warn}; +use log::{debug, error, info, warn}; use vmm_sys_util::eventfd::EventFd; #[cfg(feature = "virtio_gpu")] @@ -316,6 +316,8 @@ pub struct VirtioPciDevice { multi_func: bool, /// If the device need to register irqfd. need_irqfd: bool, + /// Device activation error + activate_err: bool, } impl VirtioPciDevice { @@ -326,6 +328,7 @@ impl VirtioPciDevice { device: Arc>, parent_bus: Weak>, multi_func: bool, + need_irqfd: bool, ) -> Self { let queue_num = device.lock().unwrap().queue_num(); VirtioPciDevice { @@ -342,14 +345,11 @@ impl VirtioPciDevice { notify_eventfds: Arc::new(NotifyEventFds::new(queue_num)), interrupt_cb: None, multi_func, - need_irqfd: false, + need_irqfd, + activate_err: false, } } - pub fn enable_need_irqfd(&mut self) { - self.need_irqfd = true; - } - fn assign_interrupt_cb(&mut self) { let locked_dev = self.device.lock().unwrap(); let virtio_base = locked_dev.virtio_base(); @@ -390,7 +390,9 @@ impl VirtioPciDevice { let mut locked_msix = cloned_msix.lock().unwrap(); if locked_msix.enabled { - locked_msix.notify(vector, dev_id.load(Ordering::Acquire)); + if vector != INVALID_VECTOR_NUM { + locked_msix.notify(vector, dev_id.load(Ordering::Acquire)); + } } else { cloned_intx.lock().unwrap().notify(1); } @@ -431,8 +433,8 @@ impl VirtioPciDevice { Ok(write_start) } - fn activate_device(&self) -> bool { - trace::virtio_tpt_common("activate_device", &self.base.base.id); + fn activate_device(&mut self) -> bool { + info!("func: activate_device, id: {:?}", &self.base.base.id); let mut locked_dev = self.device.lock().unwrap(); if locked_dev.device_activated() { return true; @@ -457,7 +459,10 @@ impl VirtioPciDevice { } let queue = Queue::new(*q_config, queue_type).unwrap(); if q_config.ready && !queue.is_valid(&self.sys_mem) { - error!("Failed to activate device: Invalid queue"); + error!( + "Failed to activate device {}: Invalid queue", + self.base.base.id + ); return false; } let arc_queue = Arc::new(Mutex::new(queue)); @@ -479,12 +484,18 @@ impl VirtioPciDevice { } let call_evts = NotifyEventFds::new(queue_num); if let Err(e) = locked_dev.set_guest_notifiers(&call_evts.events) { - error!("Failed to set guest notifiers, error is {:?}", e); + error!( + "Failed to set guest notifiers, device {} error is {:?}", + self.base.base.id, e + ); return false; } drop(locked_dev); if !self.queues_register_irqfd(&call_evts.events) { - error!("Failed to register queues irqfd."); + error!( + "Failed to register queues irqfd for device {}", + self.base.base.id + ); return false; } locked_dev = self.device.lock().unwrap(); @@ -496,40 +507,47 @@ impl VirtioPciDevice { self.interrupt_cb.clone().unwrap(), queue_evts, ) { - error!("Failed to activate device, error is {:?}", e); + // log only first activation error + if !self.activate_err { + self.activate_err = true; + error!( + "Failed to activate device {}, error is {:?}", + self.base.base.id, e + ); + } return false; } + self.activate_err = false; locked_dev.set_device_activated(true); true } - fn deactivate_device(&self) -> bool { - trace::virtio_tpt_common("deactivate_device", &self.base.base.id); + fn deactivate_device(&self) { + info!("func: deactivate_device, id: {:?}", &self.base.base.id); if self.need_irqfd && self.base.config.msix.is_some() { let msix = self.base.config.msix.as_ref().unwrap(); if msix.lock().unwrap().unregister_irqfd().is_err() { - return false; + warn!("unregister_irqfd failed"); } } - + // call deactivate unconditionally, since device can be + // in half initialized state let mut locked_dev = self.device.lock().unwrap(); - if locked_dev.device_activated() { - if let Err(e) = locked_dev.deactivate() { - error!("Failed to deactivate virtio device, error is {:?}", e); - return false; - } - locked_dev.virtio_base_mut().reset(); + if let Err(e) = locked_dev.deactivate() { + error!( + "Failed to deactivate virtio device {}, error is {:?}", + self.base.base.id, e + ); } + locked_dev.virtio_base_mut().reset(); if let Some(intx) = &self.base.config.intx { intx.lock().unwrap().reset(); } if let Some(msix) = &self.base.config.msix { msix.lock().unwrap().clear_pending_vectors(); - } - - true + }; } /// Read data from the common config of virtio device. @@ -621,7 +639,7 @@ impl VirtioPciDevice { } COMMON_GF_REG => { if locked_device.device_status() & CONFIG_STATUS_FEATURES_OK != 0 { - error!("it's not allowed to set features after having been negoiated"); + error!("it's not allowed to set features after having been negoiated for device {}", self.base.base.id); return Ok(()); } let gfeatures_sel = locked_device.gfeatures_sel(); @@ -652,13 +670,16 @@ impl VirtioPciDevice { let features = (locked_device.driver_features(1) as u64) << 32; if !virtio_has_feature(features, VIRTIO_F_VERSION_1) { error!( - "Device is modern only, but the driver not support VIRTIO_F_VERSION_1" + "Device {} is modern only, but the driver not support VIRTIO_F_VERSION_1", self.base.base.id ); return Ok(()); } } if value != 0 && (locked_device.device_status() & !value) != 0 { - error!("Driver must not clear a device status bit"); + error!( + "Driver must not clear a device status bit, device {}", + self.base.base.id + ); return Ok(()); } @@ -688,7 +709,10 @@ impl VirtioPciDevice { .map(|config| config.size = value as u16)?, COMMON_Q_ENABLE_REG => { if value != 1 { - error!("Driver set illegal value for queue_enable {}", value); + error!( + "Driver set illegal value for queue_enable {}, device {}", + value, self.base.base.id + ); return Err(anyhow!(PciError::QueueEnable(value))); } locked_device @@ -924,7 +948,7 @@ impl VirtioPciDevice { }; if let Err(e) = result { error!( - "Failed to access virtio configuration through VirtioPciCfgAccessCap. {:?}", + "Failed to access virtio configuration through VirtioPciCfgAccessCap. device is {}, error is {:?}", self.base.base.id, e ); } @@ -940,7 +964,10 @@ impl VirtioPciDevice { fn queues_register_irqfd(&self, call_fds: &[Arc]) -> bool { if self.base.config.msix.is_none() { - error!("Failed to get msix in virtio pci device configure"); + error!( + "Failed to get msix in virtio pci device configure, device is {}", + self.base.base.id + ); return false; } @@ -997,6 +1024,7 @@ impl PciDevOps for VirtioPciDevice { } fn realize(mut self) -> Result<()> { + info!("func: realize, id: {:?}", &self.base.base.id); self.init_write_mask(false)?; self.init_write_clear_mask(false)?; @@ -1164,7 +1192,7 @@ impl PciDevOps for VirtioPciDevice { } fn unrealize(&mut self) -> Result<()> { - trace::virtio_tpt_common("unrealize", &self.base.base.id); + info!("func: unrealize, id: {:?}", &self.base.base.id); self.device .lock() .unwrap() @@ -1191,8 +1219,8 @@ impl PciDevOps for VirtioPciDevice { let end = offset + data_size; if end > PCIE_CONFIG_SPACE_SIZE || data_size > REG_SIZE { error!( - "Failed to write pcie config space at offset 0x{:x} with data size {}", - offset, data_size + "Failed to write pcie config space at offset {:#x} with data size {}, device is {}", + offset, data_size, self.base.base.id ); return; } @@ -1212,7 +1240,7 @@ impl PciDevOps for VirtioPciDevice { } fn reset(&mut self, _reset_child_device: bool) -> Result<()> { - trace::virtio_tpt_common("reset", &self.base.base.id); + info!("func: reset, id: {:?}", &self.base.base.id); self.deactivate_device(); self.device .lock() @@ -1333,10 +1361,16 @@ impl MigrationHook for VirtioPciDevice { .unwrap() .activate(self.sys_mem.clone(), cb, queue_evts) { - error!("Failed to resume device, error is {:?}", e); + error!( + "Failed to resume device {}, error is {:?}", + self.base.base.id, e + ); } } else { - error!("Failed to resume device: No interrupt callback"); + error!( + "Failed to resume device {}: No interrupt callback", + self.base.base.id + ); } Ok(()) @@ -1351,8 +1385,9 @@ mod tests { use vmm_sys_util::eventfd::EventFd; use super::*; + use crate::transport::virtio_mmio::tests::address_space_init; use crate::VirtioBase; - use address_space::{AddressSpace, GuestAddress, HostMemMapping}; + use address_space::{AddressSpace, GuestAddress}; use devices::pci::{ config::{HEADER_TYPE, HEADER_TYPE_MULTIFUNC}, le_read_u16, @@ -1434,30 +1469,40 @@ mod tests { }; } - #[test] - fn test_common_config_dev_feature() { + fn virtio_pci_test_init( + multi_func: bool, + ) -> ( + Arc>, + Arc>, + VirtioPciDevice, + ) { let virtio_dev = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); + let sys_mem = address_space_init(); let parent_bus = Arc::new(Mutex::new(PciBus::new( String::from("test bus"), #[cfg(target_arch = "x86_64")] Region::init_container_region(1 << 16, "parent_bus"), sys_mem.root().clone(), ))); - let mut virtio_pci = VirtioPciDevice::new( + let virtio_pci = VirtioPciDevice::new( String::from("test device"), 0, sys_mem, virtio_dev.clone(), Arc::downgrade(&parent_bus), + multi_func, false, ); + // Note: if parent_bus is used in the code execution during the testing process, a variable needs to + // be used to maintain the count and avoid rust from automatically releasing this `Arc`. + (virtio_dev, parent_bus, virtio_pci) + } + + #[test] + fn test_common_config_dev_feature() { + let (virtio_dev, _, mut virtio_pci) = virtio_pci_test_init(false); + // Read virtio device features virtio_dev.lock().unwrap().set_hfeatures_sel(0_u32); com_cfg_read_test!(virtio_pci, COMMON_DF_REG, 0xFFFF_FFF0_u32); @@ -1493,27 +1538,7 @@ mod tests { #[test] fn test_common_config_queue() { - let virtio_dev = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 0, - sys_mem, - virtio_dev.clone(), - Arc::downgrade(&parent_bus), - false, - ); + let (virtio_dev, _, virtio_pci) = virtio_pci_test_init(false); // Read Queue's Descriptor Table address virtio_dev @@ -1543,28 +1568,7 @@ mod tests { #[test] fn test_common_config_queue_error() { - let virtio_dev = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let cloned_virtio_dev = virtio_dev.clone(); - let mut virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 0, - sys_mem, - cloned_virtio_dev, - Arc::downgrade(&parent_bus), - false, - ); + let (virtio_dev, _, mut virtio_pci) = virtio_pci_test_init(false); assert!(init_msix( &mut virtio_pci.base, @@ -1617,28 +1621,8 @@ mod tests { #[test] fn test_virtio_pci_config_access() { - let virtio_dev: Arc> = - Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let mut virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 0, - sys_mem, - virtio_dev, - Arc::downgrade(&parent_bus), - false, - ); + let (_, _parent_bus, mut virtio_pci) = virtio_pci_test_init(false); + virtio_pci.init_write_mask(false).unwrap(); virtio_pci.init_write_clear_mask(false).unwrap(); @@ -1657,67 +1641,14 @@ mod tests { #[test] fn test_virtio_pci_realize() { - let virtio_dev: Arc> = - Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 0, - sys_mem, - virtio_dev, - Arc::downgrade(&parent_bus), - false, - ); + let (_, _parent_bus, virtio_pci) = virtio_pci_test_init(false); assert!(virtio_pci.realize().is_ok()); } #[test] fn test_device_activate() { - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let mem_size: u64 = 1024 * 1024; - let host_mmap = Arc::new( - HostMemMapping::new(GuestAddress(0), None, mem_size, None, false, false, false) - .unwrap(), - ); - sys_mem - .root() - .add_subregion( - Region::init_ram_region(host_mmap.clone(), "sysmem"), - host_mmap.start_address().raw_value(), - ) - .unwrap(); + let (virtio_dev, _parent_bus, mut virtio_pci) = virtio_pci_test_init(false); - let virtio_dev = Arc::new(Mutex::new(VirtioDeviceTest::new())); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let mut virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 0, - sys_mem, - virtio_dev.clone(), - Arc::downgrade(&parent_bus), - false, - ); #[cfg(target_arch = "aarch64")] virtio_pci.base.config.set_interrupt_pin(); @@ -1787,28 +1718,7 @@ mod tests { #[test] fn test_multifunction() { - let virtio_dev: Arc> = - Arc::new(Mutex::new(VirtioDeviceTest::new())); - let sys_mem = AddressSpace::new( - Region::init_container_region(u64::max_value(), "sysmem"), - "sysmem", - None, - ) - .unwrap(); - let parent_bus = Arc::new(Mutex::new(PciBus::new( - String::from("test bus"), - #[cfg(target_arch = "x86_64")] - Region::init_container_region(1 << 16, "parent_bus"), - sys_mem.root().clone(), - ))); - let mut virtio_pci = VirtioPciDevice::new( - String::from("test device"), - 24, - sys_mem, - virtio_dev, - Arc::downgrade(&parent_bus), - true, - ); + let (_, _parent_bus, mut virtio_pci) = virtio_pci_test_init(true); assert!(init_multifunction( virtio_pci.multi_func, diff --git a/virtio/src/vhost/kernel/mod.rs b/virtio/src/vhost/kernel/mod.rs index 02d55ca..2111e2b 100644 --- a/virtio/src/vhost/kernel/mod.rs +++ b/virtio/src/vhost/kernel/mod.rs @@ -14,7 +14,7 @@ mod net; mod vsock; pub use net::Net; -pub use vsock::{Vsock, VsockState}; +pub use vsock::{Vsock, VsockConfig, VsockState}; use std::fs::{File, OpenOptions}; use std::os::unix::fs::OpenOptionsExt; diff --git a/virtio/src/vhost/kernel/net.rs b/virtio/src/vhost/kernel/net.rs index 55a4972..89a2997 100644 --- a/virtio/src/vhost/kernel/net.rs +++ b/virtio/src/vhost/kernel/net.rs @@ -31,10 +31,10 @@ use crate::{ VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_UFO, VIRTIO_NET_F_MQ, VIRTIO_TYPE_NET, }; use address_space::AddressSpace; -use machine_manager::config::NetworkInterfaceConfig; +use machine_manager::config::{NetDevcfg, NetworkInterfaceConfig}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use util::byte_code::ByteCode; -use util::loop_context::EventNotifierHelper; +use util::loop_context::{create_new_eventfd, EventNotifierHelper}; use util::tap::Tap; /// Number of virtqueues. @@ -79,6 +79,8 @@ pub struct Net { base: VirtioBase, /// Configuration of the network device. net_cfg: NetworkInterfaceConfig, + /// Configuration of the backend netdev. + netdev_cfg: NetDevcfg, /// Virtio net configurations. config_space: Arc>, /// Tap device opened. @@ -94,17 +96,22 @@ pub struct Net { } impl Net { - pub fn new(cfg: &NetworkInterfaceConfig, mem_space: &Arc) -> Self { - let queue_num = if cfg.mq { - (cfg.queues + 1) as usize + pub fn new( + net_cfg: &NetworkInterfaceConfig, + netdev_cfg: NetDevcfg, + mem_space: &Arc, + ) -> Self { + let queue_num = if net_cfg.mq { + (netdev_cfg.queues + 1) as usize } else { QUEUE_NUM_NET }; - let queue_size = cfg.queue_size; + let queue_size = net_cfg.queue_size; Net { base: VirtioBase::new(VIRTIO_TYPE_NET, queue_num, queue_size), - net_cfg: cfg.clone(), + net_cfg: net_cfg.clone(), + netdev_cfg, config_space: Default::default(), taps: None, backends: None, @@ -125,10 +132,10 @@ impl VirtioDevice for Net { } fn realize(&mut self) -> Result<()> { - let queue_pairs = self.net_cfg.queues / 2; + let queue_pairs = self.netdev_cfg.queues / 2; let mut backends = Vec::with_capacity(queue_pairs as usize); for index in 0..queue_pairs { - let fd = if let Some(fds) = self.net_cfg.vhost_fds.as_mut() { + let fd = if let Some(fds) = self.netdev_cfg.vhost_fds.as_mut() { fds.get(index as usize).copied() } else { None @@ -142,12 +149,12 @@ impl VirtioDevice for Net { backends.push(backend); } - let host_dev_name = match self.net_cfg.host_dev_name.as_str() { + let host_dev_name = match self.netdev_cfg.ifname.as_str() { "" => None, - _ => Some(self.net_cfg.host_dev_name.as_str()), + _ => Some(self.netdev_cfg.ifname.as_str()), }; - self.taps = create_tap(self.net_cfg.tap_fds.as_ref(), host_dev_name, queue_pairs) + self.taps = create_tap(self.netdev_cfg.tap_fds.as_ref(), host_dev_name, queue_pairs) .with_context(|| "Failed to create tap for vhost net")?; self.backends = Some(backends); @@ -174,7 +181,7 @@ impl VirtioDevice for Net { let mut locked_config = self.config_space.lock().unwrap(); - let queue_pairs = self.net_cfg.queues / 2; + let queue_pairs = self.netdev_cfg.queues / 2; if self.net_cfg.mq && (VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN..=VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX) .contains(&queue_pairs) @@ -320,8 +327,7 @@ impl VirtioDevice for Net { let event = if self.call_events.is_empty() { let host_notify = VhostNotify { notify_evt: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) - .with_context(|| VirtioError::EventFdCreate)?, + create_new_eventfd().with_context(|| VirtioError::EventFdCreate)?, ), queue: queue_mutex.clone(), }; @@ -385,7 +391,7 @@ impl VirtioDevice for Net { } fn reset(&mut self) -> Result<()> { - let queue_pairs = self.net_cfg.queues / 2; + let queue_pairs = self.netdev_cfg.queues / 2; for index in 0..queue_pairs as usize { let backend = match &self.backends { None => return Err(anyhow!("Failed to get backend for vhost net")), @@ -441,46 +447,43 @@ mod tests { #[test] fn test_vhost_net_realize() { - let net1 = NetworkInterfaceConfig { - id: "eth1".to_string(), - host_dev_name: "tap1".to_string(), - mac: Some("1F:2C:3E:4A:5B:6D".to_string()), - vhost_type: Some("vhost-kernel".to_string()), + let netdev_cfg1 = NetDevcfg { + netdev_type: "tap".to_string(), + id: "net1".to_string(), tap_fds: Some(vec![4]), + vhost_kernel: true, vhost_fds: Some(vec![5]), - iothread: None, + ifname: "tap1".to_string(), queues: 2, + ..Default::default() + }; + let vhost_net_conf = NetworkInterfaceConfig { + id: "eth1".to_string(), + mac: Some("1F:2C:3E:4A:5B:6D".to_string()), + iothread: None, mq: false, - socket_path: None, queue_size: DEFAULT_VIRTQUEUE_SIZE, + ..Default::default() }; - let conf = vec![net1]; - let confs = Some(conf); - let vhost_net_confs = confs.unwrap(); - let vhost_net_conf = vhost_net_confs[0].clone(); let vhost_net_space = vhost_address_space_init(); - let mut vhost_net = Net::new(&vhost_net_conf, &vhost_net_space); + let mut vhost_net = Net::new(&vhost_net_conf, netdev_cfg1, &vhost_net_space); // the tap_fd and vhost_fd attribute of vhost-net can't be assigned. - assert_eq!(vhost_net.realize().is_ok(), false); + assert!(vhost_net.realize().is_err()); - let net1 = NetworkInterfaceConfig { - id: "eth0".to_string(), - host_dev_name: "".to_string(), - mac: Some("1A:2B:3C:4D:5E:6F".to_string()), - vhost_type: Some("vhost-kernel".to_string()), - tap_fds: None, - vhost_fds: None, - iothread: None, + let netdev_cfg2 = NetDevcfg { + netdev_type: "tap".to_string(), + id: "net2".to_string(), + vhost_kernel: true, queues: 2, - mq: false, - socket_path: None, + ..Default::default() + }; + let net_cfg2 = NetworkInterfaceConfig { + id: "eth2".to_string(), + mac: Some("1A:2B:3C:4D:5E:6F".to_string()), queue_size: DEFAULT_VIRTQUEUE_SIZE, + ..Default::default() }; - let conf = vec![net1]; - let confs = Some(conf); - let vhost_net_confs = confs.unwrap(); - let vhost_net_conf = vhost_net_confs[0].clone(); - let mut vhost_net = Net::new(&vhost_net_conf, &vhost_net_space); + let mut vhost_net = Net::new(&net_cfg2, netdev_cfg2, &vhost_net_space); // if fail to open vhost-net device, no need to continue. if let Err(_e) = File::open("/dev/vhost-net") { diff --git a/virtio/src/vhost/kernel/vsock.rs b/virtio/src/vhost/kernel/vsock.rs index 8d782c6..ade3ec2 100644 --- a/virtio/src/vhost/kernel/vsock.rs +++ b/virtio/src/vhost/kernel/vsock.rs @@ -16,6 +16,7 @@ use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; +use clap::{ArgAction, Parser}; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::ioctl::ioctl_with_ref; @@ -26,12 +27,12 @@ use crate::{ VirtioInterruptType, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_TYPE_VSOCK, }; use address_space::AddressSpace; -use machine_manager::config::{VsockConfig, DEFAULT_VIRTQUEUE_SIZE}; +use machine_manager::config::{get_pci_df, parse_bool, valid_id, DEFAULT_VIRTQUEUE_SIZE}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use migration::{DeviceStateDesc, FieldDesc, MigrationHook, MigrationManager, StateTransfer}; use migration_derive::{ByteCode, Desc}; use util::byte_code::ByteCode; -use util::loop_context::EventNotifierHelper; +use util::loop_context::{create_new_eventfd, EventNotifierHelper}; /// Number of virtqueues. const QUEUE_NUM_VSOCK: usize = 3; @@ -40,6 +41,29 @@ const VHOST_PATH: &str = "/dev/vhost-vsock"; /// Event transport reset const VIRTIO_VSOCK_EVENT_TRANSPORT_RESET: u32 = 0; +const MAX_GUEST_CID: u64 = 4_294_967_295; +const MIN_GUEST_CID: u64 = 3; + +/// Config structure for virtio-vsock. +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct VsockConfig { + #[arg(long, value_parser = ["vhost-vsock-pci", "vhost-vsock-device"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: Option, + #[arg(long, alias = "guest-cid", value_parser = clap::value_parser!(u64).range(MIN_GUEST_CID..=MAX_GUEST_CID))] + pub guest_cid: u64, + #[arg(long, alias = "vhostfd")] + pub vhost_fd: Option, +} + trait VhostVsockBackend { /// Each guest should have an unique CID which is used to route data to the guest. fn set_guest_cid(&self, cid: u64) -> Result<()>; @@ -287,8 +311,7 @@ impl VirtioDevice for Vsock { let event = if self.call_events.is_empty() { let host_notify = VhostNotify { notify_evt: Arc::new( - EventFd::new(libc::EFD_NONBLOCK) - .with_context(|| VirtioError::EventFdCreate)?, + create_new_eventfd().with_context(|| VirtioError::EventFdCreate)?, ), queue: queue_mutex.clone(), }; @@ -390,9 +413,9 @@ impl MigrationHook for Vsock { #[cfg(test)] mod tests { - pub use super::super::*; - pub use super::*; - pub use address_space::*; + use super::*; + use address_space::*; + use machine_manager::config::str_slip_to_clap; fn vsock_address_space_init() -> Arc { let root = Region::init_container_region(u64::max_value(), "sysmem"); @@ -405,12 +428,30 @@ mod tests { id: "test_vsock_1".to_string(), guest_cid: 3, vhost_fd: None, + ..Default::default() }; let sys_mem = vsock_address_space_init(); let vsock = Vsock::new(&vsock_conf, &sys_mem); vsock } + #[test] + fn test_vsock_config_cmdline_parser() { + let vsock_cmd = "vhost-vsock-device,id=test_vsock,guest-cid=3"; + let vsock_config = + VsockConfig::try_parse_from(str_slip_to_clap(vsock_cmd, true, false)).unwrap(); + assert_eq!(vsock_config.id, "test_vsock"); + assert_eq!(vsock_config.guest_cid, 3); + assert_eq!(vsock_config.vhost_fd, None); + + let vsock_cmd = "vhost-vsock-device,id=test_vsock,guest-cid=3,vhostfd=4"; + let vsock_config = + VsockConfig::try_parse_from(str_slip_to_clap(vsock_cmd, true, false)).unwrap(); + assert_eq!(vsock_config.id, "test_vsock"); + assert_eq!(vsock_config.guest_cid, 3); + assert_eq!(vsock_config.vhost_fd, Some(4)); + } + #[test] fn test_vsock_init() { // test vsock new method diff --git a/virtio/src/vhost/user/block.rs b/virtio/src/vhost/user/block.rs index 1fe8ac8..8c55cac 100644 --- a/virtio/src/vhost/user/block.rs +++ b/virtio/src/vhost/user/block.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use vmm_sys_util::eventfd::EventFd; use super::client::VhostUserClient; @@ -30,14 +31,41 @@ use crate::{ VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_WRITE_ZEROES, VIRTIO_F_VERSION_1, VIRTIO_TYPE_BLOCK, }; use address_space::AddressSpace; -use machine_manager::{config::BlkDevConfig, event_loop::unregister_event_helper}; +use machine_manager::config::{ + get_chardev_socket_path, get_pci_df, valid_block_device_virtqueue_size, valid_id, + ChardevConfig, MAX_VIRTIO_QUEUE, +}; +use machine_manager::event_loop::unregister_event_helper; use util::byte_code::ByteCode; +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct VhostUserBlkDevConfig { + #[arg(long, value_parser = ["vhost-user-blk-device", "vhost-user-blk-pci"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, alias = "num-queues", value_parser = clap::value_parser!(u16).range(1..=MAX_VIRTIO_QUEUE as i64))] + pub num_queues: Option, + #[arg(long)] + pub chardev: String, + #[arg(long, alias = "queue-size", default_value = "256", value_parser = valid_block_device_virtqueue_size)] + pub queue_size: u16, + #[arg(long)] + pub bootindex: Option, +} + pub struct Block { /// Virtio device base property. base: VirtioBase, /// Configuration of the block device. - blk_cfg: BlkDevConfig, + blk_cfg: VhostUserBlkDevConfig, + /// Configuration of the vhost user blk's socket chardev. + chardev_cfg: ChardevConfig, /// Config space of the block device. config_space: VirtioBlkConfig, /// System address space. @@ -51,13 +79,18 @@ pub struct Block { } impl Block { - pub fn new(cfg: &BlkDevConfig, mem_space: &Arc) -> Self { - let queue_num = cfg.queues as usize; + pub fn new( + cfg: &VhostUserBlkDevConfig, + chardev_cfg: ChardevConfig, + mem_space: &Arc, + ) -> Self { + let queue_num = cfg.num_queues.unwrap_or(1) as usize; let queue_size = cfg.queue_size; Block { base: VirtioBase::new(VIRTIO_TYPE_BLOCK, queue_num, queue_size), blk_cfg: cfg.clone(), + chardev_cfg, config_space: Default::default(), mem_space: mem_space.clone(), client: None, @@ -68,12 +101,7 @@ impl Block { /// Connect with spdk and register update event. fn init_client(&mut self) -> Result<()> { - let socket_path = self - .blk_cfg - .socket_path - .as_ref() - .map(|path| path.to_string()) - .with_context(|| "vhost-user: socket path is not found")?; + let socket_path = get_chardev_socket_path(self.chardev_cfg.clone())?; let client = VhostUserClient::new( &self.mem_space, &socket_path, @@ -146,10 +174,10 @@ impl VirtioDevice for Block { ); } - if self.blk_cfg.queues > 1 { - self.config_space.num_queues = self.blk_cfg.queues; + if self.blk_cfg.num_queues.unwrap_or(1) > 1 { + self.config_space.num_queues = self.blk_cfg.num_queues.unwrap_or(1); } - } else if self.blk_cfg.queues > 1 { + } else if self.blk_cfg.num_queues.unwrap_or(1) > 1 { bail!( "spdk doesn't support multi queue, spdk protocol features: {:#b}", protocol_features @@ -169,7 +197,7 @@ impl VirtioDevice for Block { | 1_u64 << VIRTIO_BLK_F_WRITE_ZEROES | 1_u64 << VIRTIO_BLK_F_SEG_MAX | 1_u64 << VIRTIO_BLK_F_RO; - if self.blk_cfg.queues > 1 { + if self.blk_cfg.num_queues.unwrap_or(1) > 1 { self.base.device_features |= 1_u64 << VIRTIO_BLK_F_MQ; } self.base.device_features &= features; @@ -217,13 +245,7 @@ impl VirtioDevice for Block { if !self.enable_irqfd { let queue_num = self.base.queues.len(); - listen_guest_notifier( - &mut self.base, - &mut client, - self.blk_cfg.iothread.as_ref(), - queue_num, - interrupt_cb, - )?; + listen_guest_notifier(&mut self.base, &mut client, None, queue_num, interrupt_cb)?; } client.activate_vhost_user()?; @@ -232,18 +254,10 @@ impl VirtioDevice for Block { } fn deactivate(&mut self) -> Result<()> { - self.client - .as_ref() - .with_context(|| "Failed to get client when deactivating device")? - .lock() - .unwrap() - .reset_vhost_user()?; - if !self.base.deactivate_evts.is_empty() { - unregister_event_helper( - self.blk_cfg.iothread.as_ref(), - &mut self.base.deactivate_evts, - )?; + if let Some(client) = &self.client { + client.lock().unwrap().reset_vhost_user(false); } + unregister_event_helper(None, &mut self.base.deactivate_evts)?; Ok(()) } diff --git a/virtio/src/vhost/user/client.rs b/virtio/src/vhost/user/client.rs index 0bab675..b32bf72 100644 --- a/virtio/src/vhost/user/client.rs +++ b/virtio/src/vhost/user/client.rs @@ -142,9 +142,9 @@ fn vhost_user_reconnect(client: &Arc>) { } if let Err(e) = locked_client.activate_vhost_user() { - error!("Failed to reactivate vhost-user net, {:?}", e); + error!("Failed to reactivate vhost-user {}, {:?}", dev_type, e); } else { - info!("Reconnecting vhost-user net succeed."); + info!("Reconnecting vhost-user {} succeed.", dev_type); } } @@ -612,7 +612,7 @@ impl VhostUserClient { Ok(()) } - pub fn reset_vhost_user(&mut self) -> Result<()> { + fn reset_queues(&mut self) -> Result<()> { for (queue_index, queue_mutex) in self.queues.iter().enumerate() { if !queue_mutex.lock().unwrap().vring.is_enabled() { continue; @@ -622,12 +622,25 @@ impl VhostUserClient { self.get_vring_base(queue_index) .with_context(|| format!("Failed to get vring base, index: {}", queue_index))?; } + Ok(()) + } + + pub fn reset_vhost_user(&mut self, reset_owner: bool) { + let dev_type = self.backend_type.to_string(); + if reset_owner { + if let Err(e) = self.reset_owner() { + warn!("Failed to reset owner for vhost-user {}: {:?}", dev_type, e); + } + } else if let Err(e) = self.reset_queues() { + warn!( + "Failed to reset queues for vhost-user {}: {:?}", + dev_type, e + ); + } self.queue_evts.clear(); self.call_events.clear(); self.queues.clear(); - - Ok(()) } pub fn add_event(client: &Arc>) -> Result<()> { @@ -1055,7 +1068,16 @@ impl VhostOps for VhostUserClient { } fn reset_owner(&self) -> Result<()> { - bail!("Does not support for resetting owner") + trace::vhost_reset_owner(); + let hdr = VhostUserMsgHdr::new(VhostUserMsgReq::ResetOwner as u32, 0, 0); + let body_opt: Option<&u32> = None; + let payload_opt: Option<&[u8]> = None; + let client = self.client.lock().unwrap(); + client + .sock + .send_msg(Some(&hdr), body_opt, payload_opt, &[]) + .with_context(|| "Failed to send msg for reset_owner")?; + Ok(()) } fn get_vring_base(&self, queue_idx: usize) -> Result { diff --git a/virtio/src/vhost/user/fs.rs b/virtio/src/vhost/user/fs.rs index 3f60f68..75c3698 100644 --- a/virtio/src/vhost/user/fs.rs +++ b/virtio/src/vhost/user/fs.rs @@ -19,7 +19,8 @@ const VIRTIO_FS_QUEUE_SIZE: u16 = 128; use std::sync::{Arc, Mutex}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; use vmm_sys_util::eventfd::EventFd; use super::super::super::{VirtioDevice, VIRTIO_TYPE_FS}; @@ -27,10 +28,45 @@ use super::super::VhostOps; use super::{listen_guest_notifier, VhostBackendType, VhostUserClient}; use crate::{read_config_default, VirtioBase, VirtioInterrupt}; use address_space::AddressSpace; -use machine_manager::config::{FsConfig, MAX_TAG_LENGTH}; +use machine_manager::config::{ + get_pci_df, parse_bool, valid_id, ChardevConfig, ConfigError, SocketType, +}; use machine_manager::event_loop::unregister_event_helper; use util::byte_code::ByteCode; +const MAX_TAG_LENGTH: usize = 36; + +/// Config struct for `fs`. +/// Contains fs device's attr. +#[derive(Parser, Debug, Clone)] +#[command(no_binary_name(true))] +pub struct FsConfig { + #[arg(long, value_parser = ["vhost-user-fs-pci", "vhost-user-fs-device"])] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub chardev: String, + #[arg(long, value_parser = valid_tag)] + pub tag: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool)] + pub multifunction: Option, +} + +fn valid_tag(tag: &str) -> Result { + if tag.len() >= MAX_TAG_LENGTH { + return Err(anyhow!(ConfigError::StringLengthTooLong( + "fs device tag".to_string(), + MAX_TAG_LENGTH - 1, + ))); + } + Ok(tag.to_string()) +} + #[derive(Copy, Clone)] #[repr(C, packed)] struct VirtioFsConfig { @@ -52,6 +88,7 @@ impl ByteCode for VirtioFsConfig {} pub struct Fs { base: VirtioBase, fs_cfg: FsConfig, + chardev_cfg: ChardevConfig, config_space: VirtioFsConfig, client: Option>>, mem_space: Arc, @@ -64,14 +101,16 @@ impl Fs { /// # Arguments /// /// `fs_cfg` - The config of this Fs device. + /// `chardev_cfg` - The config of this Fs device's chardev. /// `mem_space` - The address space of this Fs device. - pub fn new(fs_cfg: FsConfig, mem_space: Arc) -> Self { + pub fn new(fs_cfg: FsConfig, chardev_cfg: ChardevConfig, mem_space: Arc) -> Self { let queue_num = VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM; let queue_size = VIRTIO_FS_QUEUE_SIZE; Fs { base: VirtioBase::new(VIRTIO_TYPE_FS, queue_num, queue_size), fs_cfg, + chardev_cfg, config_space: VirtioFsConfig::default(), client: None, mem_space, @@ -91,9 +130,15 @@ impl VirtioDevice for Fs { fn realize(&mut self) -> Result<()> { let queues_num = VIRIOT_FS_HIGH_PRIO_QUEUE_NUM + VIRTIO_FS_REQ_QUEUES_NUM; + + let socket_path = match self.chardev_cfg.classtype.socket_type()? { + SocketType::Unix { path } => path, + _ => bail!("Vhost-user-fs Chardev backend should be unix-socket type."), + }; + let client = VhostUserClient::new( &self.mem_space, - &self.fs_cfg.sock, + &socket_path, queues_num as u64, VhostBackendType::TypeFs, ) @@ -167,6 +212,9 @@ impl VirtioDevice for Fs { } fn deactivate(&mut self) -> Result<()> { + if let Some(client) = &self.client { + client.lock().unwrap().reset_vhost_user(true); + } unregister_event_helper(None, &mut self.base.deactivate_evts)?; Ok(()) } @@ -191,3 +239,24 @@ impl VirtioDevice for Fs { self.realize() } } + +#[cfg(test)] +mod tests { + use super::*; + use machine_manager::config::str_slip_to_clap; + + #[test] + fn test_vhostuserfs_cmdline_parser() { + // Test1: Right. + let fs_cmd = "vhost-user-fs-device,id=fs0,chardev=chardev0,tag=tag0"; + let fs_config = FsConfig::try_parse_from(str_slip_to_clap(fs_cmd, true, false)).unwrap(); + assert_eq!(fs_config.id, "fs0"); + assert_eq!(fs_config.chardev, "chardev0"); + assert_eq!(fs_config.tag, "tag0"); + + // Test2: Illegal value. + let fs_cmd = "vhost-user-fs-device,id=fs0,chardev=chardev0,tag=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + let result = FsConfig::try_parse_from(str_slip_to_clap(fs_cmd, true, false)); + assert!(result.is_err()); + } +} diff --git a/virtio/src/vhost/user/mod.rs b/virtio/src/vhost/user/mod.rs index 2f0dc96..8a6e8d4 100644 --- a/virtio/src/vhost/user/mod.rs +++ b/virtio/src/vhost/user/mod.rs @@ -18,7 +18,7 @@ mod message; mod net; mod sock; -pub use self::block::Block; +pub use self::block::{Block, VhostUserBlkDevConfig}; pub use self::client::*; pub use self::fs::*; pub use self::message::*; diff --git a/virtio/src/vhost/user/net.rs b/virtio/src/vhost/user/net.rs index e7f0964..4dc7634 100644 --- a/virtio/src/vhost/user/net.rs +++ b/virtio/src/vhost/user/net.rs @@ -28,7 +28,7 @@ use crate::{ VIRTIO_NET_F_MRG_RXBUF, VIRTIO_TYPE_NET, }; use address_space::AddressSpace; -use machine_manager::config::NetworkInterfaceConfig; +use machine_manager::config::{NetDevcfg, NetworkInterfaceConfig}; use machine_manager::event_loop::{register_event_helper, unregister_event_helper}; use util::byte_code::ByteCode; use util::loop_context::EventNotifierHelper; @@ -42,6 +42,10 @@ pub struct Net { base: VirtioBase, /// Configuration of the vhost user network device. net_cfg: NetworkInterfaceConfig, + /// Configuration of the backend netdev. + netdev_cfg: NetDevcfg, + /// path of the socket chardev. + sock_path: String, /// Virtio net configurations. config_space: Arc>, /// System address space. @@ -53,18 +57,25 @@ pub struct Net { } impl Net { - pub fn new(cfg: &NetworkInterfaceConfig, mem_space: &Arc) -> Self { - let queue_num = if cfg.mq { + pub fn new( + net_cfg: &NetworkInterfaceConfig, + netdev_cfg: NetDevcfg, + sock_path: String, + mem_space: &Arc, + ) -> Self { + let queue_num = if net_cfg.mq { // If support multi-queue, it should add 1 control queue. - (cfg.queues + 1) as usize + (netdev_cfg.queues + 1) as usize } else { QUEUE_NUM_NET }; - let queue_size = cfg.queue_size; + let queue_size = net_cfg.queue_size; Net { base: VirtioBase::new(VIRTIO_TYPE_NET, queue_num, queue_size), - net_cfg: cfg.clone(), + net_cfg: net_cfg.clone(), + netdev_cfg, + sock_path, config_space: Default::default(), mem_space: mem_space.clone(), client: None, @@ -115,14 +126,9 @@ impl VirtioDevice for Net { } fn realize(&mut self) -> Result<()> { - let socket_path = self - .net_cfg - .socket_path - .as_ref() - .with_context(|| "vhost-user: socket path is not found")?; let client = VhostUserClient::new( &self.mem_space, - socket_path, + &self.sock_path, self.queue_num() as u64, VhostBackendType::TypeNet, ) @@ -158,7 +164,7 @@ impl VirtioDevice for Net { let mut locked_config = self.config_space.lock().unwrap(); - let queue_pairs = self.net_cfg.queues / 2; + let queue_pairs = self.netdev_cfg.queues / 2; if self.net_cfg.mq && (VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN..=VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX) .contains(&queue_pairs) -- 2.43.0