From c13648b444a77ea850adc7da25859696a4b20578 Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Fri, 8 Nov 2019 17:43:53 +0800 Subject: Vfio: Create Msix capability for vfio device Loop vfio device config register, then find out the msi and msix pci capability. both msi and msix need IrqRequestSocket for adding its routing info into kvm routing table, but vfio device has one IrqRequestSocket only, and only msi or msix is enabled at runtime, so Arc is used to let msi and msix share one device IrqRequestSocket. BUG=chromium:992270 TEST=pass a device with msix capability to guest, and check device msix function in guest Change-Id: I008ccd0e98507dc4d587418fbe00aa23029bdbad Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1987812 Tested-by: kokoro Reviewed-by: Daniel Verkamp --- devices/src/pci/msix.rs | 5 +- devices/src/pci/vfio_pci.rs | 134 ++++++++++++++++++++++++-------- devices/src/virtio/virtio_pci_device.rs | 5 +- 3 files changed, 107 insertions(+), 37 deletions(-) diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs index 282771f..0b9f391 100644 --- a/devices/src/pci/msix.rs +++ b/devices/src/pci/msix.rs @@ -6,6 +6,7 @@ use crate::pci::{PciCapability, PciCapabilityID}; use msg_socket::{MsgReceiver, MsgSender}; use std::convert::TryInto; use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; use sys_util::{error, EventFd}; use vm_control::{MaybeOwnedFd, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse}; @@ -55,12 +56,12 @@ pub struct MsixConfig { irq_vec: Vec, masked: bool, enabled: bool, - msi_device_socket: VmIrqRequestSocket, + msi_device_socket: Arc, msix_num: u16, } impl MsixConfig { - pub fn new(msix_vectors: u16, vm_socket: VmIrqRequestSocket) -> Self { + pub fn new(msix_vectors: u16, vm_socket: Arc) -> Self { assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE); let mut table_entries: Vec = Vec::new(); diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index 216f5eb..216c6b2 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -17,6 +17,8 @@ use vm_control::{ VmMemoryRequest, VmMemoryResponse, }; +use crate::pci::msix::MsixConfig; + use crate::pci::pci_device::{Error as PciDeviceError, PciDevice}; use crate::pci::{PciClassCode, PciInterruptPin}; @@ -94,6 +96,7 @@ impl VfioPciConfig { const PCI_CAPABILITY_LIST: u32 = 0x34; const PCI_CAP_ID_MSI: u8 = 0x05; +const PCI_CAP_ID_MSIX: u8 = 0x11; // MSI registers const PCI_MSI_NEXT_POINTER: u32 = 0x1; // Next cap pointer @@ -122,43 +125,37 @@ struct VfioMsiCap { ctl: u16, address: u64, data: u16, - vm_socket_irq: VmIrqRequestSocket, + vm_socket_irq: Arc, irqfd: Option, gsi: Option, } impl VfioMsiCap { - fn new(config: &VfioPciConfig, vm_socket_irq: VmIrqRequestSocket) -> Option { + fn new( + config: &VfioPciConfig, + msi_cap_start: u32, + vm_socket_irq: Arc, + ) -> Self { // msi minimum size is 0xa let mut msi_len: u32 = MSI_LENGTH_32BIT; - let mut cap_next: u32 = config.read_config_byte(PCI_CAPABILITY_LIST).into(); - while cap_next != 0 { - let cap_id = config.read_config_byte(cap_next); - // find msi cap - if cap_id == PCI_CAP_ID_MSI { - let msi_ctl = config.read_config_word(cap_next + PCI_MSI_FLAGS); - if msi_ctl & PCI_MSI_FLAGS_64BIT != 0 { - msi_len = MSI_LENGTH_64BIT_WITHOUT_MASK; - } - if msi_ctl & PCI_MSI_FLAGS_MASKBIT != 0 { - msi_len = MSI_LENGTH_64BIT_WITH_MASK; - } - return Some(VfioMsiCap { - offset: cap_next, - size: msi_len, - ctl: 0, - address: 0, - data: 0, - vm_socket_irq, - irqfd: None, - gsi: None, - }); - } - let offset = cap_next + PCI_MSI_NEXT_POINTER; - cap_next = config.read_config_byte(offset).into(); + let msi_ctl = config.read_config_word(msi_cap_start + PCI_MSI_FLAGS); + if msi_ctl & PCI_MSI_FLAGS_64BIT != 0 { + msi_len = MSI_LENGTH_64BIT_WITHOUT_MASK; + } + if msi_ctl & PCI_MSI_FLAGS_MASKBIT != 0 { + msi_len = MSI_LENGTH_64BIT_WITH_MASK; } - None + VfioMsiCap { + offset: msi_cap_start, + size: msi_len, + ctl: 0, + address: 0, + data: 0, + vm_socket_irq, + irqfd: None, + gsi: None, + } } fn is_msi_reg(&self, index: u64, len: usize) -> bool { @@ -290,9 +287,53 @@ impl VfioMsiCap { fn get_msi_irqfd(&self) -> Option<&EventFd> { self.irqfd.as_ref() } +} - fn get_vm_socket(&self) -> RawFd { - self.vm_socket_irq.as_ref().as_raw_fd() +// MSI-X registers in MSI-X capability +const PCI_MSIX_FLAGS: u32 = 0x02; // Message Control +const PCI_MSIX_FLAGS_QSIZE: u16 = 0x07FF; // Table size +const PCI_MSIX_TABLE: u32 = 0x04; // Table offset +const PCI_MSIX_TABLE_BIR: u32 = 0x07; // BAR index +const PCI_MSIX_TABLE_OFFSET: u32 = 0xFFFFFFF8; // Offset into specified BAR +const PCI_MSIX_PBA: u32 = 0x08; // Pending bit Array offset +const PCI_MSIX_PBA_BIR: u32 = 0x07; // BAR index +const PCI_MSIX_PBA_OFFSET: u32 = 0xFFFFFFF8; // Offset into specified BAR + +#[allow(dead_code)] +struct VfioMsixCap { + config: MsixConfig, + offset: u32, + table_size: u16, + table_pci_bar: u32, + table_offset: u64, + pba_pci_bar: u32, + pba_offset: u64, +} + +impl VfioMsixCap { + fn new( + config: &VfioPciConfig, + msix_cap_start: u32, + vm_socket_irq: Arc, + ) -> Self { + let msix_ctl = config.read_config_word(msix_cap_start + PCI_MSIX_FLAGS); + let table_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) + 1; + let table = config.read_config_dword(msix_cap_start + PCI_MSIX_TABLE); + let table_pci_bar = table & PCI_MSIX_TABLE_BIR; + let table_offset = (table & PCI_MSIX_TABLE_OFFSET) as u64; + let pba = config.read_config_dword(msix_cap_start + PCI_MSIX_PBA); + let pba_pci_bar = pba & PCI_MSIX_PBA_BIR; + let pba_offset = (pba & PCI_MSIX_PBA_OFFSET) as u64; + + VfioMsixCap { + config: MsixConfig::new(table_size, vm_socket_irq), + offset: msix_cap_start, + table_size, + table_pci_bar, + table_offset, + pba_pci_bar, + pba_offset, + } } } @@ -311,6 +352,7 @@ enum DeviceData { } /// Implements the Vfio Pci device, then a pci device is added into vm +#[allow(dead_code)] pub struct VfioPciDevice { device: Arc, config: VfioPciConfig, @@ -320,8 +362,10 @@ pub struct VfioPciDevice { mmio_regions: Vec, io_regions: Vec, msi_cap: Option, + msix_cap: Option, irq_type: Option, vm_socket_mem: VmMemoryControlRequestSocket, + vm_socket_irq: Arc, device_data: Option, // scratch MemoryMapping to avoid unmap beform vm exit @@ -337,7 +381,29 @@ impl VfioPciDevice { ) -> Self { let dev = Arc::new(device); let config = VfioPciConfig::new(Arc::clone(&dev)); - let msi_cap = VfioMsiCap::new(&config, vfio_device_socket_irq); + let vm_socket_irq = Arc::new(vfio_device_socket_irq); + let mut msi_cap: Option = None; + let mut msix_cap: Option = None; + + let mut cap_next: u32 = config.read_config_byte(PCI_CAPABILITY_LIST).into(); + while cap_next != 0 { + let cap_id = config.read_config_byte(cap_next); + if cap_id == PCI_CAP_ID_MSI { + msi_cap = Some(VfioMsiCap::new( + &config, + cap_next, + Arc::clone(&vm_socket_irq), + )); + } else if cap_id == PCI_CAP_ID_MSIX { + msix_cap = Some(VfioMsixCap::new( + &config, + cap_next, + Arc::clone(&vm_socket_irq), + )); + } + let offset = cap_next + PCI_MSI_NEXT_POINTER; + cap_next = config.read_config_byte(offset).into(); + } let vendor_id = config.read_config_word(PCI_VENDOR_ID); let class_code = config.read_config_byte(PCI_BASE_CLASS_CODE); @@ -361,8 +427,10 @@ impl VfioPciDevice { mmio_regions: Vec::new(), io_regions: Vec::new(), msi_cap, + msix_cap, irq_type: None, vm_socket_mem: vfio_device_socket_mem, + vm_socket_irq, device_data, mem: Vec::new(), } @@ -566,10 +634,8 @@ impl PciDevice for VfioPciDevice { if let Some(ref interrupt_resample_evt) = self.interrupt_resample_evt { fds.push(interrupt_resample_evt.as_raw_fd()); } - if let Some(msi_cap) = &self.msi_cap { - fds.push(msi_cap.get_vm_socket()); - } fds.push(self.vm_socket_mem.as_raw_fd()); + fds.push(self.vm_socket_irq.as_ref().as_raw_fd()); fds } diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs index 4b161d4..c6d6786 100644 --- a/devices/src/virtio/virtio_pci_device.rs +++ b/devices/src/virtio/virtio_pci_device.rs @@ -252,7 +252,10 @@ impl VirtioPciDevice { // One MSI-X vector per queue plus one for configuration changes. let msix_num = u16::try_from(num_queues + 1).map_err(|_| sys_util::Error::new(ERANGE))?; - let msix_config = Arc::new(Mutex::new(MsixConfig::new(msix_num, msi_device_socket))); + let msix_config = Arc::new(Mutex::new(MsixConfig::new( + msix_num, + Arc::new(msi_device_socket), + ))); let config_regs = PciConfiguration::new( VIRTIO_PCI_VENDOR_ID, -- cgit 1.4.1 From bccb4ebb852f74de44034ca4ebac9242bfb71d22 Mon Sep 17 00:00:00 2001 From: Kaiyi Li Date: Thu, 6 Feb 2020 17:53:11 -0800 Subject: Use display size as the default size for single touch When the user specifies the display size through the gpu argument but not specifies the size of the single touch device, the display size will be used as the size of these touch devices. Use default() to initialize the GpuParameters. Allow initialize the GpuParameters dynamically in the future. Change-Id: I9fa04f8ff479732370514fbaeb062d737adba319 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2043072 Tested-by: kokoro Commit-Queue: Kaiyi Li Reviewed-by: Zach Reizner --- devices/src/virtio/gpu/mod.rs | 24 +++--- src/crosvm.rs | 51 ++++++++++-- src/linux.rs | 41 ++++++--- src/main.rs | 188 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 237 insertions(+), 67 deletions(-) diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index aa674a8..49fdff4 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -64,16 +64,6 @@ pub struct GpuParameters { pub mode: GpuMode, } -pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters { - display_width: DEFAULT_DISPLAY_WIDTH, - display_height: DEFAULT_DISPLAY_HEIGHT, - renderer_use_egl: true, - renderer_use_gles: true, - renderer_use_glx: false, - renderer_use_surfaceless: true, - mode: GpuMode::Mode3D, -}; - // First queue is for virtio gpu commands. Second queue is for cursor commands, which we expect // there to be fewer of. const QUEUE_SIZES: &[u16] = &[256, 16]; @@ -83,6 +73,20 @@ const GPU_BAR_NUM: u8 = 4; const GPU_BAR_OFFSET: u64 = 0; const GPU_BAR_SIZE: u64 = 1 << 33; +impl Default for GpuParameters { + fn default() -> Self { + GpuParameters { + display_width: DEFAULT_DISPLAY_WIDTH, + display_height: DEFAULT_DISPLAY_HEIGHT, + renderer_use_egl: true, + renderer_use_gles: true, + renderer_use_glx: false, + renderer_use_surfaceless: true, + mode: GpuMode::Mode3D, + } + } +} + /// A virtio-gpu backend state tracker which supports display and potentially accelerated rendering. /// /// Commands from the virtio-gpu protocol can be submitted here using the methods, and they will be diff --git a/src/crosvm.rs b/src/crosvm.rs index 082e43c..e0ddf06 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -13,7 +13,7 @@ pub mod plugin; use std::collections::BTreeMap; use std::net; use std::os::unix::io::RawFd; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use arch::Pstore; @@ -57,23 +57,58 @@ pub struct GidMap { pub count: u32, } -const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 800; -const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1280; +pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 800; +pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1280; pub struct TouchDeviceOption { - pub path: PathBuf, - pub width: u32, - pub height: u32, + path: PathBuf, + width: Option, + height: Option, + default_width: u32, + default_height: u32, } impl TouchDeviceOption { pub fn new(path: PathBuf) -> TouchDeviceOption { TouchDeviceOption { path, - width: DEFAULT_TOUCH_DEVICE_WIDTH, - height: DEFAULT_TOUCH_DEVICE_HEIGHT, + width: None, + height: None, + default_width: DEFAULT_TOUCH_DEVICE_WIDTH, + default_height: DEFAULT_TOUCH_DEVICE_HEIGHT, } } + + /// Getter for the path to the input event streams. + pub fn get_path(&self) -> &Path { + self.path.as_path() + } + + /// When a user specifies the parameters for a touch device, width and height are optional. + /// If the width and height are missing, default values are used. Default values can be set + /// dynamically, for example from the display sizes specified by the gpu argument. + pub fn set_default_size(&mut self, width: u32, height: u32) { + self.default_width = width; + self.default_height = height; + } + + /// Setter for the width specified by the user. + pub fn set_width(&mut self, width: u32) { + self.width.replace(width); + } + + /// Setter for the height specified by the user. + pub fn set_height(&mut self, height: u32) { + self.height.replace(height); + } + + /// If the user specifies the size, use it. Otherwise, use the default values. + pub fn get_size(&self) -> (u32, u32) { + ( + self.width.unwrap_or(self.default_width), + self.height.unwrap_or(self.default_height), + ) + } } pub enum SharedDirKind { diff --git a/src/linux.rs b/src/linux.rs index 84edf5c..ab6c64c 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -62,7 +62,10 @@ use vm_control::{ VmRunMode, }; -use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption}; +use crate::{ + Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption, + DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH, +}; use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; @@ -473,13 +476,16 @@ fn create_tpm_device(cfg: &Config) -> DeviceResult { } fn create_single_touch_device(cfg: &Config, single_touch_spec: &TouchDeviceOption) -> DeviceResult { - let socket = single_touch_spec.path.into_unix_stream().map_err(|e| { - error!("failed configuring virtio single touch: {:?}", e); - e - })?; - - let dev = virtio::new_single_touch(socket, single_touch_spec.width, single_touch_spec.height) - .map_err(Error::InputDeviceNew)?; + let socket = single_touch_spec + .get_path() + .into_unix_stream() + .map_err(|e| { + error!("failed configuring virtio single touch: {:?}", e); + e + })?; + + let (width, height) = single_touch_spec.get_size(); + let dev = virtio::new_single_touch(socket, width, height).map_err(Error::InputDeviceNew)?; Ok(VirtioDeviceStub { dev: Box::new(dev), jail: simple_jail(&cfg, "input_device")?, @@ -487,13 +493,13 @@ fn create_single_touch_device(cfg: &Config, single_touch_spec: &TouchDeviceOptio } fn create_trackpad_device(cfg: &Config, trackpad_spec: &TouchDeviceOption) -> DeviceResult { - let socket = trackpad_spec.path.into_unix_stream().map_err(|e| { + let socket = trackpad_spec.get_path().into_unix_stream().map_err(|e| { error!("failed configuring virtio trackpad: {}", e); e })?; - let dev = virtio::new_trackpad(socket, trackpad_spec.width, trackpad_spec.height) - .map_err(Error::InputDeviceNew)?; + let (width, height) = trackpad_spec.get_size(); + let dev = virtio::new_trackpad(socket, width, height).map_err(Error::InputDeviceNew)?; Ok(VirtioDeviceStub { dev: Box::new(dev), @@ -1030,8 +1036,17 @@ fn create_virtio_devices( // TODO(nkgold): the width/height here should match the display's height/width. When // those settings are available as CLI options, we should use the CLI options here // as well. - let dev = virtio::new_single_touch(virtio_dev_socket, 1280, 1024) - .map_err(Error::InputDeviceNew)?; + let (single_touch_width, single_touch_height) = cfg + .virtio_single_touch + .as_ref() + .map(|single_touch_spec| single_touch_spec.get_size()) + .unwrap_or((DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)); + let dev = virtio::new_single_touch( + virtio_dev_socket, + single_touch_width, + single_touch_height, + ) + .map_err(Error::InputDeviceNew)?; devs.push(VirtioDeviceStub { dev: Box::new(dev), jail: simple_jail(&cfg, "input_device")?, diff --git a/src/main.rs b/src/main.rs index fb1be25..1e86e47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use crosvm::{ linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption, }; #[cfg(feature = "gpu")] -use devices::virtio::gpu::{GpuMode, GpuParameters, DEFAULT_GPU_PARAMS}; +use devices::virtio::gpu::{GpuMode, GpuParameters}; use devices::{SerialParameters, SerialType}; use disk::QcowFile; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; @@ -114,7 +114,7 @@ fn parse_cpu_set(s: &str) -> argument::Result> { #[cfg(feature = "gpu")] fn parse_gpu_options(s: Option<&str>) -> argument::Result { - let mut gpu_params = DEFAULT_GPU_PARAMS; + let mut gpu_params: GpuParameters = Default::default(); if let Some(s) = s { let opts = s @@ -982,12 +982,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let mut single_touch_spec = TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned())); if let Some(width) = it.next() { - single_touch_spec.width = width.trim().parse().unwrap(); + single_touch_spec.set_width(width.trim().parse().unwrap()); } if let Some(height) = it.next() { - single_touch_spec.height = height.trim().parse().unwrap(); + single_touch_spec.set_height(height.trim().parse().unwrap()); } - cfg.virtio_single_touch = Some(single_touch_spec); } "trackpad" => { @@ -1001,12 +1000,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let mut trackpad_spec = TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned())); if let Some(width) = it.next() { - trackpad_spec.width = width.trim().parse().unwrap(); + trackpad_spec.set_width(width.trim().parse().unwrap()); } if let Some(height) = it.next() { - trackpad_spec.height = height.trim().parse().unwrap(); + trackpad_spec.set_height(height.trim().parse().unwrap()); } - cfg.virtio_trackpad = Some(trackpad_spec); } "mouse" => { @@ -1074,6 +1072,44 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: Ok(()) } +fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error> { + if cfg.executable_path.is_none() { + return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned())); + } + if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() { + if cfg.host_ip.is_none() { + return Err(argument::Error::ExpectedArgument( + "`host_ip` missing from network config".to_owned(), + )); + } + if cfg.netmask.is_none() { + return Err(argument::Error::ExpectedArgument( + "`netmask` missing from network config".to_owned(), + )); + } + if cfg.mac_address.is_none() { + return Err(argument::Error::ExpectedArgument( + "`mac` missing from network config".to_owned(), + )); + } + } + if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) { + return Err(argument::Error::ExpectedArgument( + "`plugin-root` requires `plugin`".to_owned(), + )); + } + #[cfg(feature = "gpu")] + { + if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() { + let (width, height) = (gpu_parameters.display_width, gpu_parameters.display_height); + if let Some(virtio_single_touch) = cfg.virtio_single_touch.as_mut() { + virtio_single_touch.set_default_size(width, height); + } + } + } + Ok(()) +} + fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { let arguments = &[Argument::positional("KERNEL", "bzImage of kernel to run"), @@ -1196,34 +1232,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa let match_res = set_arguments(args, &arguments[..], |name, value| { set_argument(&mut cfg, name, value) }) - .and_then(|_| { - if cfg.executable_path.is_none() { - return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned())); - } - if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() { - if cfg.host_ip.is_none() { - return Err(argument::Error::ExpectedArgument( - "`host_ip` missing from network config".to_owned(), - )); - } - if cfg.netmask.is_none() { - return Err(argument::Error::ExpectedArgument( - "`netmask` missing from network config".to_owned(), - )); - } - if cfg.mac_address.is_none() { - return Err(argument::Error::ExpectedArgument( - "`mac` missing from network config".to_owned(), - )); - } - } - if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) { - return Err(argument::Error::ExpectedArgument( - "`plugin-root` requires `plugin`".to_owned(), - )); - } - Ok(()) - }); + .and_then(|_| validate_arguments(&mut cfg)); match match_res { #[cfg(feature = "plugin")] @@ -1683,6 +1692,7 @@ fn main() { #[cfg(test)] mod tests { use super::*; + use crosvm::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH}; #[test] fn parse_cpu_set_single() { @@ -1873,4 +1883,110 @@ mod tests { set_argument(&mut config, "plugin-gid-map", Some("1:2:blah")) .expect_err("parse should fail because count is not a number"); } + + #[test] + fn single_touch_spec_and_track_pad_spec_default_size() { + let mut config = Config::default(); + config + .executable_path + .replace(Executable::Kernel(PathBuf::from("kernel"))); + set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap(); + set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap(); + validate_arguments(&mut config).unwrap(); + assert_eq!( + config.virtio_single_touch.unwrap().get_size(), + (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT) + ); + assert_eq!( + config.virtio_trackpad.unwrap().get_size(), + (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT) + ); + } + + #[cfg(feature = "gpu")] + #[test] + fn single_touch_spec_default_size_from_gpu() { + let width = 12345u32; + let height = 54321u32; + let mut config = Config::default(); + config + .executable_path + .replace(Executable::Kernel(PathBuf::from("kernel"))); + set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap(); + set_argument( + &mut config, + "gpu", + Some(&format!("width={},height={}", width, height)), + ) + .unwrap(); + validate_arguments(&mut config).unwrap(); + assert_eq!( + config.virtio_single_touch.unwrap().get_size(), + (width, height) + ); + } + + #[test] + fn single_touch_spec_and_track_pad_spec_with_size() { + let width = 12345u32; + let height = 54321u32; + let mut config = Config::default(); + config + .executable_path + .replace(Executable::Kernel(PathBuf::from("kernel"))); + set_argument( + &mut config, + "single-touch", + Some(&format!("/dev/single-touch-test:{}:{}", width, height)), + ) + .unwrap(); + set_argument( + &mut config, + "trackpad", + Some(&format!("/dev/single-touch-test:{}:{}", width, height)), + ) + .unwrap(); + validate_arguments(&mut config).unwrap(); + assert_eq!( + config.virtio_single_touch.unwrap().get_size(), + (width, height) + ); + assert_eq!(config.virtio_trackpad.unwrap().get_size(), (width, height)); + } + + #[cfg(feature = "gpu")] + #[test] + fn single_touch_spec_with_size_independent_from_gpu() { + let touch_width = 12345u32; + let touch_height = 54321u32; + let display_width = 1234u32; + let display_height = 5432u32; + let mut config = Config::default(); + config + .executable_path + .replace(Executable::Kernel(PathBuf::from("kernel"))); + set_argument( + &mut config, + "single-touch", + Some(&format!( + "/dev/single-touch-test:{}:{}", + touch_width, touch_height + )), + ) + .unwrap(); + set_argument( + &mut config, + "gpu", + Some(&format!( + "width={},height={}", + display_width, display_height + )), + ) + .unwrap(); + validate_arguments(&mut config).unwrap(); + assert_eq!( + config.virtio_single_touch.unwrap().get_size(), + (touch_width, touch_height) + ); + } } -- cgit 1.4.1 From 62d95045e1c5489f2e01cb35c66326c05bbe4acc Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Wed, 12 Feb 2020 12:35:00 -0800 Subject: README: Add IRC info Copy the IRC text from the chromium docs, giving users a place to go ask questions. Change-Id: I9466cb3058823b6afd65ff2912b05d8a70d70229 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2052390 Reviewed-by: Daniel Verkamp Commit-Queue: Daniel Verkamp Tested-by: Daniel Verkamp Auto-Submit: Dylan Reid --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ba85244..51c200d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ makes crosvm unique is a focus on safety within the programming language and a sandbox around the virtual devices to protect the kernel from attack in case of an exploit in the devices. +## IRC + +The channel #crosvm on [freenode](https://webchat.freenode.net/#crosvm) is used +for technical discussion related to crosvm development and integration. + ## Building with Docker See the [README](docker/README.md) from the `docker` subdirectory to learn how -- cgit 1.4.1 From c0cbedd8d2a2b865791c9f5307f56faa5f42409f Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Mon, 10 Feb 2020 17:08:35 -0800 Subject: docker: upgrade rustc to 1.41.0 BUG=chromium:1050853 TEST=docker/build_crosvm_base.sh && docker/wrapped_smoke_test.sh Change-Id: Id98a05c6ca01c9220e692cefc1a2c1d9eca50834 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2049243 Reviewed-by: Stephen Barber Commit-Queue: Daniel Verkamp Tested-by: Daniel Verkamp --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9a4f7ce..41d755a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,7 +36,7 @@ RUN apt-get update && apt-get install -y \ ENV RUSTUP_HOME=/usr/local/rustup \ CARGO_HOME=/usr/local/cargo \ PATH=/usr/local/cargo/bin:$PATH \ - RUST_VERSION=1.38.0 \ + RUST_VERSION=1.41.0 \ RUSTFLAGS='--cfg hermetic' # Debian usually has an old rust version in the repository. Instead of using that, we use rustup to -- cgit 1.4.1 From a2d845a569494a3924e587b7d63487832cf55ab8 Mon Sep 17 00:00:00 2001 From: Stephen Barber Date: Mon, 9 Dec 2019 17:26:22 -0800 Subject: io_jail: use minijail-sys crate BUG=chromium:1032360 TEST=cargo build outside of chroot; emerge-board crosvm Cq-Depend: chromium:2055725 Change-Id: I2493f563b07aeaff4627c8b8c4b578901393dc58 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1959449 Reviewed-by: Stephen Barber Tested-by: Stephen Barber Commit-Queue: Stephen Barber --- Cargo.toml | 2 + io_jail/Cargo.toml | 2 +- io_jail/src/lib.rs | 120 ++++++++++++++++++++++++----------------------------- 3 files changed, 58 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e626ae0..9afef46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ kvm = { path = "kvm" } kvm_sys = { path = "kvm_sys" } libc = "0.2.44" libcras = "*" +minijail-sys = "*" # provided by ebuild msg_socket = { path = "msg_socket" } net_util = { path = "net_util" } p9 = { path = "p9" } @@ -85,6 +86,7 @@ assertions = { path = "assertions" } audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild data_model = { path = "data_model" } libcras = { path = "../../third_party/adhd/cras/client/libcras" } # ignored by ebuild +minijail-sys = { path = "../../aosp/external/minijail" } # ignored by ebuild poll_token_derive = { path = "sys_util/poll_token_derive" } sync = { path = "sync" } sys_util = { path = "sys_util" } diff --git a/io_jail/Cargo.toml b/io_jail/Cargo.toml index 31f8e6f..dcd5d4e 100644 --- a/io_jail/Cargo.toml +++ b/io_jail/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" [dependencies] libc = "*" -net_sys = { path = "../net_sys" } +minijail-sys = "*" diff --git a/io_jail/src/lib.rs b/io_jail/src/lib.rs index 16212c6..a927cdb 100644 --- a/io_jail/src/lib.rs +++ b/io_jail/src/lib.rs @@ -2,19 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[allow(non_snake_case)] -#[allow(non_upper_case_globals)] -mod libminijail; - use libc::pid_t; -use net_sys::{sock_filter, sock_fprog}; +use minijail_sys::*; use std::ffi::CString; use std::fmt::{self, Display}; use std::fs; use std::io; -use std::os::raw::{c_ulong, c_ushort}; +use std::os::raw::{c_char, c_ulong, c_ushort}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::ptr::{null, null_mut}; @@ -206,7 +200,7 @@ pub type Result = std::result::Result; /// partial jail is not recoverable and will instead result in killing the /// process. pub struct Minijail { - jail: *mut libminijail::minijail, + jail: *mut minijail, } impl Minijail { @@ -215,7 +209,7 @@ impl Minijail { let j = unsafe { // libminijail actually owns the minijail structure. It will live until we call // minijail_destroy. - libminijail::minijail_new() + minijail_new() }; if j.is_null() { return Err(Error::CreatingMinijail); @@ -229,22 +223,22 @@ impl Minijail { pub fn change_uid(&mut self, uid: libc::uid_t) { unsafe { - libminijail::minijail_change_uid(self.jail, uid); + minijail_change_uid(self.jail, uid); } } pub fn change_gid(&mut self, gid: libc::gid_t) { unsafe { - libminijail::minijail_change_gid(self.jail, gid); + minijail_change_gid(self.jail, gid); } } pub fn set_supplementary_gids(&mut self, ids: &[libc::gid_t]) { unsafe { - libminijail::minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); + minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); } } pub fn keep_supplementary_gids(&mut self) { unsafe { - libminijail::minijail_keep_supplementary_gids(self.jail); + minijail_keep_supplementary_gids(self.jail); } } pub fn set_rlimit( @@ -253,7 +247,7 @@ impl Minijail { cur: libc::rlim64_t, max: libc::rlim64_t, ) -> Result<()> { - let errno = unsafe { libminijail::minijail_rlimit(self.jail, kind, cur, max) }; + let errno = unsafe { minijail_rlimit(self.jail, kind, cur, max) }; if errno == 0 { Ok(()) } else { @@ -262,22 +256,22 @@ impl Minijail { } pub fn use_seccomp(&mut self) { unsafe { - libminijail::minijail_use_seccomp(self.jail); + minijail_use_seccomp(self.jail); } } pub fn no_new_privs(&mut self) { unsafe { - libminijail::minijail_no_new_privs(self.jail); + minijail_no_new_privs(self.jail); } } pub fn use_seccomp_filter(&mut self) { unsafe { - libminijail::minijail_use_seccomp_filter(self.jail); + minijail_use_seccomp_filter(self.jail); } } pub fn set_seccomp_filter_tsync(&mut self) { unsafe { - libminijail::minijail_set_seccomp_filter_tsync(self.jail); + minijail_set_seccomp_filter_tsync(self.jail); } } pub fn parse_seccomp_program(&mut self, path: &Path) -> Result<()> { @@ -298,7 +292,7 @@ impl Minijail { filter: buffer.as_ptr() as *mut sock_filter, }; unsafe { - libminijail::minijail_set_seccomp_filters(self.jail, &header); + minijail_set_seccomp_filters(self.jail, &header); } Ok(()) } @@ -314,98 +308,98 @@ impl Minijail { let filename = CString::new(pathstring).map_err(|_| Error::PathToCString(path.to_owned()))?; unsafe { - libminijail::minijail_parse_seccomp_filters(self.jail, filename.as_ptr()); + minijail_parse_seccomp_filters(self.jail, filename.as_ptr()); } Ok(()) } pub fn log_seccomp_filter_failures(&mut self) { unsafe { - libminijail::minijail_log_seccomp_filter_failures(self.jail); + minijail_log_seccomp_filter_failures(self.jail); } } pub fn use_caps(&mut self, capmask: u64) { unsafe { - libminijail::minijail_use_caps(self.jail, capmask); + minijail_use_caps(self.jail, capmask); } } pub fn capbset_drop(&mut self, capmask: u64) { unsafe { - libminijail::minijail_capbset_drop(self.jail, capmask); + minijail_capbset_drop(self.jail, capmask); } } pub fn set_ambient_caps(&mut self) { unsafe { - libminijail::minijail_set_ambient_caps(self.jail); + minijail_set_ambient_caps(self.jail); } } pub fn reset_signal_mask(&mut self) { unsafe { - libminijail::minijail_reset_signal_mask(self.jail); + minijail_reset_signal_mask(self.jail); } } pub fn run_as_init(&mut self) { unsafe { - libminijail::minijail_run_as_init(self.jail); + minijail_run_as_init(self.jail); } } pub fn namespace_pids(&mut self) { unsafe { - libminijail::minijail_namespace_pids(self.jail); + minijail_namespace_pids(self.jail); } } pub fn namespace_user(&mut self) { unsafe { - libminijail::minijail_namespace_user(self.jail); + minijail_namespace_user(self.jail); } } pub fn namespace_user_disable_setgroups(&mut self) { unsafe { - libminijail::minijail_namespace_user_disable_setgroups(self.jail); + minijail_namespace_user_disable_setgroups(self.jail); } } pub fn namespace_vfs(&mut self) { unsafe { - libminijail::minijail_namespace_vfs(self.jail); + minijail_namespace_vfs(self.jail); } } pub fn new_session_keyring(&mut self) { unsafe { - libminijail::minijail_new_session_keyring(self.jail); + minijail_new_session_keyring(self.jail); } } pub fn skip_remount_private(&mut self) { unsafe { - libminijail::minijail_skip_remount_private(self.jail); + minijail_skip_remount_private(self.jail); } } pub fn namespace_ipc(&mut self) { unsafe { - libminijail::minijail_namespace_ipc(self.jail); + minijail_namespace_ipc(self.jail); } } pub fn namespace_net(&mut self) { unsafe { - libminijail::minijail_namespace_net(self.jail); + minijail_namespace_net(self.jail); } } pub fn namespace_cgroups(&mut self) { unsafe { - libminijail::minijail_namespace_cgroups(self.jail); + minijail_namespace_cgroups(self.jail); } } pub fn remount_proc_readonly(&mut self) { unsafe { - libminijail::minijail_remount_proc_readonly(self.jail); + minijail_remount_proc_readonly(self.jail); } } pub fn set_remount_mode(&mut self, mode: c_ulong) { - unsafe { libminijail::minijail_remount_mode(self.jail, mode) } + unsafe { minijail_remount_mode(self.jail, mode) } } pub fn uidmap(&mut self, uid_map: &str) -> Result<()> { let map_cstring = CString::new(uid_map).map_err(|_| Error::StrToCString(uid_map.to_owned()))?; unsafe { - libminijail::minijail_uidmap(self.jail, map_cstring.as_ptr()); + minijail_uidmap(self.jail, map_cstring.as_ptr()); } Ok(()) } @@ -413,20 +407,19 @@ impl Minijail { let map_cstring = CString::new(gid_map).map_err(|_| Error::StrToCString(gid_map.to_owned()))?; unsafe { - libminijail::minijail_gidmap(self.jail, map_cstring.as_ptr()); + minijail_gidmap(self.jail, map_cstring.as_ptr()); } Ok(()) } pub fn inherit_usergroups(&mut self) { unsafe { - libminijail::minijail_inherit_usergroups(self.jail); + minijail_inherit_usergroups(self.jail); } } pub fn use_alt_syscall(&mut self, table_name: &str) -> Result<()> { let table_name_string = CString::new(table_name).map_err(|_| Error::StrToCString(table_name.to_owned()))?; - let ret = - unsafe { libminijail::minijail_use_alt_syscall(self.jail, table_name_string.as_ptr()) }; + let ret = unsafe { minijail_use_alt_syscall(self.jail, table_name_string.as_ptr()) }; if ret < 0 { return Err(Error::SetAltSyscallTable { errno: ret, @@ -441,7 +434,7 @@ impl Minijail { .to_str() .ok_or(Error::PathToCString(dir.to_owned()))?; let dirname = CString::new(pathstring).map_err(|_| Error::PathToCString(dir.to_owned()))?; - let ret = unsafe { libminijail::minijail_enter_chroot(self.jail, dirname.as_ptr()) }; + let ret = unsafe { minijail_enter_chroot(self.jail, dirname.as_ptr()) }; if ret < 0 { return Err(Error::SettingChrootDirectory(ret, dir.to_owned())); } @@ -453,7 +446,7 @@ impl Minijail { .to_str() .ok_or(Error::PathToCString(dir.to_owned()))?; let dirname = CString::new(pathstring).map_err(|_| Error::PathToCString(dir.to_owned()))?; - let ret = unsafe { libminijail::minijail_enter_pivot_root(self.jail, dirname.as_ptr()) }; + let ret = unsafe { minijail_enter_pivot_root(self.jail, dirname.as_ptr()) }; if ret < 0 { return Err(Error::SettingPivotRootDirectory(ret, dir.to_owned())); } @@ -485,7 +478,7 @@ impl Minijail { CString::new(fstype).map_err(|_| Error::StrToCString(fstype.to_owned()))?; let data_string = CString::new(data).map_err(|_| Error::StrToCString(data.to_owned()))?; let ret = unsafe { - libminijail::minijail_mount_with_data( + minijail_mount_with_data( self.jail, src_path.as_ptr(), dest_path.as_ptr(), @@ -508,17 +501,17 @@ impl Minijail { } pub fn mount_dev(&mut self) { unsafe { - libminijail::minijail_mount_dev(self.jail); + minijail_mount_dev(self.jail); } } pub fn mount_tmp(&mut self) { unsafe { - libminijail::minijail_mount_tmp(self.jail); + minijail_mount_tmp(self.jail); } } pub fn mount_tmp_size(&mut self, size: usize) { unsafe { - libminijail::minijail_mount_tmp_size(self.jail, size); + minijail_mount_tmp_size(self.jail, size); } } pub fn mount_bind(&mut self, src: &Path, dest: &Path, writable: bool) -> Result<()> { @@ -534,7 +527,7 @@ impl Minijail { let dest_path = CString::new(dest_os).map_err(|_| Error::StrToCString(dest_os.to_owned()))?; let ret = unsafe { - libminijail::minijail_bind( + minijail_bind( self.jail, src_path.as_ptr(), dest_path.as_ptr(), @@ -571,7 +564,7 @@ impl Minijail { args_array.push(null()); for fd in inheritable_fds { - let ret = unsafe { libminijail::minijail_preserve_fd(self.jail, *fd, *fd) }; + let ret = unsafe { minijail_preserve_fd(self.jail, *fd, *fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); } @@ -586,9 +579,7 @@ impl Minijail { // These will only be closed when this process exits. for io_fd in &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] { if !inheritable_fds.contains(io_fd) { - let ret = unsafe { - libminijail::minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd) - }; + let ret = unsafe { minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); } @@ -596,15 +587,15 @@ impl Minijail { } unsafe { - libminijail::minijail_close_open_fds(self.jail); + minijail_close_open_fds(self.jail); } let mut pid = 0; let ret = unsafe { - libminijail::minijail_run_pid_pipes( + minijail_run_pid_pipes( self.jail, cmd_cstr.as_ptr(), - args_array.as_ptr(), + args_array.as_ptr() as *const *mut c_char, &mut pid, null_mut(), null_mut(), @@ -635,7 +626,7 @@ impl Minijail { if let Some(keep_fds) = inheritable_fds { for fd in keep_fds { - let ret = libminijail::minijail_preserve_fd(self.jail, *fd, *fd); + let ret = minijail_preserve_fd(self.jail, *fd, *fd); if ret < 0 { return Err(Error::PreservingFd(ret)); } @@ -651,17 +642,16 @@ impl Minijail { // These will only be closed when this process exits. for io_fd in &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] { if inheritable_fds.is_none() || !inheritable_fds.unwrap().contains(io_fd) { - let ret = - libminijail::minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd); + let ret = minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd); if ret < 0 { return Err(Error::PreservingFd(ret)); } } } - libminijail::minijail_close_open_fds(self.jail); + minijail_close_open_fds(self.jail); - let ret = libminijail::minijail_fork(self.jail); + let ret = minijail_fork(self.jail); if ret < 0 { return Err(Error::ForkingMinijail(ret)); } @@ -675,7 +665,7 @@ impl Drop for Minijail { unsafe { // Destroys the minijail's memory. It is safe to do here because all references to // this object have been dropped. - libminijail::minijail_destroy(self.jail); + minijail_destroy(self.jail); } } } @@ -701,9 +691,9 @@ mod tests { #[test] fn create_and_free() { unsafe { - let j = libminijail::minijail_new(); + let j = minijail_new(); assert_ne!(std::ptr::null_mut(), j); - libminijail::minijail_destroy(j); + minijail_destroy(j); } let j = Minijail::new().unwrap(); -- cgit 1.4.1 From 7c43b32a36151b23a4a84144bfca0dade4e64ade Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Tue, 28 Jan 2020 18:56:09 +0900 Subject: crosvm: virtio: Use larger IDs for crosvm-specific devices Stop using 30 and 31 as device IDs of virtio-wl and virtio-tpm, as these numbers were reserved for virtio-video devices in the upstream [1]. Instead, use integers from 63, which is the largest number we can use for a virtio device ID. Note that this CL must be merged after kernels with CL:2024135 landed. [1]: https://github.com/oasis-tcs/virtio-spec/issues/67 BUG=chromium:1031512 TEST=Check if /dev/wl0 exists on ARCVM with CL:2024135 Change-Id: I267c7702d3c28642492f560170a0d1d9d6523c31 Signed-off-by: Keiichi Watanabe Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2025127 Reviewed-by: Chirantan Ekbote Reviewed-by: Stephen Barber Commit-Queue: Fergus Dall --- devices/src/virtio/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index a1701f5..7716fe0 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -75,8 +75,9 @@ const TYPE_IOMMU: u32 = 23; const TYPE_FS: u32 = 26; const TYPE_PMEM: u32 = 27; // Additional types invented by crosvm -const TYPE_WL: u32 = 30; -const TYPE_TPM: u32 = 31; +const MAX_VIRTIO_DEVICE_ID: u32 = 63; +const TYPE_WL: u32 = MAX_VIRTIO_DEVICE_ID; +const TYPE_TPM: u32 = MAX_VIRTIO_DEVICE_ID - 1; const VIRTIO_F_VERSION_1: u32 = 32; -- cgit 1.4.1 From a0742bb58ed54231f5c2bda855b9954218afa00e Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 13 Feb 2020 17:48:08 -0800 Subject: arch64: Support rng-seed to seed the kernel's rng Having this property in the chosen node in conjuction with CONFIG_RANDOM_TRUST_BOOTLOADER lets us seed the kernel's random number generator with some truly random numbers. This is useful to get a better stack canary than the default build time one and it means that you should see a message like: random: get_random_bytes called from start_kernel+0x1e8/0x39c with crng_init=1 instead of a message like random: get_random_bytes called from start_kernel+0x1e8/0x39c with crng_init=0 in the kernel logs. We seed 256 bytes here because that seems good enough to kick start the rng. BUG=None TEST=Boot vm, see crng_init=1 when guest kernel has CONFIG_RANDOM_TRUST_BOOTLOADER=y Change-Id: If3689f56cc17204a16410cf368e8413de160646c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2055526 Reviewed-by: Sonny Rao Reviewed-by: Dylan Reid Reviewed-by: Hsin-Yi Wang Reviewed-by: Stephen Barber Reviewed-by: Daniel Verkamp Tested-by: kokoro Tested-by: Stephen Boyd Commit-Queue: Stephen Boyd --- aarch64/src/fdt.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs index 23d0481..9dcafa5 100644 --- a/aarch64/src/fdt.rs +++ b/aarch64/src/fdt.rs @@ -197,6 +197,12 @@ fn create_chosen_node( let kaslr_seed = u64::from_le_bytes(kaslr_seed_bytes); property_u64(fdt, "kaslr-seed", kaslr_seed)?; + let mut rng_seed_bytes = [0u8; 256]; + random_file + .read_exact(&mut rng_seed_bytes) + .map_err(Error::FdtIoError)?; + property(fdt, "rng-seed", &rng_seed_bytes)?; + if let Some((initrd_addr, initrd_size)) = initrd { let initrd_start = initrd_addr.offset() as u32; let initrd_end = initrd_start + initrd_size as u32; -- cgit 1.4.1 From ed6c972994418cae6e09ad95511fe378b6057f53 Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Mon, 13 Jan 2020 10:48:37 +0800 Subject: devices: add acpi device emulation code in devices Add ACPI PM resource emulation code in devices, so that it can support the ACPI PM requestion from guest OS. BUG=chromium:1018674 TEST=cargo test -p devices Change-Id: I7b82b1c3a6f609136e493b55420b947afd1d5cfc Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035168 Tested-by: kokoro Reviewed-by: Tomasz Jeznach --- aarch64/src/lib.rs | 5 +++ arch/src/lib.rs | 1 + devices/src/acpi.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ devices/src/lib.rs | 2 + x86_64/src/lib.rs | 17 ++++++++ 5 files changed, 140 insertions(+) create mode 100644 devices/src/acpi.rs diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index 13855a6..67fad72 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -237,6 +237,10 @@ impl arch::LinuxArch for AArch64 { let exit_evt = EventFd::new().map_err(Error::CreateEventFd)?; + // Event used by PMDevice to notify crosvm that + // guest OS is trying to suspend. + let suspend_evt = EventFd::new().map_err(Error::CreateEventFd)?; + let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt) .map_err(|e| Error::CreateDevices(Box::new(e)))?; let (pci, pci_irqs, pid_debug_label_map) = @@ -313,6 +317,7 @@ impl arch::LinuxArch for AArch64 { io_bus, mmio_bus, pid_debug_label_map, + suspend_evt, }) } } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 16cd6fa..6af78e3 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -63,6 +63,7 @@ pub struct RunnableLinuxVm { pub io_bus: Bus, pub mmio_bus: Bus, pub pid_debug_label_map: BTreeMap, + pub suspend_evt: EventFd, } /// The device and optional jail. diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs new file mode 100644 index 0000000..983e635 --- /dev/null +++ b/devices/src/acpi.rs @@ -0,0 +1,115 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use crate::BusDevice; +use sys_util::{error, warn, EventFd}; + +/// ACPI PM resource for handling OS suspend/resume request +pub struct ACPIPMResource { + suspend_evt: EventFd, + pm1_status: u16, + pm1_enable: u16, + pm1_control: u16, + sleep_control: u8, + sleep_status: u8, +} + +impl ACPIPMResource { + /// Constructs ACPI Power Management Resouce. + pub fn new(suspend_evt: EventFd) -> ACPIPMResource { + ACPIPMResource { + suspend_evt, + pm1_status: 0, + pm1_enable: 0, + pm1_control: 0, + sleep_control: 0, + sleep_status: 0, + } + } +} + +/// the ACPI PM register's base and length. +pub const ACPIPM_RESOURCE_BASE: u64 = 0x600; +pub const ACPIPM_RESOURCE_LEN: u64 = 8; + +/// ACPI PM register value definations +const PM1_STATUS: u16 = 0; +const PM1_ENABLE: u16 = 2; +const PM1_CONTROL: u16 = 4; +const SLEEP_CONTROL: u16 = 6; +const SLEEP_STATUS: u16 = 7; +const BITMASK_PM1CNT_SLEEP_ENABLE: u16 = 0x2000; +const BITMASK_SLEEPCNT_SLEEP_ENABLE: u8 = 0x20; + +impl BusDevice for ACPIPMResource { + fn debug_label(&self) -> String { + "ACPIPMResource".to_owned() + } + + fn read(&mut self, offset: u64, data: &mut [u8]) { + let val = match offset as u16 { + PM1_STATUS => self.pm1_status, + PM1_ENABLE => self.pm1_enable, + PM1_CONTROL => self.pm1_control, + SLEEP_CONTROL => self.sleep_control as u16, + SLEEP_STATUS => self.sleep_status as u16, + _ => { + warn!("ACPIPM: Bad read from offset {}", offset); + return; + } + }; + + let val_arr = val.to_ne_bytes(); + for i in 0..std::mem::size_of::() { + if i < data.len() { + data[i] = val_arr[i]; + } + } + } + + fn write(&mut self, offset: u64, data: &[u8]) { + let max_bytes = std::mem::size_of::(); + + // only allow maximum max_bytes to write + if data.len() > max_bytes { + warn!("ACPIPM: bad write size: {}", data.len()); + return; + } + + let mut val_arr = u16::to_ne_bytes(0 as u16); + for i in 0..std::mem::size_of::() { + if i < data.len() { + val_arr[i] = data[i]; + } + } + let val = u16::from_ne_bytes(val_arr); + + match offset as u16 { + PM1_STATUS => self.pm1_status &= !val, + PM1_ENABLE => self.pm1_enable = val, + PM1_CONTROL => { + if (val & BITMASK_PM1CNT_SLEEP_ENABLE) == BITMASK_PM1CNT_SLEEP_ENABLE { + if let Err(e) = self.suspend_evt.write(1) { + error!("ACPIPM: failed to trigger suspend event: {}", e); + } + } + self.pm1_control = val & !BITMASK_PM1CNT_SLEEP_ENABLE; + } + SLEEP_CONTROL => { + let sleep_control = val as u8; + if (sleep_control & BITMASK_SLEEPCNT_SLEEP_ENABLE) == BITMASK_SLEEPCNT_SLEEP_ENABLE + { + if let Err(e) = self.suspend_evt.write(1) { + error!("ACPIPM: failed to trigger suspend event: {}", e); + } + } + self.sleep_control = sleep_control as u8 & !BITMASK_SLEEPCNT_SLEEP_ENABLE; + } + SLEEP_STATUS => self.sleep_status &= !val as u8, + _ => { + warn!("ACPIPM: Bad write to offset {}", offset); + } + }; + } +} diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 512d08b..3d6d21f 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -15,6 +15,7 @@ pub mod pl030; mod proxy; #[macro_use] mod register_space; +pub mod acpi; mod serial; pub mod split_irqchip_common; pub mod usb; @@ -22,6 +23,7 @@ mod utils; pub mod vfio; pub mod virtio; +pub use self::acpi::ACPIPMResource; pub use self::bus::Error as BusError; pub use self::bus::{Bus, BusDevice, BusRange}; pub use self::cmos::Cmos; diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 84f7dfd..f9efad4 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -360,12 +360,16 @@ impl arch::LinuxArch for X8664arch { .map_err(Error::CreatePciRoot)?; let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci))); + // Event used to notify crosvm that guest OS is trying to suspend. + let suspend_evt = EventFd::new().map_err(Error::CreateEventFd)?; + let mut io_bus = Self::setup_io_bus( &mut vm, split_irqchip, exit_evt.try_clone().map_err(Error::CloneEventFd)?, Some(pci_bus.clone()), components.memory_size, + suspend_evt.try_clone().map_err(Error::CloneEventFd)?, )?; let stdio_serial_num = @@ -434,6 +438,7 @@ impl arch::LinuxArch for X8664arch { io_bus, mmio_bus, pid_debug_label_map, + suspend_evt, }) } } @@ -653,12 +658,14 @@ impl X8664arch { /// * - `split_irqchip`: whether to use a split IRQ chip (i.e. userspace PIT/PIC/IOAPIC) /// * - `exit_evt` - the event fd object which should receive exit events /// * - `mem_size` - the size in bytes of physical ram for the guest + /// * - `suspend_evt` - the event fd object which used to suspend the vm fn setup_io_bus( vm: &mut Vm, split_irqchip: bool, exit_evt: EventFd, pci: Option>>, mem_size: u64, + suspend_evt: EventFd, ) -> Result { struct NoDevice; impl devices::BusDevice for NoDevice { @@ -724,6 +731,16 @@ impl X8664arch { .unwrap(); } + let pm = Arc::new(Mutex::new(devices::ACPIPMResource::new(suspend_evt))); + io_bus + .insert( + pm, + devices::acpi::ACPIPM_RESOURCE_BASE, + devices::acpi::ACPIPM_RESOURCE_LEN, + false, + ) + .unwrap(); + Ok(io_bus) } -- cgit 1.4.1 From 1ba620d8219cbda05ca170ad8ffefa76d6bb72d4 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Fri, 3 Jan 2020 13:38:03 -0800 Subject: qcow_utils: use DiskFile trait from disk crate Drop the local DiskFile trait definition from qcow_utils and use the one defined by the disk crate, since qcow_utils already depends on disk. In order to make the switch, use the DiskGetLen::get_len function instead of seeking to the end of the file to get the current file size. BUG=None TEST=emerge-nami crosvm TEST=cargo build -p qcow_utils Change-Id: Ie4b3b8ee0fb11ef02fbc322c5b0f9e22b0345bb0 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2056991 Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Daniel Verkamp --- qcow_utils/src/qcow_utils.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs index 97d9a94..dc9adad 100644 --- a/qcow_utils/src/qcow_utils.rs +++ b/qcow_utils/src/qcow_utils.rs @@ -7,14 +7,10 @@ use libc::{EINVAL, EIO, ENOSYS}; use std::ffi::CStr; use std::fs::OpenOptions; -use std::io::{Seek, SeekFrom}; use std::os::raw::{c_char, c_int}; -use disk::{ImageType, QcowFile}; -use sys_util::{flock, FileSetLen, FlockOperation}; - -trait DiskFile: FileSetLen + Seek {} -impl DiskFile for D {} +use disk::{DiskFile, ImageType, QcowFile}; +use sys_util::{flock, FlockOperation}; #[no_mangle] pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int { @@ -73,7 +69,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6 Err(_) => return -EINVAL, }; - let mut disk_image: Box = match image_type { + let disk_image: Box = match image_type { ImageType::Raw => Box::new(raw_image), ImageType::Qcow2 => match QcowFile::from(raw_image) { Ok(f) => Box::new(f), @@ -89,7 +85,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6 // acquired by other instances of this function as well as crosvm // itself when running a VM, so this should be safe in all cases that // can access a disk image in normal operation. - let current_size = match disk_image.seek(SeekFrom::End(0)) { + let current_size = match disk_image.get_len() { Ok(len) => len, Err(_) => return -EIO, }; -- cgit 1.4.1 From 2dae56768c0dcfb57ff07c051f5785162632073e Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Tue, 18 Feb 2020 10:31:20 -0800 Subject: docker: check out minijail in expected location Move the minijail source checkout location in the Dockerfile so that it is in the expected place relative to the crosvm checkout so that Docker (and kokoro) can successfully find it. Also update Cargo.lock for the new minijail-sys dependency; this fixes "read-only filesystem" errors from cargo running inside Docker when it tries to write the updated Cargo.lock. BUG=None TEST=docker/build_crosvm_base.sh && docker/wrapped_smoke_test.sh Change-Id: Ic399030004c2c4891a03a60474348b0bed9f01d7 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2062675 Reviewed-by: Dylan Reid Commit-Queue: Daniel Verkamp Tested-by: Daniel Verkamp --- Cargo.lock | 11 ++++++++++- docker/Dockerfile | 14 ++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1aea075..0ec0a39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,7 @@ dependencies = [ "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "libcras 0.1.0", + "minijail-sys 0.0.11", "msg_socket 0.1.0", "net_util 0.1.0", "p9 0.1.0", @@ -240,7 +241,7 @@ name = "io_jail" version = "0.1.0" dependencies = [ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", - "net_sys 0.1.0", + "minijail-sys 0.0.11", ] [[package]] @@ -310,6 +311,14 @@ dependencies = [ "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "minijail-sys" +version = "0.0.11" +dependencies = [ + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "msg_on_socket_derive" version = "0.1.0" diff --git a/docker/Dockerfile b/docker/Dockerfile index 41d755a..fecc31d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -59,12 +59,6 @@ RUN cargo install thisiznotarealpackage -q || true # Used /scratch for building dependencies which are too new or don't exist on Debian stretch. WORKDIR /scratch -# minijail does not exist in upstream linux distros. -RUN git clone https://android.googlesource.com/platform/external/minijail \ - && cd minijail \ - && make -j$(nproc) \ - && cp libminijail.so /usr/lib/x86_64-linux-gnu/ - # New libepoxy and libdrm-dev requires newer meson than is in Debian stretch. ARG MESON_COMMIT=master RUN git clone https://github.com/mesonbuild/meson \ @@ -147,6 +141,14 @@ ENV THIRD_PARTY_ROOT=$CROS_ROOT/third_party RUN mkdir -p $THIRD_PARTY_ROOT ENV PLATFORM_ROOT=$CROS_ROOT/platform RUN mkdir -p $PLATFORM_ROOT +ENV AOSP_EXTERNAL_ROOT=$CROS_ROOT/aosp/external +RUN mkdir -p $AOSP_EXTERNAL_ROOT + +# minijail does not exist in upstream linux distros. +RUN git clone https://android.googlesource.com/platform/external/minijail $AOSP_EXTERNAL_ROOT/minijail \ + && cd $AOSP_EXTERNAL_ROOT/minijail \ + && make -j$(nproc) \ + && cp libminijail.so /usr/lib/x86_64-linux-gnu/ # Pull the cras library for audio access. ARG ADHD_COMMIT=master -- cgit 1.4.1 From 79f10170da3c131ee23eee3018f981811e09a4cc Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Mon, 30 Dec 2019 14:39:39 +0800 Subject: virtio: Inject virtio-blk interrupt quickly Current blk interrupt is injected into guest after device handle a batch of requests. While this patch injects interrupt at the end of each request. So guest block will get much more interrupts and could handle request more quickly. With this patch, the guest fio read test improves 13%, while fio write doesn't get better. BUG=none TEST=run fio_read and fio_write in guest Change-Id: Ib0bd23e624dfc5d940d6cc124468b898d8ba128e Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2008338 Reviewed-by: Daniel Verkamp Tested-by: kokoro --- devices/src/virtio/block.rs | 13 +++++-------- devices/src/virtio/queue.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index 30cf4f7..65be6d5 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -298,13 +298,13 @@ impl Worker { queue_index: usize, flush_timer: &mut TimerFd, flush_timer_armed: &mut bool, - ) -> bool { + ) { let queue = &mut self.queues[queue_index]; let disk_size = self.disk_size.lock(); - let mut needs_interrupt = false; while let Some(avail_desc) = queue.pop(&self.mem) { + queue.set_notify(&self.mem, false); let desc_index = avail_desc.index; let len = match Worker::process_one_request( @@ -325,10 +325,9 @@ impl Worker { }; queue.add_used(&self.mem, desc_index, len as u32); - needs_interrupt = true; + self.interrupt.signal_used_queue(queue.vector); + queue.set_notify(&self.mem, true); } - - needs_interrupt } fn resize(&mut self, new_size: u64) -> DiskControlResult { @@ -419,9 +418,7 @@ impl Worker { error!("failed reading queue EventFd: {}", e); break 'poll; } - if self.process_queue(0, &mut flush_timer, &mut flush_timer_armed) { - self.interrupt.signal_used_queue(self.queues[0].vector); - } + self.process_queue(0, &mut flush_timer, &mut flush_timer_armed); } Token::ControlRequest => { let req = match self.control_socket.recv() { diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index 21cf63e..b3c738c 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -15,6 +15,8 @@ const VIRTQ_DESC_F_WRITE: u16 = 0x2; #[allow(dead_code)] const VIRTQ_DESC_F_INDIRECT: u16 = 0x4; +const VIRTQ_USED_F_NO_NOTIFY: u16 = 0x1; + /// An iterator over a single descriptor chain. Not to be confused with AvailIter, /// which iterates over the descriptor chain heads in a queue. pub struct DescIter<'a> { @@ -381,4 +383,16 @@ impl Queue { mem.write_obj_at_addr(self.next_used.0 as u16, used_ring.unchecked_add(2)) .unwrap(); } + + /// Enable / Disable guest notify device that requests are available on + /// the descriptor chain. + pub fn set_notify(&mut self, mem: &GuestMemory, enable: bool) { + let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap(); + if enable { + used_flags &= !VIRTQ_USED_F_NO_NOTIFY; + } else { + used_flags |= VIRTQ_USED_F_NO_NOTIFY; + } + mem.write_obj_at_addr(used_flags, self.used_ring).unwrap(); + } } -- cgit 1.4.1 From ddbe8b7e8eb141bf7ccbb0554278fff7164be166 Mon Sep 17 00:00:00 2001 From: Lingfeng Yang Date: Thu, 30 Jan 2020 10:00:36 -0800 Subject: virtio-gpu: gfxstream backend Adds a new backend type, gfxstream, that calls out to a C library implementing the actual rendering. The purpose is to allow the Cuttlefish and Android Studio Emulator teams to use crosvm with the current API-forwarding style of rendering employed in the Android Studio Emulator. Also, introduces a new key to the --gpu command line interface, backend=, which selects from different backends. Note that the previous behavior is now deprecated and will be removed after some time (when all clients switch over to backend=). The gfxstream backend itself implements a subset of 3d-related resource and context creation/transfer/execbuffer commands. Their meaning is specific to the way in which they are interpreted in the backend library. To interface with display, gfx stream backend takes a callback that is run on guest vsync. The callback is responsible for repainting the display's contents. gfx stream provides a callback, get_pixels, that can be used asynchronously. The asyncness is not taken advantage of currently but will be useful for cases where the client attached to the VMM might want to update at a different rate than guest vsync. The guts of the stream backend library is currently defined here: https://android.googlesource.com/platform/external/qemu/+/refs/heads/emu-master-dev/android-qemu2-glue/emulation/virtio-goldfish-pipe.cpp The linking of the library is controlled via the feature "gfxstream". If the feature is turned off, we use a default do-nothing stub impl. Next steps: - integrate virtio-gpu-next so as to have host coherent memory for vulkan - Figure out low latency command submit/response with SUBMIT_CMD_3DV2 BUG=b:146066070 Change-Id: If647381c15e5459cec85e2325f97e2f0a963b083 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2033305 Tested-by: kokoro Tested-by: Lingfeng Yang Reviewed-by: Lingfeng Yang Reviewed-by: Jason Macnak Reviewed-by: Zach Reizner Auto-Submit: Lingfeng Yang Commit-Queue: Zach Reizner --- Cargo.toml | 1 + devices/Cargo.toml | 1 + devices/src/virtio/gpu/mod.rs | 23 + devices/src/virtio/gpu/virtio_gfxstream_backend.rs | 722 +++++++++++++++++++++ src/main.rs | 26 + 5 files changed, 773 insertions(+) create mode 100644 devices/src/virtio/gpu/virtio_gfxstream_backend.rs diff --git a/Cargo.toml b/Cargo.toml index 9afef46..7eb7215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"] x = ["devices/x"] virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"] composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"] +gfxstream = ["devices/gfxstream"] [dependencies] arch = { path = "arch" } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 236cf4e..83aa406 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -9,6 +9,7 @@ gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"] tpm = ["protos/trunks", "tpm2"] wl-dmabuf = [] x = ["gpu_display/x"] +gfxstream = ["gpu"] [dependencies] audio_streams = "*" diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 49fdff4..0fcc453 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -6,6 +6,7 @@ mod protocol; mod virtio_2d_backend; mod virtio_3d_backend; mod virtio_backend; +mod virtio_gfxstream_backend; use std::cell::RefCell; use std::collections::VecDeque; @@ -40,6 +41,8 @@ use super::{PciCapabilityType, VirtioPciShmCap, VirtioPciShmCapID}; use self::protocol::*; use self::virtio_2d_backend::Virtio2DBackend; use self::virtio_3d_backend::Virtio3DBackend; +#[cfg(feature = "gfxstream")] +use self::virtio_gfxstream_backend::VirtioGfxStreamBackend; use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability}; use vm_control::VmMemoryControlRequestSocket; @@ -51,6 +54,8 @@ pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024; pub enum GpuMode { Mode2D, Mode3D, + #[cfg(feature = "gfxstream")] + ModeGfxStream, } #[derive(Debug)] @@ -305,6 +310,8 @@ trait Backend { enum BackendKind { Virtio2D, Virtio3D, + #[cfg(feature = "gfxstream")] + VirtioGfxStream, } impl BackendKind { @@ -313,6 +320,8 @@ impl BackendKind { match self { BackendKind::Virtio2D => Virtio2DBackend::capsets(), BackendKind::Virtio3D => Virtio3DBackend::capsets(), + #[cfg(feature = "gfxstream")] + BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::capsets(), } } @@ -321,6 +330,8 @@ impl BackendKind { match self { BackendKind::Virtio2D => Virtio2DBackend::features(), BackendKind::Virtio3D => Virtio3DBackend::features(), + #[cfg(feature = "gfxstream")] + BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::features(), } } @@ -354,6 +365,16 @@ impl BackendKind { gpu_device_socket, pci_bar, ), + #[cfg(feature = "gfxstream")] + BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::build( + possible_displays, + display_width, + display_height, + renderer_flags, + event_devices, + gpu_device_socket, + pci_bar, + ), } } } @@ -1033,6 +1054,8 @@ impl Gpu { let backend_kind = match gpu_parameters.mode { GpuMode::Mode2D => BackendKind::Virtio2D, GpuMode::Mode3D => BackendKind::Virtio3D, + #[cfg(feature = "gfxstream")] + GpuMode::ModeGfxStream => BackendKind::VirtioGfxStream, }; Gpu { diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs new file mode 100644 index 0000000..aa02e15 --- /dev/null +++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs @@ -0,0 +1,722 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Implementation of a virtio-gpu protocol command processor for +//! API passthrough. + +#![cfg(feature = "gfxstream")] + +use std::cell::RefCell; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap as Map; +use std::fs::File; +use std::mem::transmute; +use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void}; +use std::panic; +use std::rc::Rc; +use std::usize; + +use data_model::*; +use gpu_display::*; +use gpu_renderer::RendererFlags; +use resources::Alloc; +use sys_util::{error, GuestAddress, GuestMemory}; +use vm_control::VmMemoryControlRequestSocket; + +use super::protocol::GpuResponse; +pub use super::virtio_backend::{VirtioBackend, VirtioResource}; +use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_VIRGL}; + +// C definitions related to gfxstream +// In gfxstream, only write_fence is used +// (for synchronization of commands delivered) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GfxStreamRendererCallbacks { + pub version: c_int, + pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32), +} + +// virtio-gpu-3d transfer-related structs (begin) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct virgl_renderer_resource_create_args { + pub handle: u32, + pub target: u32, + pub format: u32, + pub bind: u32, + pub width: u32, + pub height: u32, + pub depth: u32, + pub array_size: u32, + pub last_level: u32, + pub nr_samples: u32, + pub flags: u32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct virgl_renderer_resource_info { + pub handle: u32, + pub virgl_format: u32, + pub width: u32, + pub height: u32, + pub depth: u32, + pub flags: u32, + pub tex_id: u32, + pub stride: u32, + pub drm_fourcc: c_int, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct virgl_box { + pub x: u32, + pub y: u32, + pub z: u32, + pub w: u32, + pub h: u32, + pub d: u32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iovec { + pub iov_base: *mut c_void, + pub iov_len: usize, +} + +// virtio-gpu-3d transfer-related structs (end) + +#[link(name = "gfxstream_backend")] +extern "C" { + + // Function to globally init gfxstream backend's internal state, taking display/renderer + // parameters. + fn gfxstream_backend_init( + display_width: u32, + display_height: u32, + display_type: u32, + renderer_cookie: *mut c_void, + renderer_flags: i32, + renderer_callbacks: *mut GfxStreamRendererCallbacks, + ); + + // virtio-gpu-3d ioctl functions (begin) + + // In gfxstream, the resource create/transfer ioctls correspond to creating buffers for API + // forwarding and the notification of new API calls forwarded by the guest, unless they + // correspond to minigbm resource targets (PIPE_TEXTURE_2D), in which case they create globally + // visible shared GL textures to support gralloc. + fn pipe_virgl_renderer_poll(); + fn pipe_virgl_renderer_resource_create( + args: *mut virgl_renderer_resource_create_args, + iov: *mut iovec, + num_iovs: u32, + ) -> c_int; + + fn pipe_virgl_renderer_resource_unref(res_handle: u32); + fn pipe_virgl_renderer_context_create(handle: u32, nlen: u32, name: *const c_char) -> c_int; + fn pipe_virgl_renderer_context_destroy(handle: u32); + fn pipe_virgl_renderer_transfer_read_iov( + handle: u32, + ctx_id: u32, + level: u32, + stride: u32, + layer_stride: u32, + box_: *mut virgl_box, + offset: u64, + iov: *mut iovec, + iovec_cnt: c_int, + ) -> c_int; + fn pipe_virgl_renderer_transfer_write_iov( + handle: u32, + ctx_id: u32, + level: c_int, + stride: u32, + layer_stride: u32, + box_: *mut virgl_box, + offset: u64, + iovec: *mut iovec, + iovec_cnt: c_uint, + ) -> c_int; + fn pipe_virgl_renderer_resource_attach_iov( + res_handle: c_int, + iov: *mut iovec, + num_iovs: c_int, + ) -> c_int; + fn pipe_virgl_renderer_resource_detach_iov( + res_handle: c_int, + iov: *mut *mut iovec, + num_iovs: *mut c_int, + ); + fn pipe_virgl_renderer_create_fence(client_fence_id: c_int, ctx_id: u32) -> c_int; + fn pipe_virgl_renderer_ctx_attach_resource(ctx_id: c_int, res_handle: c_int); + fn pipe_virgl_renderer_ctx_detach_resource(ctx_id: c_int, res_handle: c_int); + + fn stream_renderer_flush_resource_and_readback( + res_handle: u32, + x: u32, + y: u32, + width: u32, + height: u32, + pixels: *mut c_uchar, + max_bytes: u32, + ); +} + +// Fence state stuff (begin) + +struct FenceState { + latest_fence: u32, +} +impl FenceState { + pub fn write(&mut self, latest_fence: u32) { + if latest_fence > self.latest_fence { + self.latest_fence = latest_fence; + } + } +} + +struct VirglCookie { + fence_state: Rc>, +} + +extern "C" fn write_fence(cookie: *mut c_void, fence: u32) { + assert!(!cookie.is_null()); + let cookie = unsafe { &*(cookie as *mut VirglCookie) }; + + // Track the most recent fence. + let mut fence_state = cookie.fence_state.borrow_mut(); + fence_state.write(fence); +} + +const GFXSTREAM_RENDERER_CALLBACKS: &GfxStreamRendererCallbacks = &GfxStreamRendererCallbacks { + version: 1, + write_fence, +}; + +// Fence state stuff (end) + +pub struct VirtioGfxStreamBackend { + base: VirtioBackend, + + /// Mapping from resource ids to in-use GuestMemory. + resources: Map>, + + /// All commands processed by this backend are synchronous + /// and are either completed immediately or handled in a different layer, + /// so we just need to keep track of the latest created fence + /// and return that in fence_poll(). + fence_state: Rc>, +} + +impl VirtioGfxStreamBackend { + pub fn new( + display: GpuDisplay, + display_width: u32, + display_height: u32, + _gpu_device_socket: VmMemoryControlRequestSocket, + _pci_bar: Alloc, + ) -> VirtioGfxStreamBackend { + let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 })); + let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie { + fence_state: Rc::clone(&fence_state), + })); + + let renderer_flags: RendererFlags = RendererFlags::new().use_surfaceless(true); + + let display_rc_refcell = Rc::new(RefCell::new(display)); + + let scanout_surface = match (display_rc_refcell.borrow_mut()).create_surface( + None, + display_width, + display_height, + ) { + Ok(surface) => surface, + Err(e) => { + error!("Failed to create display surface: {}", e); + 0 + } + }; + + unsafe { + gfxstream_backend_init( + display_width, + display_height, + 1, /* default to shmem display */ + cookie as *mut c_void, + renderer_flags.into(), + transmute(GFXSTREAM_RENDERER_CALLBACKS), + ); + } + + VirtioGfxStreamBackend { + base: VirtioBackend { + display: Rc::clone(&display_rc_refcell), + display_width, + display_height, + event_devices: Default::default(), + scanout_resource_id: None, + scanout_surface_id: Some(scanout_surface), + cursor_resource_id: None, + cursor_surface_id: None, + }, + resources: Default::default(), + fence_state, + } + } +} + +impl Backend for VirtioGfxStreamBackend { + /// Returns the number of capsets provided by the Backend. + fn capsets() -> u32 { + 1 + } + + /// Returns the bitset of virtio features provided by the Backend. + fn features() -> u64 { + 1 << VIRTIO_GPU_F_VIRGL | 1 << VIRTIO_F_VERSION_1 + } + + /// Returns the underlying Backend. + fn build( + possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, + _renderer_flags: RendererFlags, + _event_devices: Vec, + gpu_device_socket: VmMemoryControlRequestSocket, + pci_bar: Alloc, + ) -> Option> { + let mut display_opt = None; + for display in possible_displays { + match display.build() { + Ok(c) => { + display_opt = Some(c); + break; + } + Err(e) => error!("failed to open display: {}", e), + }; + } + + let display = match display_opt { + Some(d) => d, + None => { + error!("failed to open any displays"); + return None; + } + }; + + Some(Box::new(VirtioGfxStreamBackend::new( + display, + display_width, + display_height, + gpu_device_socket, + pci_bar, + ))) + } + + /// Gets a reference to the display passed into `new`. + fn display(&self) -> &Rc> { + &self.base.display + } + + /// Processes the internal `display` events and returns `true` if the main display was closed. + fn process_display(&mut self) -> bool { + self.base.process_display() + } + + /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. + fn display_info(&self) -> [(u32, u32); 1] { + self.base.display_info() + } + + /// Attaches the given input device to the given surface of the display (to allow for input + /// from a X11 window for example). + fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { + self.base.import_event_device(event_device, scanout); + } + + /// If supported, export the resource with the given id to a file. + fn export_resource(&mut self, _id: u32) -> Option { + None + } + + /// Creates a fence with the given id that can be used to determine when the previous command + /// completed. + fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse { + unsafe { + pipe_virgl_renderer_create_fence(fence_id as i32, ctx_id); + } + GpuResponse::OkNoData + } + + /// Returns the id of the latest fence to complete. + fn fence_poll(&mut self) -> u32 { + unsafe { + pipe_virgl_renderer_poll(); + } + self.fence_state.borrow().latest_fence + } + + fn create_resource_2d( + &mut self, + _id: u32, + _width: u32, + _height: u32, + _format: u32, + ) -> GpuResponse { + // Not considered for gfxstream + GpuResponse::ErrUnspec + } + + /// Removes the guest's reference count for the given resource id. + fn unref_resource(&mut self, id: u32) -> GpuResponse { + match self.resources.remove(&id) { + Some(_) => (), + None => { + return GpuResponse::ErrInvalidResourceId; + } + } + + unsafe { + pipe_virgl_renderer_resource_unref(id); + } + + GpuResponse::OkNoData + } + + /// Sets the given resource id as the source of scanout to the display. + fn set_scanout(&mut self, _scanout_id: u32, _resource_id: u32) -> GpuResponse { + GpuResponse::OkNoData + } + + /// Flushes the given rectangle of pixels of the given resource to the display. + fn flush_resource( + &mut self, + id: u32, + _x: u32, + _y: u32, + _width: u32, + _height: u32, + ) -> GpuResponse { + // For now, always update the whole display. + let mut display_ref = self.base.display.borrow_mut(); + + let scanout_surface_id = match self.base.scanout_surface_id { + Some(id) => id, + _ => { + error!("No scanout surface created for backend!"); + return GpuResponse::ErrInvalidResourceId; + } + }; + + let fb = match display_ref.framebuffer_region( + scanout_surface_id, + 0, + 0, + self.base.display_width, + self.base.display_height, + ) { + Some(fb) => fb, + None => { + panic!( + "failed to access framebuffer for surface {}", + scanout_surface_id + ); + } + }; + + let fb_volatile_slice = fb.as_volatile_slice(); + let fb_begin = fb_volatile_slice.as_ptr() as *mut c_uchar; + let fb_bytes = fb_volatile_slice.size() as usize; + + unsafe { + stream_renderer_flush_resource_and_readback( + id, + 0, + 0, + self.base.display_width, + self.base.display_height, + fb_begin, + fb_bytes as u32, + ); + } + + display_ref.flip(scanout_surface_id); + + GpuResponse::OkNoData + } + + /// Copes the given rectangle of pixels of the given resource's backing memory to the host side + /// resource. + fn transfer_to_resource_2d( + &mut self, + _id: u32, + _x: u32, + _y: u32, + _width: u32, + _height: u32, + _src_offset: u64, + _mem: &GuestMemory, + ) -> GpuResponse { + // Not considered for gfxstream + GpuResponse::ErrInvalidResourceId + } + + /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)` + /// tuples in the guest's physical address space. + fn attach_backing( + &mut self, + id: u32, + mem: &GuestMemory, + vecs: Vec<(GuestAddress, usize)>, + ) -> GpuResponse { + match self.resources.get_mut(&id) { + Some(entry) => { + *entry = Some(mem.clone()); + } + None => { + return GpuResponse::ErrInvalidResourceId; + } + } + + let mut backing_iovecs: Vec = Vec::new(); + + for (addr, len) in vecs { + let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); + backing_iovecs.push(iovec { + iov_base: slice.as_ptr() as *mut c_void, + iov_len: len as usize, + }); + } + + unsafe { + pipe_virgl_renderer_resource_attach_iov( + id as i32, + backing_iovecs.as_mut_ptr() as *mut iovec, + backing_iovecs.len() as i32, + ); + } + GpuResponse::OkNoData + } + + /// Detaches any backing memory from the given resource, if there is any. + fn detach_backing(&mut self, id: u32) -> GpuResponse { + match self.resources.get_mut(&id) { + Some(entry) => { + *entry = None; + } + None => { + return GpuResponse::ErrInvalidResourceId; + } + } + + unsafe { + pipe_virgl_renderer_resource_detach_iov( + id as i32, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + } + GpuResponse::OkNoData + } + + fn update_cursor(&mut self, _id: u32, _x: u32, _y: u32) -> GpuResponse { + // Not considered for gfxstream + GpuResponse::OkNoData + } + + fn move_cursor(&mut self, _x: u32, _y: u32) -> GpuResponse { + // Not considered for gfxstream + GpuResponse::OkNoData + } + + fn get_capset_info(&self, index: u32) -> GpuResponse { + if 0 != index { + return GpuResponse::ErrUnspec; + } + GpuResponse::OkCapsetInfo { + id: index, + version: 1, + size: 0, + } + } + + fn get_capset(&self, id: u32, _version: u32) -> GpuResponse { + if 0 != id { + return GpuResponse::ErrUnspec; + } + GpuResponse::OkCapset(Vec::new()) + } + + fn create_renderer_context(&mut self, id: u32) -> GpuResponse { + unsafe { + pipe_virgl_renderer_context_create(id, 1, std::ptr::null_mut()); + } + GpuResponse::OkNoData + } + + fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse { + unsafe { + pipe_virgl_renderer_context_destroy(id); + } + GpuResponse::OkNoData + } + + fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { + unsafe { + pipe_virgl_renderer_ctx_attach_resource(ctx_id as i32, res_id as i32); + } + GpuResponse::OkNoData + } + + fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { + unsafe { + pipe_virgl_renderer_ctx_detach_resource(ctx_id as i32, res_id as i32); + } + GpuResponse::OkNoData + } + + fn resource_create_3d( + &mut self, + id: u32, + target: u32, + format: u32, + bind: u32, + width: u32, + height: u32, + depth: u32, + array_size: u32, + last_level: u32, + nr_samples: u32, + flags: u32, + ) -> GpuResponse { + if id == 0 { + return GpuResponse::ErrInvalidResourceId; + } + + match self.resources.entry(id) { + Entry::Vacant(slot) => { + slot.insert(None /* no guest memory attached yet */); + } + Entry::Occupied(_) => { + return GpuResponse::ErrInvalidResourceId; + } + } + + let mut create_args = virgl_renderer_resource_create_args { + handle: id, + target, + format, + bind, + width, + height, + depth, + array_size, + last_level, + nr_samples, + flags, + }; + + unsafe { + pipe_virgl_renderer_resource_create( + &mut create_args as *mut virgl_renderer_resource_create_args, + std::ptr::null_mut(), + 0, + ); + } + + GpuResponse::OkNoData + } + + fn transfer_to_resource_3d( + &mut self, + ctx_id: u32, + res_id: u32, + x: u32, + y: u32, + z: u32, + width: u32, + height: u32, + depth: u32, + level: u32, + stride: u32, + layer_stride: u32, + offset: u64, + ) -> GpuResponse { + let mut transfer_box = virgl_box { + x, + y, + z, + w: width, + h: height, + d: depth, + }; + + unsafe { + pipe_virgl_renderer_transfer_write_iov( + res_id, + ctx_id, + level as i32, + stride, + layer_stride, + &mut transfer_box as *mut virgl_box, + offset, + std::ptr::null_mut(), + 0, + ); + } + GpuResponse::OkNoData + } + + fn transfer_from_resource_3d( + &mut self, + ctx_id: u32, + res_id: u32, + x: u32, + y: u32, + z: u32, + width: u32, + height: u32, + depth: u32, + level: u32, + stride: u32, + layer_stride: u32, + offset: u64, + ) -> GpuResponse { + let mut transfer_box = virgl_box { + x, + y, + z, + w: width, + h: height, + d: depth, + }; + + unsafe { + pipe_virgl_renderer_transfer_read_iov( + res_id, + ctx_id, + level, + stride, + layer_stride, + &mut transfer_box as *mut virgl_box, + offset, + std::ptr::null_mut(), + 0, + ); + } + GpuResponse::OkNoData + } + + // Not considered for gfxstream + fn submit_command(&mut self, _ctx_id: u32, _commands: &mut [u8]) -> GpuResponse { + GpuResponse::ErrUnspec + } + + // Not considered for gfxstream + fn force_ctx_0(&mut self) {} +} diff --git a/src/main.rs b/src/main.rs index 1e86e47..569935c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,12 +124,37 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result { for (k, v) in opts { match k { + // Deprecated: Specifying --gpu= Not great as the mode can be set multiple + // times if the user specifies several modes (--gpu=2d,3d,gfxstream) "2d" | "2D" => { gpu_params.mode = GpuMode::Mode2D; } "3d" | "3D" => { gpu_params.mode = GpuMode::Mode3D; } + #[cfg(feature = "gfxstream")] + "gfxstream" => { + gpu_params.mode = GpuMode::ModeGfxStream; + } + // Preferred: Specifying --gpu,backend= + "backend" => match v { + "2d" | "2D" => { + gpu_params.mode = GpuMode::Mode2D; + } + "3d" | "3D" => { + gpu_params.mode = GpuMode::Mode3D; + } + #[cfg(feature = "gfxstream")] + "gfxstream" => { + gpu_params.mode = GpuMode::ModeGfxStream; + } + _ => { + return Err(argument::Error::InvalidValue { + value: v.to_string(), + expected: "gpu parameter 'backend' should be one of (2d|3d|gfxstream)", + }); + } + }, "egl" => match v { "true" | "" => { gpu_params.renderer_use_egl = true; @@ -1209,6 +1234,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa "[width=INT,height=INT]", "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device Possible key values: + backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol) width=INT - The width of the virtual display connected to the virtio-gpu. height=INT - The height of the virtual display connected to the virtio-gpu. egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering. -- cgit 1.4.1 From 80a8d52fac83f5f6cd0187ebcbab6e1e5bd8586f Mon Sep 17 00:00:00 2001 From: Charles William Dick Date: Wed, 15 Jan 2020 18:10:08 +0900 Subject: devices: virtio: Implement Reader::collect() and Writer::consume() Adds a method Reader::collect() to read a collection of DataInit types, and a method Writer::consume() to write a collection of DataInit types. BUG=b:147334004 TEST=cargo test -p devices Change-Id: Ib5947d30b44b74dc6cf0474e5b87778aad6f08a0 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2061516 Reviewed-by: Keiichi Watanabe Commit-Queue: Keiichi Watanabe Tested-by: Keiichi Watanabe --- devices/src/virtio/descriptor_utils.rs | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs index fcd18ec..2e5dfd3 100644 --- a/devices/src/virtio/descriptor_utils.rs +++ b/devices/src/virtio/descriptor_utils.rs @@ -6,6 +6,7 @@ use std::cmp; use std::collections::VecDeque; use std::fmt::{self, Display}; use std::io::{self, Read, Write}; +use std::iter::FromIterator; use std::marker::PhantomData; use std::mem::{size_of, MaybeUninit}; use std::ptr::copy_nonoverlapping; @@ -215,6 +216,24 @@ pub struct Reader<'a> { buffer: DescriptorChainConsumer<'a>, } +// An iterator over `DataInit` objects on readable descriptors in the descriptor chain. +struct ReaderIterator<'a, T: DataInit> { + reader: &'a mut Reader<'a>, + phantom: PhantomData, +} + +impl<'a, T: DataInit> Iterator for ReaderIterator<'a, T> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + if self.reader.available_bytes() == 0 { + None + } else { + Some(self.reader.read_obj()) + } + } +} + impl<'a> Reader<'a> { /// Construct a new Reader wrapper over `desc_chain`. pub fn new(mem: &'a GuestMemory, desc_chain: DescriptorChain<'a>) -> Result> { @@ -260,6 +279,16 @@ impl<'a> Reader<'a> { Ok(unsafe { obj.assume_init() }) } + /// Reads objects by consuming all the remaining data in the descriptor chain buffer and returns + /// them as a collection. Returns an error if the size of the remaining data is indivisible by + /// the size of an object of type `T`. + pub fn collect>, T: DataInit>(&'a mut self) -> C { + C::from_iter(ReaderIterator { + reader: self, + phantom: PhantomData, + }) + } + /// Reads data from the descriptor chain buffer into a file descriptor. /// Returns the number of bytes read from the descriptor chain buffer. /// The number of bytes read can be less than `count` if there isn't @@ -431,6 +460,11 @@ impl<'a> Writer<'a> { self.write_all(val.as_slice()) } + /// Writes a collection of objects into the descriptor chain buffer. + pub fn consume>(&mut self, vals: C) -> io::Result<()> { + vals.into_iter().map(|v| self.write_obj(v)).collect() + } + /// Returns number of bytes available for writing. May return an error if the combined /// lengths of all the buffers in the DescriptorChain would cause an overflow. pub fn available_bytes(&self) -> usize { @@ -1150,4 +1184,44 @@ mod tests { 48 ); } + + #[test] + fn consume_collect() { + use DescriptorType::*; + + let memory_start_addr = GuestAddress(0x0); + let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap(); + let vs: Vec = vec![ + 0x0101010101010101.into(), + 0x0202020202020202.into(), + 0x0303030303030303.into(), + ]; + + let write_chain = create_descriptor_chain( + &memory, + GuestAddress(0x0), + GuestAddress(0x100), + vec![(Writable, 24)], + 0, + ) + .expect("create_descriptor_chain failed"); + let mut writer = Writer::new(&memory, write_chain).expect("failed to create Writer"); + writer + .consume(vs.clone()) + .expect("failed to consume() a vector"); + + let read_chain = create_descriptor_chain( + &memory, + GuestAddress(0x0), + GuestAddress(0x100), + vec![(Readable, 24)], + 0, + ) + .expect("create_descriptor_chain failed"); + let mut reader = Reader::new(&memory, read_chain).expect("failed to create Reader"); + let vs_read = reader + .collect::>, _>() + .expect("failed to collect() values"); + assert_eq!(vs, vs_read); + } } -- cgit 1.4.1 From 546f01cb96b0b2c688257371608c64db78b5d31a Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Wed, 12 Feb 2020 21:58:47 +0800 Subject: acpipm: implement suspend and resume mechanism For suspend request from VM, will write suspend event and notify crosvm main process to pause VCPUs. For resume request, it is not from VM itself but by the resume command through crosvm socket. Resume request will notify the PM device to fill its wakeup registers with wakeup event so that when VCPUs start to run, VM can know there is wakeup from outside. BUG=chromium:1018674 TEST=cargo test -p devices Change-Id: I4724ffee10150065a62bf520076c16cbc70b7749 Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035169 Tested-by: kokoro Reviewed-by: Tomasz Jeznach --- devices/src/acpi.rs | 14 +++++++++++++- devices/src/bus.rs | 24 ++++++++++++++++++++++++ devices/src/lib.rs | 2 +- src/linux.rs | 22 ++++++++++++++++++++++ x86_64/src/lib.rs | 3 ++- 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 983e635..6d36353 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use crate::BusDevice; +use crate::{BusDevice, BusResumeDevice}; use sys_util::{error, warn, EventFd}; /// ACPI PM resource for handling OS suspend/resume request @@ -41,6 +41,8 @@ const SLEEP_CONTROL: u16 = 6; const SLEEP_STATUS: u16 = 7; const BITMASK_PM1CNT_SLEEP_ENABLE: u16 = 0x2000; const BITMASK_SLEEPCNT_SLEEP_ENABLE: u8 = 0x20; +const BITMASK_PM1CNT_WAKE_STATUS: u16 = 0x8000; +const BITMASK_SLEEPCNT_WAKE_STATUS: u8 = 0x80; impl BusDevice for ACPIPMResource { fn debug_label(&self) -> String { @@ -113,3 +115,13 @@ impl BusDevice for ACPIPMResource { }; } } + +impl BusResumeDevice for ACPIPMResource { + fn resume_imminent(&mut self) { + let val = self.pm1_status; + self.pm1_status = val | BITMASK_PM1CNT_WAKE_STATUS; + + let val = self.sleep_status; + self.sleep_status = val | BITMASK_SLEEPCNT_WAKE_STATUS; + } +} diff --git a/devices/src/bus.rs b/devices/src/bus.rs index d4b46eb..3f93974 100644 --- a/devices/src/bus.rs +++ b/devices/src/bus.rs @@ -37,6 +37,12 @@ pub trait BusDevice: Send { fn on_sandboxed(&mut self) {} } +pub trait BusResumeDevice: Send { + /// notify the devices which are invoked + /// before the VM resumes form suspend. + fn resume_imminent(&mut self) {} +} + #[derive(Debug)] pub enum Error { /// The insertion failed because the new device overlapped with an old device. @@ -104,9 +110,13 @@ impl PartialOrd for BusRange { /// /// This doesn't have any restrictions on what kind of device or address space this applies to. The /// only restriction is that no two devices can overlap in this address space. +/// +/// the 'resume_notify_devices' contains the devices which requires to be notified before the system +/// resume back from S3 suspended state. #[derive(Clone)] pub struct Bus { devices: BTreeMap>>, + resume_notify_devices: Vec>>, } impl Bus { @@ -114,6 +124,7 @@ impl Bus { pub fn new() -> Bus { Bus { devices: BTreeMap::new(), + resume_notify_devices: Vec::new(), } } @@ -208,6 +219,19 @@ impl Bus { false } } + + /// Register `device` for notifications of VM resume from suspend. + pub fn notify_on_resume(&mut self, device: Arc>) { + self.resume_notify_devices.push(device); + } + + /// Call `notify_resume` to notify the device that suspend resume is imminent. + pub fn notify_resume(&mut self) { + let devices = self.resume_notify_devices.clone(); + for dev in devices { + dev.lock().resume_imminent(); + } + } } #[cfg(test)] diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 3d6d21f..174b956 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -25,7 +25,7 @@ pub mod virtio; pub use self::acpi::ACPIPMResource; pub use self::bus::Error as BusError; -pub use self::bus::{Bus, BusDevice, BusRange}; +pub use self::bus::{Bus, BusDevice, BusRange, BusResumeDevice}; pub use self::cmos::Cmos; pub use self::i8042::I8042Device; pub use self::ioapic::Ioapic; diff --git a/src/linux.rs b/src/linux.rs index ab6c64c..0f8a848 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1638,6 +1638,7 @@ fn run_control( #[derive(PollToken)] enum Token { Exit, + Suspend, ChildSignal, CheckAvailableMemory, LowMemory, @@ -1652,6 +1653,7 @@ fn run_control( let poll_ctx = PollContext::build_with(&[ (&linux.exit_evt, Token::Exit), + (&linux.suspend_evt, Token::Suspend), (&sigchld_fd, Token::ChildSignal), ]) .map_err(Error::PollContextAdd)?; @@ -1744,6 +1746,14 @@ fn run_control( info!("vcpu requested shutdown"); break 'poll; } + Token::Suspend => { + info!("VM requested suspend"); + linux.suspend_evt.read().unwrap(); + run_mode_arc.set_and_notify(VmRunMode::Suspending); + for handle in &vcpu_handles { + let _ = handle.kill(SIGRTMIN() + 0); + } + } Token::ChildSignal => { // Print all available siginfo structs, then exit the loop. while let Some(siginfo) = sigchld_fd.read().map_err(Error::SignalFd)? { @@ -1879,6 +1889,17 @@ fn run_control( VmRunMode::Exiting => { break 'poll; } + VmRunMode::Running => { + if let VmRunMode::Suspending = + *run_mode_arc.mtx.lock() + { + linux.io_bus.notify_resume(); + } + run_mode_arc.set_and_notify(VmRunMode::Running); + for handle in &vcpu_handles { + let _ = handle.kill(SIGRTMIN() + 0); + } + } other => { run_mode_arc.set_and_notify(other); for handle in &vcpu_handles { @@ -1937,6 +1958,7 @@ fn run_control( for event in events.iter_hungup() { match event.token() { Token::Exit => {} + Token::Suspend => {} Token::ChildSignal => {} Token::CheckAvailableMemory => {} Token::LowMemory => {} diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index f9efad4..5ab0445 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -734,12 +734,13 @@ impl X8664arch { let pm = Arc::new(Mutex::new(devices::ACPIPMResource::new(suspend_evt))); io_bus .insert( - pm, + pm.clone(), devices::acpi::ACPIPM_RESOURCE_BASE, devices::acpi::ACPIPM_RESOURCE_LEN, false, ) .unwrap(); + io_bus.notify_on_resume(pm); Ok(io_bus) } -- cgit 1.4.1 From 9f7e38de575e1b6fa7b009489c554b5d27de401a Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Sun, 19 Jan 2020 16:45:32 +0800 Subject: Virtio: Add virtio block irq suppress The flag in avail descriptor supplies irq suppress, it could reduce irq injection from device, so many redundant interrupts could be removed from guest, then improve guest performance. BUG=None TEST=run fio read and fio write in guest Change-Id: I68789d8ca24d0e84d0b446db65057f4da2fac56f Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2008339 Tested-by: kokoro Reviewed-by: Daniel Verkamp --- devices/src/virtio/block.rs | 2 +- devices/src/virtio/queue.rs | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index 65be6d5..1e9c63e 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -325,7 +325,7 @@ impl Worker { }; queue.add_used(&self.mem, desc_index, len as u32); - self.interrupt.signal_used_queue(queue.vector); + queue.trigger_interrupt(&self.mem, &self.interrupt); queue.set_notify(&self.mem, true); } } diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index b3c738c..15dc3e1 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{fence, Ordering}; use sys_util::{error, GuestAddress, GuestMemory}; -use super::VIRTIO_MSI_NO_VECTOR; +use super::{Interrupt, VIRTIO_MSI_NO_VECTOR}; const VIRTQ_DESC_F_NEXT: u16 = 0x1; const VIRTQ_DESC_F_WRITE: u16 = 0x2; @@ -16,6 +16,7 @@ const VIRTQ_DESC_F_WRITE: u16 = 0x2; const VIRTQ_DESC_F_INDIRECT: u16 = 0x4; const VIRTQ_USED_F_NO_NOTIFY: u16 = 0x1; +const VIRTQ_AVAIL_F_NO_INTERRUPT: u16 = 0x1; /// An iterator over a single descriptor chain. Not to be confused with AvailIter, /// which iterates over the descriptor chain heads in a queue. @@ -395,4 +396,21 @@ impl Queue { } mem.write_obj_at_addr(used_flags, self.used_ring).unwrap(); } + + // Check Whether guest enable interrupt injection or not. + fn available_interrupt_enabled(&self, mem: &GuestMemory) -> bool { + let avail_flags: u16 = mem.read_obj_from_addr(self.avail_ring).unwrap(); + if avail_flags & VIRTQ_AVAIL_F_NO_INTERRUPT == VIRTQ_AVAIL_F_NO_INTERRUPT { + false + } else { + true + } + } + + /// inject interrupt into guest on this queue + pub fn trigger_interrupt(&self, mem: &GuestMemory, interrupt: &Interrupt) { + if self.available_interrupt_enabled(mem) { + interrupt.signal_used_queue(self.vector); + } + } } -- cgit 1.4.1 From de92ad05c71e4996852e3c90acb3eafe559bc57b Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Fri, 8 Nov 2019 18:06:29 +0800 Subject: Vfio: Enable multi vectors in irq_enable() When msix is enabled, msix will have multi vectors, this patch enable mutlti vectors in vfio_device.irq_enable() function. BUG=chromium:992270 TEST=passthrough a device with msix capability to guest, and check device msix function in guest Change-Id: I5f8265e7badec8551ff9a974462f08425ee93ab2 Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1987813 Tested-by: kokoro Reviewed-by: Daniel Verkamp --- devices/src/pci/vfio_pci.rs | 8 ++++++-- devices/src/vfio.rs | 32 +++++++++++++++++++------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index 216c6b2..766cbcf 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -468,7 +468,9 @@ impl VfioPciDevice { } if let Some(ref interrupt_evt) = self.interrupt_evt { - if let Err(e) = self.device.irq_enable(interrupt_evt, VfioIrqType::Intx) { + let mut fds = Vec::new(); + fds.push(interrupt_evt); + if let Err(e) = self.device.irq_enable(fds, VfioIrqType::Intx) { error!("Intx enable failed: {}", e); return; } @@ -524,7 +526,9 @@ impl VfioPciDevice { } }; - if let Err(e) = self.device.irq_enable(irqfd, VfioIrqType::Msi) { + let mut fds = Vec::new(); + fds.push(irqfd); + if let Err(e) = self.device.irq_enable(fds, VfioIrqType::Msi) { error!("failed to enable msi: {}", e); self.enable_intx(); return; diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs index 791dc78..88d7066 100644 --- a/devices/src/vfio.rs +++ b/devices/src/vfio.rs @@ -329,10 +329,14 @@ impl VfioDevice { }) } - /// enable vfio device's irq and associate Irqfd EventFd with device - pub fn irq_enable(&self, fd: &EventFd, irq_type: VfioIrqType) -> Result<(), VfioError> { - let mut irq_set = vec_with_array_field::(1); - irq_set[0].argsz = (mem::size_of::() + mem::size_of::()) as u32; + /// Enable vfio device's irq and associate Irqfd EventFd with device. + /// When MSIx is enabled, multi vectors will be supported, so fds is vector and the vector + /// length is the num of MSIx vectors + pub fn irq_enable(&self, fds: Vec<&EventFd>, irq_type: VfioIrqType) -> Result<(), VfioError> { + let count = fds.len(); + let u32_size = mem::size_of::(); + let mut irq_set = vec_with_array_field::(count); + irq_set[0].argsz = (mem::size_of::() + count * u32_size) as u32; irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; match irq_type { VfioIrqType::Intx => irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX, @@ -340,15 +344,17 @@ impl VfioDevice { VfioIrqType::Msix => irq_set[0].index = VFIO_PCI_MSIX_IRQ_INDEX, } irq_set[0].start = 0; - irq_set[0].count = 1; - - { - // irq_set.data could be none, bool or fd according to flags, so irq_set.data - // is u8 default, here irq_set.data is fd as u32, so 4 default u8 are combined - // together as u32. It is safe as enough space is reserved through - // vec_with_array_field(u32)<1>. - let fds = unsafe { irq_set[0].data.as_mut_slice(4) }; - fds.copy_from_slice(&fd.as_raw_fd().to_le_bytes()[..]); + irq_set[0].count = count as u32; + + // irq_set.data could be none, bool or fd according to flags, so irq_set.data + // is u8 default, here irq_set.data is fd as u32, so 4 default u8 are combined + // together as u32. It is safe as enough space is reserved through + // vec_with_array_field(u32). + let mut data = unsafe { irq_set[0].data.as_mut_slice(count * u32_size) }; + for fd in fds.iter().take(count) { + let (left, right) = data.split_at_mut(u32_size); + left.copy_from_slice(&fd.as_raw_fd().to_ne_bytes()[..]); + data = right; } // Safe as we are the owner of self and irq_set which are valid value -- cgit 1.4.1 From c24ad78624c7f15abeabf05f621c6acd050efc4b Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Fri, 14 Feb 2020 13:24:36 -0800 Subject: linux.rs: unify jail creation This change unifies two substantially similiar segments of code used to create a jail. BUG=none TEST=Ran 'build_test'. Local build, deployed to DUT, and verified that termina VM could still be used. Change-Id: Ib1f2f9bc5cfe1e6c9f3633af7e23f52e5eafe3c7 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2057744 Tested-by: Matt Delco Tested-by: kokoro Reviewed-by: Dylan Reid Commit-Queue: Matt Delco --- src/linux.rs | 163 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 0f8a848..bf2c014 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -304,55 +304,78 @@ fn get_max_open_files() -> Result { } } +struct SandboxConfig<'a> { + limit_caps: bool, + log_failures: bool, + seccomp_policy: &'a Path, + uid_map: Option<&'a str>, + gid_map: Option<&'a str>, +} + fn create_base_minijail( root: &Path, - log_failures: bool, - seccomp_policy: &Path, + r_limit: Option, + config: Option<&SandboxConfig>, ) -> Result { // All child jails run in a new user namespace without any users mapped, // they run as nobody unless otherwise configured. let mut j = Minijail::new().map_err(Error::DeviceJail)?; - j.namespace_pids(); - j.namespace_user(); - j.namespace_user_disable_setgroups(); - // Don't need any capabilities. - j.use_caps(0); + + if let Some(config) = config { + j.namespace_pids(); + j.namespace_user(); + j.namespace_user_disable_setgroups(); + if config.limit_caps { + // Don't need any capabilities. + j.use_caps(0); + } + if let Some(uid_map) = config.uid_map { + j.uidmap(uid_map).map_err(Error::SettingUidMap)?; + } + if let Some(gid_map) = config.gid_map { + j.gidmap(gid_map).map_err(Error::SettingGidMap)?; + } + // Run in an empty network namespace. + j.namespace_net(); + // Apply the block device seccomp policy. + j.no_new_privs(); + + // By default we'll prioritize using the pre-compiled .bpf over the .policy + // file (the .bpf is expected to be compiled using "trap" as the failure + // behavior instead of the default "kill" behavior). + // Refer to the code comment for the "seccomp-log-failures" + // command-line parameter for an explanation about why the |log_failures| + // flag forces the use of .policy files (and the build-time alternative to + // this run-time flag). + let bpf_policy_file = config.seccomp_policy.with_extension("bpf"); + if bpf_policy_file.exists() && !config.log_failures { + j.parse_seccomp_program(&bpf_policy_file) + .map_err(Error::DeviceJail)?; + } else { + // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP, + // which will correctly kill the entire device process if a worker + // thread commits a seccomp violation. + j.set_seccomp_filter_tsync(); + if config.log_failures { + j.log_seccomp_filter_failures(); + } + j.parse_seccomp_filters(&config.seccomp_policy.with_extension("policy")) + .map_err(Error::DeviceJail)?; + } + j.use_seccomp_filter(); + // Don't do init setup. + j.run_as_init(); + } + // Create a new mount namespace with an empty root FS. j.namespace_vfs(); j.enter_pivot_root(root).map_err(Error::DevicePivotRoot)?; - // Run in an empty network namespace. - j.namespace_net(); + // Most devices don't need to open many fds. - j.set_rlimit(libc::RLIMIT_NOFILE as i32, 1024, 1024) + let limit = if let Some(r) = r_limit { r } else { 1024u64 }; + j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit) .map_err(Error::SettingMaxOpenFiles)?; - // Apply the block device seccomp policy. - j.no_new_privs(); - - // By default we'll prioritize using the pre-compiled .bpf over the .policy - // file (the .bpf is expected to be compiled using "trap" as the failure - // behavior instead of the default "kill" behavior). - // Refer to the code comment for the "seccomp-log-failures" - // command-line parameter for an explanation about why the |log_failures| - // flag forces the use of .policy files (and the build-time alternative to - // this run-time flag). - let bpf_policy_file = seccomp_policy.with_extension("bpf"); - if bpf_policy_file.exists() && !log_failures { - j.parse_seccomp_program(&bpf_policy_file) - .map_err(Error::DeviceJail)?; - } else { - // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP, - // which will correctly kill the entire device process if a worker - // thread commits a seccomp violation. - j.set_seccomp_filter_tsync(); - if log_failures { - j.log_seccomp_filter_failures(); - } - j.parse_seccomp_filters(&seccomp_policy.with_extension("policy")) - .map_err(Error::DeviceJail)?; - } - j.use_seccomp_filter(); - // Don't do init setup. - j.run_as_init(); + Ok(j) } @@ -365,11 +388,14 @@ fn simple_jail(cfg: &Config, policy: &str) -> Result> { return Err(Error::PivotRootDoesntExist(pivot_root)); } let policy_path: PathBuf = cfg.seccomp_policy_dir.join(policy); - Ok(Some(create_base_minijail( - root_path, - cfg.seccomp_log_failures, - &policy_path, - )?)) + let config = SandboxConfig { + limit_caps: true, + log_failures: cfg.seccomp_log_failures, + seccomp_policy: &policy_path, + uid_map: None, + gid_map: None, + }; + Ok(Some(create_base_minijail(root_path, None, Some(&config))?)) } else { Ok(None) } @@ -774,45 +800,20 @@ fn create_fs_device( tag: &str, fs_cfg: virtio::fs::passthrough::Config, ) -> DeviceResult { - let mut j = Minijail::new().map_err(Error::DeviceJail)?; - - if cfg.sandbox { - j.namespace_pids(); - j.namespace_user(); - j.namespace_user_disable_setgroups(); - j.uidmap(uid_map).map_err(Error::SettingUidMap)?; - j.gidmap(gid_map).map_err(Error::SettingGidMap)?; - - // Run in an empty network namespace. - j.namespace_net(); - - j.no_new_privs(); - - // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP, which will correctly kill - // the entire device process if a worker thread commits a seccomp violation. - let seccomp_policy = cfg.seccomp_policy_dir.join("fs_device"); - j.set_seccomp_filter_tsync(); - if cfg.seccomp_log_failures { - j.log_seccomp_filter_failures(); - } - j.parse_seccomp_filters(&seccomp_policy) - .map_err(Error::DeviceJail)?; - j.use_seccomp_filter(); - - // Don't do init setup. - j.run_as_init(); - } - - // Create a new mount namespace with the source directory as the root. We need this even when - // sandboxing is disabled as the server relies on the host kernel to prevent path traversals - // from leaking out of the shared directory. - j.namespace_vfs(); - j.enter_pivot_root(src).map_err(Error::DevicePivotRoot)?; - - // The file server opens a lot of fds and needs a really high open file limit. let max_open_files = get_max_open_files()?; - j.set_rlimit(libc::RLIMIT_NOFILE as i32, max_open_files, max_open_files) - .map_err(Error::SettingMaxOpenFiles)?; + let j = if cfg.sandbox { + let seccomp_policy = cfg.seccomp_policy_dir.join("fs_device"); + let config = SandboxConfig { + limit_caps: false, + uid_map: Some(uid_map), + gid_map: Some(gid_map), + log_failures: cfg.seccomp_log_failures, + seccomp_policy: &seccomp_policy, + }; + create_base_minijail(src, Some(max_open_files), Some(&config))? + } else { + create_base_minijail(src, Some(max_open_files), None)? + }; // TODO(chirantan): Use more than one worker once the kernel driver has been fixed to not panic // when num_queues > 1. -- cgit 1.4.1 From ce03437d567717bcee61ff464bc45f0a98dd3eb9 Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Tue, 21 Jan 2020 15:01:50 -0800 Subject: tests: avoid internal error in pause test When running plugin tests it's not uncommon to see: vcpu 0 has internal error Though the tests don't actually fail on this problem. This seems to occur as a side effect of the plugin_vcpu_pause.c calling crosvm_destory_memory() before the test VM has actually finished running. The main thread will call read() on the 'kill' eventfd 5 times for: init, pause, unpause, unpause, kill The vcpu thread will call write() on the eventfd up to 8 times: init, pause #1, unpause #1, pause #2, unpause #2, pause #3, unpause #3, kill The main thread's third pause request might occur before the second pause request has been processed, in which case the vcpu thread will only be paused twice (thus there will only be 6 calls to write()). Given the unpredictable # of events I've opted to split the init/pause/unpause events to a separate eventfd and keep the 'kill' eventfd solely for its intended purpose. BUG=None TEST=ran ./build_test several times and observed no failures in the pause plugin test. Change-Id: Ie0817a4419ae6199fcc5c53496360b2bd81556e3 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2012788 Reviewed-by: Matt Delco Commit-Queue: Matt Delco Tested-by: Matt Delco Tested-by: kokoro --- tests/plugin_vcpu_pause.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/plugin_vcpu_pause.c b/tests/plugin_vcpu_pause.c index ff69b04..010d0fa 100644 --- a/tests/plugin_vcpu_pause.c +++ b/tests/plugin_vcpu_pause.c @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -36,6 +38,7 @@ #define KILL_ADDRESS 0x3f9 static char g_serial_out[16]; +static int g_next_evt; static int g_kill_evt; static bool g_paused; @@ -70,7 +73,7 @@ static void *vcpu_thread_fn(void *arg) { /* Signal the main thread that init is done */ uint64_t dummy = 1; - write(g_kill_evt, &dummy, sizeof(dummy)); + write(g_next_evt, &dummy, sizeof(dummy)); } else if (evt.kind == CROSVM_VCPU_EVENT_KIND_IO_ACCESS && evt.io_access.address_space == CROSVM_ADDRESS_SPACE_IOPORT && @@ -85,7 +88,7 @@ static void *vcpu_thread_fn(void *arg) { else if (evt.kind == CROSVM_VCPU_EVENT_KIND_PAUSED) { /* Signal that we paused */ uint64_t dummy = 1; - write(g_kill_evt, &dummy, sizeof(dummy)); + write(g_next_evt, &dummy, sizeof(dummy)); /* Wait till we can continue again */ pthread_mutex_lock(&g_pause_mutex); @@ -101,7 +104,7 @@ static void *vcpu_thread_fn(void *arg) { } /* Signal that we are no longer paused */ - write(g_kill_evt, &dummy, sizeof(dummy)); + write(g_next_evt, &dummy, sizeof(dummy)); pthread_mutex_unlock(&g_pause_mutex); } @@ -147,6 +150,12 @@ int main(int argc, char** argv) { 0xf4 }; + g_next_evt = eventfd(0, 0); + if (g_next_evt == -1) { + fprintf(stderr, "failed to create eventfd: %d\n", errno); + return 1; + } + struct crosvm *crosvm; int ret = crosvm_connect(&crosvm); if (ret) { @@ -220,7 +229,7 @@ int main(int argc, char** argv) { /* Wait till VCPU thread tells us that its initialization is done */ uint64_t dummy; - read(g_kill_evt, &dummy, sizeof(dummy)); + read(g_next_evt, &dummy, sizeof(dummy)); ret = signal_pause(crosvm); if (ret) { @@ -229,7 +238,7 @@ int main(int argc, char** argv) { } /* Wait till VCPU thread tells us it is paused */ - read(g_kill_evt, &dummy, sizeof(dummy)); + read(g_next_evt, &dummy, sizeof(dummy)); /* Try pausing VCPUs 2nd time to make sure we do not deadlock */ ret = signal_pause(crosvm); @@ -241,7 +250,7 @@ int main(int argc, char** argv) { signal_unpause(crosvm, false); /* Wait until VCPU thread tells us that it is no longer paused */ - read(g_kill_evt, &dummy, sizeof(dummy)); + read(g_next_evt, &dummy, sizeof(dummy)); /* * Try pausing VCPUs 3rd time to see if we will miss pause @@ -255,9 +264,6 @@ int main(int argc, char** argv) { signal_unpause(crosvm, true); - /* Wait until VCPU thread tells us that it is no longer paused */ - read(g_kill_evt, &dummy, sizeof(dummy)); - /* Wait for crosvm to request that we exit otherwise we will be killed. */ read(g_kill_evt, &dummy, sizeof(dummy)); -- cgit 1.4.1 From c469580e6c9b83172ba58e8305c6e5c11acfe186 Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Tue, 4 Feb 2020 16:01:55 -0800 Subject: crosvm: support kvm's hyper-v cpuid ioctl Kvm can emulate the hyper-v paravirt interface. Newer versions of kvm can advertise the features they support via an ioctl() that reports the cpuid leafs for this interface. This change adds some support for the ioctl() and plumbs it through the plugin interface so that plugins can determine the level of support available in kvm. BUG=b:144746965 TEST=Ran build_test on kernel that supports the ioctl. Added temporary code to print the cpuid leafs and verified that the output is as expected. Instrumented failure as expected from older kernels and verified that results still passed. Change-Id: I6cd7dade1793e4edb52b331d5b960685541f7ba3 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2037919 Tested-by: Matt Delco Tested-by: kokoro Reviewed-by: Daniel Verkamp Commit-Queue: Matt Delco --- crosvm_plugin/crosvm.h | 9 +++- crosvm_plugin/src/lib.rs | 50 ++++++++++++++++++++++ kvm/src/lib.rs | 35 +++++++++++++++ kvm_sys/src/lib.rs | 1 + protos/src/plugin.proto | 9 ++++ src/plugin/vcpu.rs | 11 +++++ tests/plugin_supported_cpuid.c | 96 ++++++++++++++++++++++++++++++------------ 7 files changed, 182 insertions(+), 29 deletions(-) diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h index 63763f1..ef7759d 100644 --- a/crosvm_plugin/crosvm.h +++ b/crosvm_plugin/crosvm.h @@ -47,7 +47,7 @@ extern "C" { * do not indicate anything about what version of crosvm is running. */ #define CROSVM_API_MAJOR 0 -#define CROSVM_API_MINOR 19 +#define CROSVM_API_MINOR 20 #define CROSVM_API_PATCH 0 enum crosvm_address_space { @@ -129,6 +129,13 @@ int crosvm_get_emulated_cpuid(struct crosvm*, uint32_t __entry_count, struct kvm_cpuid_entry2 *__cpuid_entries, uint32_t *__out_count); +/* + * Queries x86 hyper-v cpuid features which are emulated by kvm. + */ +int crosvm_get_hyperv_cpuid(struct crosvm_vcpu*, uint32_t __entry_count, + struct kvm_cpuid_entry2 *__cpuid_entries, + uint32_t *__out_count); + /* * Queries kvm for list of supported MSRs. */ diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index eb30e4b..13b3d9f 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -168,6 +168,7 @@ pub enum Stat { CheckExtentsion, GetSupportedCpuid, GetEmulatedCpuid, + GetHypervCpuid, GetMsrIndexList, NetGetConfig, ReserveRange, @@ -1202,6 +1203,39 @@ impl crosvm_vcpu { Ok(()) } + fn get_hyperv_cpuid( + &mut self, + cpuid_entries: &mut [kvm_cpuid_entry2], + cpuid_count: &mut usize, + ) -> result::Result<(), c_int> { + *cpuid_count = 0; + + let mut r = VcpuRequest::new(); + r.mut_get_hyperv_cpuid(); + + let response = self.vcpu_transaction(&r)?; + if !response.has_get_hyperv_cpuid() { + return Err(EPROTO); + } + + let hyperv_cpuids: &VcpuResponse_CpuidResponse = response.get_get_hyperv_cpuid(); + + *cpuid_count = hyperv_cpuids.get_entries().len(); + if *cpuid_count > cpuid_entries.len() { + return Err(E2BIG); + } + + for (proto_entry, kvm_entry) in hyperv_cpuids + .get_entries() + .iter() + .zip(cpuid_entries.iter_mut()) + { + *kvm_entry = cpuid_proto_to_kvm(proto_entry); + } + + Ok(()) + } + fn get_msrs( &mut self, msr_entries: &mut [kvm_msr_entry], @@ -1803,6 +1837,22 @@ pub unsafe extern "C" fn crosvm_vcpu_set_xcrs( to_crosvm_rc(ret) } +#[no_mangle] +pub unsafe extern "C" fn crosvm_get_hyperv_cpuid( + this: *mut crosvm_vcpu, + entry_count: u32, + cpuid_entries: *mut kvm_cpuid_entry2, + out_count: *mut u32, +) -> c_int { + let _u = record(Stat::GetHypervCpuid); + let this = &mut *this; + let cpuid_entries = from_raw_parts_mut(cpuid_entries, entry_count as usize); + let mut cpuid_count: usize = 0; + let ret = this.get_hyperv_cpuid(cpuid_entries, &mut cpuid_count); + *out_count = cpuid_count as u32; + to_crosvm_rc(ret) +} + #[no_mangle] pub unsafe extern "C" fn crosvm_vcpu_get_msrs( this: *mut crosvm_vcpu, diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index c5da845..a522478 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1502,6 +1502,24 @@ impl Vcpu { Ok(()) } + /// X86 specific call to get the system emulated hyper-v CPUID values + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + pub fn get_hyperv_cpuid(&self) -> Result { + const MAX_KVM_CPUID_ENTRIES: usize = 256; + let mut cpuid = CpuId::new(MAX_KVM_CPUID_ENTRIES); + + let ret = unsafe { + // ioctl is unsafe. The kernel is trusted not to write beyond the bounds of the memory + // allocated for the struct. The limit is read from nent, which is set to the allocated + // size(MAX_KVM_CPUID_ENTRIES) above. + ioctl_with_mut_ptr(self, KVM_GET_SUPPORTED_HV_CPUID(), cpuid.as_mut_ptr()) + }; + if ret < 0 { + return errno_result(); + } + Ok(cpuid) + } + /// X86 specific call to get the state of the "Local Advanced Programmable Interrupt Controller". /// /// See the documentation for KVM_GET_LAPIC. @@ -2331,6 +2349,23 @@ mod tests { assert_eq!(msrs.len(), 1); } + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_hyperv_cpuid() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap(); + let vm = Vm::new(&kvm, gm).unwrap(); + let vcpu = Vcpu::new(0, &kvm, &vm).unwrap(); + let cpuid = vcpu.get_hyperv_cpuid(); + // Older kernels don't support so tolerate this kind of failure. + match cpuid { + Ok(_) => {} + Err(e) => { + assert_eq!(e.errno(), EINVAL); + } + } + } + #[test] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn mp_state() { diff --git a/kvm_sys/src/lib.rs b/kvm_sys/src/lib.rs index b9748a3..4c0324d 100644 --- a/kvm_sys/src/lib.rs +++ b/kvm_sys/src/lib.rs @@ -47,6 +47,7 @@ pub mod x86 { ioctl_iow_nr!(KVM_SET_XSAVE, KVMIO, 0xa5, kvm_xsave); ioctl_ior_nr!(KVM_GET_XCRS, KVMIO, 0xa6, kvm_xcrs); ioctl_iow_nr!(KVM_SET_XCRS, KVMIO, 0xa7, kvm_xcrs); + ioctl_iowr_nr!(KVM_GET_SUPPORTED_HV_CPUID, KVMIO, 0xc1, kvm_cpuid2); } #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] diff --git a/protos/src/plugin.proto b/protos/src/plugin.proto index e2838b0..783d23b 100644 --- a/protos/src/plugin.proto +++ b/protos/src/plugin.proto @@ -334,6 +334,9 @@ message VcpuRequest { bytes state = 2; } + message CpuidRequest { + } + message GetMsrs { // The entry data will be returned in the same order as this in the // VcpuResponse::GetMsrs::entry_data array. @@ -367,6 +370,7 @@ message VcpuRequest { SetMsrs set_msrs = 6; SetCpuid set_cpuid = 7; Shutdown shutdown = 8; + CpuidRequest get_hyperv_cpuid = 9; } } @@ -417,6 +421,10 @@ message VcpuResponse { message SetState { } + message CpuidResponse { + repeated CpuidEntry entries = 1; + } + message GetMsrs { // The order of the entry_data values is the same order as the array of indices given in the // corresponding request. @@ -439,5 +447,6 @@ message VcpuResponse { GetMsrs get_msrs = 6; SetMsrs set_msrs = 7; SetCpuid set_cpuid = 8; + CpuidResponse get_hyperv_cpuid = 9; } } diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index c623bda..6bae9c4 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -595,6 +595,17 @@ impl PluginVcpu { response.mut_set_state(); let set_state = request.get_set_state(); set_vcpu_state(vcpu, set_state.set, set_state.get_state()) + } else if request.has_get_hyperv_cpuid() { + let cpuid_response = &mut response.mut_get_hyperv_cpuid().entries; + match vcpu.get_hyperv_cpuid() { + Ok(mut cpuid) => { + for entry in cpuid.mut_entries_slice() { + cpuid_response.push(cpuid_kvm_to_proto(entry)); + } + Ok(()) + } + Err(e) => Err(e), + } } else if request.has_get_msrs() { let entry_data = &mut response.mut_get_msrs().entry_data; let entry_indices = &request.get_get_msrs().entry_indices; diff --git a/tests/plugin_supported_cpuid.c b/tests/plugin_supported_cpuid.c index 0acb134..7109ff3 100644 --- a/tests/plugin_supported_cpuid.c +++ b/tests/plugin_supported_cpuid.c @@ -12,56 +12,96 @@ #include "crosvm.h" -int main(int argc, char** argv) { - struct crosvm *crosvm; - int ret = crosvm_connect(&crosvm); - if (ret) { - fprintf(stderr, "failed to connect to crosvm: %d\n", ret); - return 1; - } +typedef int (*crosvm_function)(struct crosvm*, uint32_t, + struct kvm_cpuid_entry2*, uint32_t*); +typedef int (*vcpu_function)(struct crosvm_vcpu*, uint32_t, + struct kvm_cpuid_entry2*, uint32_t*); + +// Members of union should only differ by the pointer type of 1st arg. +union cpuid_function { + crosvm_function crosvm; + vcpu_function vcpu; +}; +int test_cpuid(void* crosvm, union cpuid_function funct, const char* name) { struct kvm_cpuid_entry2 cpuids[100]; - int n_entries; - ret = crosvm_get_supported_cpuid(crosvm, 1, cpuids, &n_entries); + int n_entries = 0; + int ret = funct.crosvm(crosvm, 1, cpuids, &n_entries); if (ret >= 0) { fprintf(stderr, - "expected crosvm_get_supported_cpuids to fail with E2BIG\n"); - return 1; + "expected %s to fail with E2BIG\n", name); + return ret; } - ret = crosvm_get_supported_cpuid(crosvm, 100, cpuids, &n_entries); + ret = funct.crosvm(crosvm, 100, cpuids, &n_entries); if (ret < 0) { - fprintf(stderr, - "unexpected failure of crosvm_get_supported_cpuids: %d\n", ret); - return 1; + if (ret != -EINVAL) { + fprintf(stderr, "unexpected failure of %s: %d\n", name, ret); + } else { + fprintf(stderr, + "Query of %s failed with EINVAL (may be expected)\n", + name, ret); + } + return ret; } if (n_entries <= 1) { fprintf(stderr, - "unexpected number of supported cpuid entries: %d\n", - n_entries); + "unexpected number of cpuid entries from %s: %d\n", + name, n_entries); return 1; } + return 0; +} - ret = crosvm_get_emulated_cpuid(crosvm, 1, cpuids, &n_entries); - if (ret >= 0) { - fprintf(stderr, - "expected crosvm_get_emulated_cpuids to fail with E2BIG\n"); +int main(int argc, char** argv) { + struct crosvm* crosvm = NULL; + int ret = crosvm_connect(&crosvm); + if (ret) { + fprintf(stderr, "failed to connect to crosvm: %d\n", ret); return 1; } - ret = crosvm_get_emulated_cpuid(crosvm, 100, cpuids, &n_entries); - if (ret < 0) { - fprintf(stderr, - "unexpected failure of crosvm_get_emulated_cpuid: %d\n", ret); + struct crosvm_vcpu* vcpu = NULL; + ret = crosvm_get_vcpu(crosvm, 0, &vcpu); + if (ret) { + fprintf(stderr, "failed to get vcpu #0: %d\n", ret); return 1; } - if (n_entries < 1) { - fprintf(stderr, - "unexpected number of emulated cpuid entries: %d\n", n_entries); + union cpuid_function funct; + funct.crosvm = crosvm_get_supported_cpuid; + if (test_cpuid(crosvm, funct, "crosvm_get_supported_cpuid")) { + return 1; + } + funct.crosvm = crosvm_get_emulated_cpuid; + if (test_cpuid(crosvm, funct, "crosvm_get_emulated_cpuid")) { + return 1; + } + + ret = crosvm_start(crosvm); + if (ret) { + fprintf(stderr, "failed to start vm: %d\n", ret); return 1; } + struct crosvm_vcpu_event evt = {0}; + ret = crosvm_vcpu_wait(vcpu, &evt); + if (ret) { + fprintf(stderr, "failed to wait for vm start: %d\n", ret); + return 1; + } + if (evt.kind != CROSVM_VCPU_EVENT_KIND_INIT) { + fprintf(stderr, "Got unexpected exit type: %d\n", evt.kind); + return 1; + } + + funct.vcpu = crosvm_get_hyperv_cpuid; + ret = test_cpuid(vcpu, funct, "crosvm_get_hyperv_cpuid"); + // Older kernels don't support and return EINVAL, so allow this for now. + if (ret && ret != -EINVAL) { + fprintf(stderr, "Ignoring failure of crosvm_get_hyperv_cpuid\n"); + return 1; + } return 0; } -- cgit 1.4.1 From e73414db487afd4bebd2fb60ca80693ee6349cf5 Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Thu, 13 Feb 2020 14:08:04 -0800 Subject: crosvm: add ability to enable caps on vcpu This change primarily adds functionality to allow kvm features to be enabled on a vcpu (most of the current infra only supporst the ioctl for the vm fd). BUG=b:144746965 TEST=ran 'build_test' and verified that the added tests passed. Change-Id: I30c00b6f462377c21d477602ceba5853df953b37 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2055883 Tested-by: Matt Delco Tested-by: kokoro Reviewed-by: Dmitry Torokhov Reviewed-by: Dylan Reid Commit-Queue: Matt Delco --- crosvm_plugin/crosvm.h | 19 ++++++++++++- crosvm_plugin/src/lib.rs | 39 +++++++++++++++++++++++++++ kvm/src/lib.rs | 25 +++++++++++++++++ kvm_sys/src/x86/bindings.rs | 2 ++ protos/src/plugin.proto | 8 ++++++ src/plugin/vcpu.rs | 16 +++++++++-- tests/plugin_enable_cap.c | 65 +++++++++++++++++++++++++++++++++++++++++++++ tests/plugins.rs | 5 ++++ 8 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 tests/plugin_enable_cap.c diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h index ef7759d..db824b9 100644 --- a/crosvm_plugin/crosvm.h +++ b/crosvm_plugin/crosvm.h @@ -47,7 +47,7 @@ extern "C" { * do not indicate anything about what version of crosvm is running. */ #define CROSVM_API_MAJOR 0 -#define CROSVM_API_MINOR 20 +#define CROSVM_API_MINOR 21 #define CROSVM_API_PATCH 0 enum crosvm_address_space { @@ -114,6 +114,14 @@ int crosvm_get_shutdown_eventfd(struct crosvm*); int crosvm_check_extension(struct crosvm*, uint32_t __extension, bool *has_extension); +/* + * Enable an extended capability for the VM. Currently |__flags| and + * |__args| must be zero. No values for |__capability| are supported, + * so all calls will fail. + */ +int crosvm_enable_capability(struct crosvm*, uint32_t __capability, + uint32_t __flags, uint64_t __args[4]); + /* * Queries x86 cpuid features which are supported by the hardware and * kvm. @@ -633,6 +641,15 @@ int crosvm_vcpu_set_msrs(struct crosvm_vcpu*, uint32_t __msr_count, int crosvm_vcpu_set_cpuid(struct crosvm_vcpu*, uint32_t __cpuid_count, const struct kvm_cpuid_entry2 *__cpuid_entries); +/* + * Enable an extended capability for a vcpu. Currently |__flags| and + * |__args| must be zero. The only permitted values for |__capability| + * are KVM_CAP_HYPERV_SYNIC or KVM_CAP_HYPERV_SYNIC2, though the latter + * also depends on kernel support. + */ +int crosvm_vcpu_enable_capability(struct crosvm_vcpu*, uint32_t __capability, + uint32_t __flags, uint64_t __args[4]); + /* Gets state of LAPIC of the VCPU. */ int crosvm_vcpu_get_lapic_state(struct crosvm_vcpu *, struct kvm_lapic_state *__lapic_state); diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index 13b3d9f..8695d41 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -166,6 +166,8 @@ pub enum Stat { DestroyConnection, GetShutdownEventFd, CheckExtentsion, + EnableVmCapability, + EnableVcpuCapability, GetSupportedCpuid, GetEmulatedCpuid, GetHypervCpuid, @@ -1289,6 +1291,13 @@ impl crosvm_vcpu { self.vcpu_transaction(&r)?; Ok(()) } + + fn enable_capability(&mut self, capability: u32) -> result::Result<(), c_int> { + let mut r = VcpuRequest::new(); + r.mut_enable_capability().capability = capability; + self.vcpu_transaction(&r)?; + Ok(()) + } } // crosvm API signals success as 0 and errors as negative values @@ -1370,6 +1379,17 @@ pub unsafe extern "C" fn crosvm_check_extension( to_crosvm_rc(ret) } +#[no_mangle] +pub unsafe extern "C" fn crosvm_enable_capability( + _self_: *mut crosvm, + _capability: u32, + _flags: u32, + _args: *const u64, +) -> c_int { + let _u = record(Stat::EnableVmCapability); + -EINVAL +} + #[no_mangle] pub unsafe extern "C" fn crosvm_get_supported_cpuid( this: *mut crosvm, @@ -1895,6 +1915,25 @@ pub unsafe extern "C" fn crosvm_vcpu_set_cpuid( to_crosvm_rc(ret) } +#[no_mangle] +pub unsafe extern "C" fn crosvm_vcpu_enable_capability( + this: *mut crosvm_vcpu, + capability: u32, + flags: u32, + args: *const u64, +) -> c_int { + let _u = record(Stat::EnableVcpuCapability); + let this = &mut *this; + let args = slice::from_raw_parts(args, 4); + + if flags != 0 || args.iter().any(|v| *v != 0) { + return -EINVAL; + } + + let ret = this.enable_capability(capability); + to_crosvm_rc(ret) +} + #[no_mangle] pub unsafe extern "C" fn crosvm_vcpu_get_lapic_state( this: *mut crosvm_vcpu, diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index a522478..b900505 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1625,6 +1625,18 @@ impl Vcpu { Ok(()) } + /// Enable the specified capability. + /// See documentation for KVM_ENABLE_CAP. + pub fn kvm_enable_cap(&self, cap: &kvm_enable_cap) -> Result<()> { + // safe becuase we allocated the struct and we know the kernel will read + // exactly the size of the struct + let ret = unsafe { ioctl_with_ref(self, KVM_ENABLE_CAP(), cap) }; + if ret < 0 { + return errno_result(); + } + Ok(()) + } + /// Signals to the host kernel that this VCPU is about to be paused. /// /// See the documentation for KVM_KVMCLOCK_CTRL. @@ -2366,6 +2378,19 @@ mod tests { } } + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn enable_feature() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap(); + let vm = Vm::new(&kvm, gm).unwrap(); + vm.create_irq_chip().unwrap(); + let vcpu = Vcpu::new(0, &kvm, &vm).unwrap(); + let mut cap: kvm_enable_cap = Default::default(); + cap.cap = kvm_sys::KVM_CAP_HYPERV_SYNIC; + vcpu.kvm_enable_cap(&cap).unwrap(); + } + #[test] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn mp_state() { diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs index a9d792f..5991d17 100644 --- a/kvm_sys/src/x86/bindings.rs +++ b/kvm_sys/src/x86/bindings.rs @@ -422,7 +422,9 @@ pub const KVM_CAP_GUEST_DEBUG_HW_BPS: ::std::os::raw::c_uint = 119; pub const KVM_CAP_GUEST_DEBUG_HW_WPS: ::std::os::raw::c_uint = 120; pub const KVM_CAP_SPLIT_IRQCHIP: ::std::os::raw::c_uint = 121; pub const KVM_CAP_IOEVENTFD_ANY_LENGTH: ::std::os::raw::c_uint = 122; +pub const KVM_CAP_HYPERV_SYNIC: ::std::os::raw::c_uint = 123; pub const KVM_CAP_IMMEDIATE_EXIT: ::std::os::raw::c_uint = 136; +pub const KVM_CAP_HYPERV_SYNIC2: ::std::os::raw::c_uint = 148; pub const KVM_IRQ_ROUTING_IRQCHIP: ::std::os::raw::c_uint = 1; pub const KVM_IRQ_ROUTING_MSI: ::std::os::raw::c_uint = 2; pub const KVM_IRQ_ROUTING_S390_ADAPTER: ::std::os::raw::c_uint = 3; diff --git a/protos/src/plugin.proto b/protos/src/plugin.proto index 783d23b..442eef0 100644 --- a/protos/src/plugin.proto +++ b/protos/src/plugin.proto @@ -359,6 +359,10 @@ message VcpuRequest { message Shutdown { } + message EnableCapability { + uint32 capability = 1; + } + // The type of the message is determined by which of these oneof fields is present in the // protobuf. oneof message { @@ -371,6 +375,7 @@ message VcpuRequest { SetCpuid set_cpuid = 7; Shutdown shutdown = 8; CpuidRequest get_hyperv_cpuid = 9; + EnableCapability enable_capability = 10; } } @@ -435,6 +440,8 @@ message VcpuResponse { message SetCpuid {} + message EnableCapability {} + // This is zero on success, and a negative integer on failure. sint32 errno = 1; // The field present here is always the same as the one present in the corresponding @@ -448,5 +455,6 @@ message VcpuResponse { SetMsrs set_msrs = 7; SetCpuid set_cpuid = 8; CpuidResponse get_hyperv_cpuid = 9; + EnableCapability enable_capability = 10; } } diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index 6bae9c4..ae96789 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -20,8 +20,8 @@ use assertions::const_assert; use data_model::DataInit; use kvm::{CpuId, Vcpu}; use kvm_sys::{ - kvm_debugregs, kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_msr_entry, kvm_msrs, kvm_regs, - kvm_sregs, kvm_vcpu_events, kvm_xcrs, KVM_CPUID_FLAG_SIGNIFCANT_INDEX, + kvm_debugregs, kvm_enable_cap, kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_msr_entry, kvm_msrs, + kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs, KVM_CPUID_FLAG_SIGNIFCANT_INDEX, }; use protobuf::stream::CodedOutputStream; use protos::plugin::*; @@ -675,6 +675,18 @@ impl PluginVcpu { cpuid_entry.edx = request_entry.edx; } vcpu.set_cpuid2(&cpuid) + } else if request.has_enable_capability() { + response.mut_enable_capability(); + let capability = request.get_enable_capability().capability; + if capability != kvm_sys::KVM_CAP_HYPERV_SYNIC + && capability != kvm_sys::KVM_CAP_HYPERV_SYNIC2 + { + Err(SysError::new(EINVAL)) + } else { + let mut cap: kvm_enable_cap = Default::default(); + cap.cap = capability; + vcpu.kvm_enable_cap(&cap) + } } else if request.has_shutdown() { return Err(SysError::new(EPIPE)); } else { diff --git a/tests/plugin_enable_cap.c b/tests/plugin_enable_cap.c new file mode 100644 index 0000000..60977ef --- /dev/null +++ b/tests/plugin_enable_cap.c @@ -0,0 +1,65 @@ +/* + * Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include + +#include "crosvm.h" + +int main(int argc, char** argv) { + struct crosvm* crosvm = NULL; + uint64_t cap_args[4] = {0}; + + int ret = crosvm_connect(&crosvm); + if (ret) { + fprintf(stderr, "failed to connect to crosvm: %d\n", ret); + return 1; + } + + struct crosvm_vcpu* vcpu = NULL; + ret = crosvm_get_vcpu(crosvm, 0, &vcpu); + if (ret) { + fprintf(stderr, "failed to get vcpu #0: %d\n", ret); + return 1; + } + + ret = crosvm_start(crosvm); + if (ret) { + fprintf(stderr, "failed to start vm: %d\n", ret); + return 1; + } + + struct crosvm_vcpu_event evt = {0}; + ret = crosvm_vcpu_wait(vcpu, &evt); + if (ret) { + fprintf(stderr, "failed to wait for vm start: %d\n", ret); + return 1; + } + if (evt.kind != CROSVM_VCPU_EVENT_KIND_INIT) { + fprintf(stderr, "Got unexpected exit type: %d\n", evt.kind); + return 1; + } + + ret = crosvm_enable_capability(crosvm, 0, 0, cap_args); + if (ret != -EINVAL) { + fprintf(stderr, "Unexpected crosvm_enable_capability result: %d\n", + ret); + return 1; + } + + ret = crosvm_vcpu_enable_capability(vcpu, KVM_CAP_HYPERV_SYNIC, 0, + cap_args); + if (ret) { + fprintf(stderr, "crosvm_vcpu_enable_capability() failed: %d\n", ret); + return 1; + } + + return 0; +} diff --git a/tests/plugins.rs b/tests/plugins.rs index d56f4ce..c45096f 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -260,6 +260,11 @@ fn test_supported_cpuid() { test_plugin(include_str!("plugin_supported_cpuid.c")); } +#[test] +fn test_enable_cap() { + test_plugin(include_str!("plugin_enable_cap.c")); +} + #[test] fn test_msr_index_list() { test_plugin(include_str!("plugin_msr_index_list.c")); -- cgit 1.4.1 From 521646a401f8ce66cf26ed21abe5e18ac929fe33 Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Fri, 8 Nov 2019 18:36:55 +0800 Subject: Vfio: Emulate MSI-x When VFIO device have msix capability, vfio kernel doesn't emulate msix, so all the msix emulation are handled by crosvm. This include msix capability register read/write, msix table read/write, msix pba table read/write. BUG=chromium:992270 TEST=passthrough a device with msix capabilty to guest, and check device msix function in guest Change-Id: Ic39737662a5051ac6b9e29aad227d3d4946190a8 Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1987814 Tested-by: kokoro Reviewed-by: Daniel Verkamp --- devices/src/pci/msix.rs | 17 +++- devices/src/pci/vfio_pci.rs | 194 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 197 insertions(+), 14 deletions(-) diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs index 0b9f391..b1dc672 100644 --- a/devices/src/pci/msix.rs +++ b/devices/src/pci/msix.rs @@ -13,9 +13,9 @@ use vm_control::{MaybeOwnedFd, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse}; use data_model::DataInit; const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048; -const MSIX_TABLE_ENTRIES_MODULO: u64 = 16; -const MSIX_PBA_ENTRIES_MODULO: u64 = 8; -const BITS_PER_PBA_ENTRY: usize = 64; +pub const MSIX_TABLE_ENTRIES_MODULO: u64 = 16; +pub const MSIX_PBA_ENTRIES_MODULO: u64 = 8; +pub const BITS_PER_PBA_ENTRY: usize = 64; const FUNCTION_MASK_BIT: u16 = 0x4000; const MSIX_ENABLE_BIT: u16 = 0x8000; @@ -433,6 +433,17 @@ impl MsixConfig { pub fn get_msi_socket(&self) -> RawFd { self.msi_device_socket.as_ref().as_raw_fd() } + + /// Return irqfd of MSI-X Table entry + /// + /// # Arguments + /// * 'vector' - the index to the MSI-X table entry + pub fn get_irqfd(&self, vector: usize) -> Option<&EventFd> { + match self.irq_vec.get(vector) { + Some(irq) => Some(&irq.irqfd), + None => None, + } + } } // It is safe to implement DataInit; all members are simple numbers and any value is valid. diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index 766cbcf..178d4ae 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -17,7 +17,9 @@ use vm_control::{ VmMemoryRequest, VmMemoryResponse, }; -use crate::pci::msix::MsixConfig; +use crate::pci::msix::{ + MsixConfig, BITS_PER_PBA_ENTRY, MSIX_PBA_ENTRIES_MODULO, MSIX_TABLE_ENTRIES_MODULO, +}; use crate::pci::pci_device::{Error as PciDeviceError, PciDevice}; use crate::pci::{PciClassCode, PciInterruptPin}; @@ -299,7 +301,6 @@ const PCI_MSIX_PBA: u32 = 0x08; // Pending bit Array offset const PCI_MSIX_PBA_BIR: u32 = 0x07; // BAR index const PCI_MSIX_PBA_OFFSET: u32 = 0xFFFFFFF8; // Offset into specified BAR -#[allow(dead_code)] struct VfioMsixCap { config: MsixConfig, offset: u32, @@ -335,6 +336,99 @@ impl VfioMsixCap { pba_offset, } } + + // only msix control register is writable and need special handle in pci r/w + fn is_msix_control_reg(&self, offset: u32, size: u32) -> bool { + let control_start = self.offset + PCI_MSIX_FLAGS; + let control_end = control_start + 2; + + if offset < control_end && offset + size > control_start { + true + } else { + false + } + } + + fn read_msix_control(&self, data: &mut u32) { + *data = self.config.read_msix_capability(*data); + } + + fn write_msix_control(&mut self, data: &[u8]) -> Option { + let old_enabled = self.config.enabled(); + + self.config + .write_msix_capability(PCI_MSIX_FLAGS.into(), data); + + let new_enabled = self.config.enabled(); + if !old_enabled && new_enabled { + Some(VfioMsiChange::Enable) + } else if old_enabled && !new_enabled { + Some(VfioMsiChange::Disable) + } else { + None + } + } + + fn is_msix_table(&self, bar_index: u32, offset: u64) -> bool { + let table_size: u64 = (self.table_size * (MSIX_TABLE_ENTRIES_MODULO as u16)).into(); + if bar_index != self.table_pci_bar + || offset < self.table_offset + || offset >= self.table_offset + table_size + { + false + } else { + true + } + } + + fn read_table(&self, offset: u64, data: &mut [u8]) { + let offset = offset - self.table_offset; + self.config.read_msix_table(offset, data); + } + + fn write_table(&mut self, offset: u64, data: &[u8]) { + let offset = offset - self.table_offset; + self.config.write_msix_table(offset, data); + } + + fn is_msix_pba(&self, bar_index: u32, offset: u64) -> bool { + let pba_size: u64 = (((self.table_size + BITS_PER_PBA_ENTRY as u16 - 1) + / BITS_PER_PBA_ENTRY as u16) + * MSIX_PBA_ENTRIES_MODULO as u16) as u64; + if bar_index != self.pba_pci_bar + || offset < self.pba_offset + || offset >= self.pba_offset + pba_size + { + false + } else { + true + } + } + + fn read_pba(&self, offset: u64, data: &mut [u8]) { + let offset = offset - self.pba_offset; + self.config.read_pba_entries(offset, data); + } + + fn write_pba(&mut self, offset: u64, data: &[u8]) { + let offset = offset - self.pba_offset; + self.config.write_pba_entries(offset, data); + } + + fn get_msix_irqfds(&self) -> Option> { + let mut irqfds = Vec::new(); + + for i in 0..self.table_size { + let irqfd = self.config.get_irqfd(i as usize); + if let Some(fd) = irqfd { + irqfds.push(fd); + } else { + return None; + } + } + + Some(irqfds) + } } struct MmioInfo { @@ -352,7 +446,6 @@ enum DeviceData { } /// Implements the Vfio Pci device, then a pci device is added into vm -#[allow(dead_code)] pub struct VfioPciDevice { device: Arc, config: VfioPciConfig, @@ -503,14 +596,24 @@ impl VfioPciDevice { self.irq_type = None; } - fn enable_msi(&mut self) { - if let Some(irq_type) = &self.irq_type { - match irq_type { - VfioIrqType::Intx => self.disable_intx(), - _ => return, - } + fn disable_irqs(&mut self) { + match self.irq_type { + Some(VfioIrqType::Msi) => self.disable_msi(), + Some(VfioIrqType::Msix) => self.disable_msix(), + _ => (), } + // Above disable_msi() or disable_msix() will enable intx again. + // so disable_intx here again. + match self.irq_type { + Some(VfioIrqType::Intx) => self.disable_intx(), + _ => (), + } + } + + fn enable_msi(&mut self) { + self.disable_irqs(); + let irqfd = match &self.msi_cap { Some(cap) => { if let Some(fd) = cap.get_msi_irqfd() { @@ -546,6 +649,37 @@ impl VfioPciDevice { self.enable_intx(); } + fn enable_msix(&mut self) { + self.disable_irqs(); + + let irqfds = match &self.msix_cap { + Some(cap) => cap.get_msix_irqfds(), + None => return, + }; + + if let Some(fds) = irqfds { + if let Err(e) = self.device.irq_enable(fds, VfioIrqType::Msix) { + error!("failed to enable msix: {}", e); + self.enable_intx(); + return; + } + } else { + self.enable_intx(); + return; + } + + self.irq_type = Some(VfioIrqType::Msix); + } + + fn disable_msix(&mut self) { + if let Err(e) = self.device.irq_disable(VfioIrqType::Msix) { + error!("failed to disable msix: {}", e); + return; + } + + self.enable_intx(); + } + fn add_bar_mmap(&self, index: u32, bar_addr: u64) -> Vec { let mut mem_map: Vec = Vec::new(); if self.device.get_region_flags(index) & VFIO_REGION_INFO_FLAG_MMAP != 0 { @@ -829,6 +963,10 @@ impl PciDevice for VfioPciDevice { // Clear multifunction flags as pci_root doesn't // support multifunction. config &= !PCI_MULTI_FLAG; + } else if let Some(msix_cap) = &self.msix_cap { + if msix_cap.is_msix_control_reg(reg, 4) { + msix_cap.read_msix_control(&mut config); + } } // Quirk for intel graphic, set stolen memory size to 0 in pci_cfg[0x51] @@ -855,6 +993,18 @@ impl PciDevice for VfioPciDevice { None => (), } + msi_change = None; + if let Some(msix_cap) = self.msix_cap.as_mut() { + if msix_cap.is_msix_control_reg(start as u32, data.len() as u32) { + msi_change = msix_cap.write_msix_control(data); + } + } + match msi_change { + Some(VfioMsiChange::Enable) => self.enable_msix(), + Some(VfioMsiChange::Disable) => self.disable_msix(), + None => (), + } + // if guest enable memory access, then enable bar mappable once if start == PCI_COMMAND as u64 && data.len() == 2 @@ -871,7 +1021,17 @@ impl PciDevice for VfioPciDevice { fn read_bar(&mut self, addr: u64, data: &mut [u8]) { if let Some(mmio_info) = self.find_region(addr) { let offset = addr - mmio_info.start; - self.device.region_read(mmio_info.bar_index, data, offset); + let bar_index = mmio_info.bar_index; + if let Some(msix_cap) = &self.msix_cap { + if msix_cap.is_msix_table(bar_index, offset) { + msix_cap.read_table(offset, data); + return; + } else if msix_cap.is_msix_pba(bar_index, offset) { + msix_cap.read_pba(offset, data); + return; + } + } + self.device.region_read(bar_index, data, offset); } } @@ -889,7 +1049,19 @@ impl PciDevice for VfioPciDevice { } let offset = addr - mmio_info.start; - self.device.region_write(mmio_info.bar_index, data, offset); + let bar_index = mmio_info.bar_index; + + if let Some(msix_cap) = self.msix_cap.as_mut() { + if msix_cap.is_msix_table(bar_index, offset) { + msix_cap.write_table(offset, data); + return; + } else if msix_cap.is_msix_pba(bar_index, offset) { + msix_cap.write_pba(offset, data); + return; + } + } + + self.device.region_write(bar_index, data, offset); } } } -- cgit 1.4.1 From c6b73e30c86cc42f8bb7069f1b01c1fcfa60aa25 Mon Sep 17 00:00:00 2001 From: Chirantan Ekbote Date: Thu, 20 Feb 2020 15:53:06 +0900 Subject: linux.rs: Refactor 9p device jail Give the 9p device the same jail as the fs device. In particular it needs a higher max open file limit and should map the current euid/egid in its user namespace rather than always using the crosvm user. BUG=b:147258662 TEST=`tast run vm.Blogbench.p9` Change-Id: I12e7ba7b651da4bae1435e0598b62fe2c35ff1bf Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2065254 Tested-by: Chirantan Ekbote Tested-by: kokoro Auto-Submit: Chirantan Ekbote Reviewed-by: Daniel Verkamp Commit-Queue: Chirantan Ekbote --- src/linux.rs | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index bf2c014..662dea5 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -825,25 +825,36 @@ fn create_fs_device( }) } -fn create_9p_device(cfg: &Config, src: &Path, tag: &str) -> DeviceResult { - let (jail, root) = match simple_jail(&cfg, "9p_device")? { - Some(mut jail) => { - // The shared directory becomes the root of the device's file system. - let root = Path::new("/"); - jail.mount_bind(src, root, true)?; +fn create_9p_device( + cfg: &Config, + uid_map: &str, + gid_map: &str, + src: &Path, + tag: &str, +) -> DeviceResult { + let max_open_files = get_max_open_files()?; + let (jail, root) = if cfg.sandbox { + let seccomp_policy = cfg.seccomp_policy_dir.join("9p_device"); + let config = SandboxConfig { + limit_caps: false, + uid_map: Some(uid_map), + gid_map: Some(gid_map), + log_failures: cfg.seccomp_log_failures, + seccomp_policy: &seccomp_policy, + }; - // We want bind mounts from the parent namespaces to propagate into the 9p server's - // namespace. - jail.set_remount_mode(libc::MS_SLAVE); + let mut jail = create_base_minijail(src, Some(max_open_files), Some(&config))?; + // We want bind mounts from the parent namespaces to propagate into the 9p server's + // namespace. + jail.set_remount_mode(libc::MS_SLAVE); - add_crosvm_user_to_jail(&mut jail, "p9")?; - (Some(jail), root) - } - None => { - // There's no bind mount so we tell the server to treat the source directory as the - // root. - (None, src) - } + // The shared directory becomes the root of the device's file system. + let root = Path::new("/"); + (Some(jail), root) + } else { + // There's no mount namespace so we tell the server to treat the source directory as the + // root. + (None, src) }; let dev = virtio::P9::new(root, tag).map_err(Error::P9DeviceNew)?; @@ -1093,7 +1104,7 @@ fn create_virtio_devices( let dev = match kind { SharedDirKind::FS => create_fs_device(cfg, uid_map, gid_map, src, tag, fs_cfg.clone())?, - SharedDirKind::P9 => create_9p_device(cfg, src, tag)?, + SharedDirKind::P9 => create_9p_device(cfg, uid_map, gid_map, src, tag)?, }; devs.push(dev); } -- cgit 1.4.1 From 04b44e3df02e87de704080e131ed90afdf2dfd7e Mon Sep 17 00:00:00 2001 From: Zhuocheng Ding Date: Mon, 2 Dec 2019 15:50:16 +0800 Subject: devices: IOAPIC: implement interrupt routing This change implements MSI routing and injection, so that service_irq can actually inject an interrupt into guest. BUG=chromium:908689 TEST=Unit tests in file. Change-Id: I2db4f00f569db56f5765c707faaa87c64fd3da9f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1945795 Reviewed-by: Stephen Barber Tested-by: kokoro Commit-Queue: Zhuocheng Ding --- devices/src/ioapic.rs | 75 ++++++++++++++++++++++++++++++------- devices/src/split_irqchip_common.rs | 2 +- x86_64/src/lib.rs | 16 +++++++- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 6f8e358..3b5ea80 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -8,7 +8,10 @@ use crate::split_irqchip_common::*; use crate::BusDevice; use bit_field::*; -use sys_util::warn; +use kvm::Vm; +use msg_socket::{MsgReceiver, MsgSender}; +use sys_util::{error, warn, EventFd, Result}; +use vm_control::{VmIrqRequest, VmIrqRequestSocket, VmIrqResponse}; #[bitfield] #[derive(Clone, Copy, PartialEq)] @@ -88,6 +91,8 @@ pub struct Ioapic { redirect_table: [RedirectionTableEntry; kvm::NUM_IOAPIC_PINS], // IOREGSEL is technically 32 bits, but only bottom 8 are writable: all others are fixed to 0. ioregsel: u8, + irqfd: Vec, + socket: VmIrqRequestSocket, } impl BusDevice for Ioapic { @@ -148,17 +153,24 @@ impl BusDevice for Ioapic { } impl Ioapic { - pub fn new() -> Ioapic { + pub fn new(vm: &mut Vm, socket: VmIrqRequestSocket) -> Result { let mut entry = RedirectionTableEntry::new(); entry.set_interrupt_mask(true); let entries = [entry; kvm::NUM_IOAPIC_PINS]; - Ioapic { + let mut irqfd = vec![]; + for i in 0..kvm::NUM_IOAPIC_PINS { + irqfd.push(EventFd::new()?); + vm.register_irqfd(&irqfd[i], i as u32)?; + } + Ok(Ioapic { id: 0, rtc_remote_irr: false, current_interrupt_level_bitmap: 0, redirect_table: entries, ioregsel: 0, - } + irqfd, + socket, + }) } // The ioapic must be informed about EOIs in order to avoid sending multiple interrupts of the @@ -218,8 +230,7 @@ impl Ioapic { return false; } - // TODO(mutexlox): Pulse (assert and deassert) interrupt - let injected = true; + let injected = self.irqfd[irq].write(1).is_ok(); if entry.get_trigger_mode() == TriggerMode::Level && level && injected { entry.set_remote_irr(true); @@ -267,13 +278,42 @@ impl Ioapic { // is the fix for this. } - // TODO(mutexlox): route MSI. if self.redirect_table[index].get_trigger_mode() == TriggerMode::Level && self.current_interrupt_level_bitmap & (1 << index) != 0 && !self.redirect_table[index].get_interrupt_mask() { self.service_irq(index, true); } + + let mut address = MsiAddressMessage::new(); + let mut data = MsiDataMessage::new(); + let entry = &self.redirect_table[index]; + address.set_destination_mode(entry.get_dest_mode()); + address.set_destination_id(entry.get_dest_id()); + address.set_always_0xfee(0xfee); + data.set_vector(entry.get_vector()); + data.set_delivery_mode(entry.get_delivery_mode()); + data.set_trigger(entry.get_trigger_mode()); + + let request = VmIrqRequest::AddMsiRoute { + gsi: index as u32, + msi_address: address.get(0, 32), + msi_data: data.get(0, 32) as u32, + }; + if let Err(e) = self.socket.send(&request) { + error!("IOAPIC: failed to send AddMsiRoute request: {}", e); + return; + } + match self.socket.recv() { + Ok(response) => { + if let VmIrqResponse::Err(e) = response { + error!("IOAPIC: failed to add msi route: {}", e); + } + } + Err(e) => { + error!("IOAPIC: failed to receive AddMsiRoute response: {}", e); + } + } } } } @@ -307,6 +347,15 @@ mod tests { const DEFAULT_VECTOR: u8 = 0x3a; const DEFAULT_DESTINATION_ID: u8 = 0x5f; + fn new() -> Ioapic { + let kvm = kvm::Kvm::new().unwrap(); + let gm = sys_util::GuestMemory::new(&vec![(sys_util::GuestAddress(0), 0x1000)]).unwrap(); + let mut vm = Vm::new(&kvm, gm).unwrap(); + vm.enable_split_irqchip().unwrap(); + let (_, device_socket) = msg_socket::pair::().unwrap(); + Ioapic::new(&mut vm, device_socket).unwrap() + } + fn set_up(trigger: TriggerMode) -> (Ioapic, usize) { let irq = kvm::NUM_IOAPIC_PINS - 1; let ioapic = set_up_with_irq(irq, trigger); @@ -314,7 +363,7 @@ mod tests { } fn set_up_with_irq(irq: usize, trigger: TriggerMode) -> Ioapic { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); set_up_redirection_table_entry(&mut ioapic, irq, trigger); ioapic } @@ -377,7 +426,7 @@ mod tests { #[test] fn write_read_ioregsel() { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); let data_write = [0x0f, 0xf0, 0x01, 0xff]; let mut data_read = [0; 4]; @@ -391,7 +440,7 @@ mod tests { // Verify that version register is actually read-only. #[test] fn write_read_ioaic_reg_version() { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); let before = read_reg(&mut ioapic, IOAPIC_REG_VERSION); let data_write = !before; @@ -402,7 +451,7 @@ mod tests { // Verify that only bits 27:24 of the IOAPICID are readable/writable. #[test] fn write_read_ioapic_reg_id() { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); write_reg(&mut ioapic, IOAPIC_REG_ID, 0x1f3e5d7c); assert_eq!(read_reg(&mut ioapic, IOAPIC_REG_ID), 0x0f000000); @@ -411,7 +460,7 @@ mod tests { // Write to read-only register IOAPICARB. #[test] fn write_read_ioapic_arbitration_id() { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); let data_write_id = 0x1f3e5d7c; let expected_result = 0x0f000000; @@ -436,7 +485,7 @@ mod tests { #[test] #[should_panic(expected = "index out of bounds: the len is 24 but the index is 24")] fn service_invalid_irq() { - let mut ioapic = Ioapic::new(); + let mut ioapic = self::new(); ioapic.service_irq(kvm::NUM_IOAPIC_PINS, false); } diff --git a/devices/src/split_irqchip_common.rs b/devices/src/split_irqchip_common.rs index 65ba809..b54c35a 100644 --- a/devices/src/split_irqchip_common.rs +++ b/devices/src/split_irqchip_common.rs @@ -48,7 +48,7 @@ pub struct MsiAddressMessage { #[bitfield] #[derive(Clone, Copy, PartialEq)] -struct MsiDataMessage { +pub struct MsiDataMessage { vector: BitField8, #[bits = 3] delivery_mode: DeliveryMode, diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 5ab0445..487273e 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -581,12 +581,26 @@ impl X8664arch { /// * `split_irqchip` - Whether to use a split IRQ chip. /// * `mem` - The memory to be used by the guest. fn create_vm(kvm: &Kvm, split_irqchip: bool, mem: GuestMemory) -> Result { - let vm = Vm::new(&kvm, mem).map_err(Error::CreateVm)?; + let mut vm = Vm::new(&kvm, mem).map_err(Error::CreateVm)?; let tss_addr = GuestAddress(0xfffbd000); vm.set_tss_addr(tss_addr).map_err(Error::SetTssAddr)?; if !split_irqchip { vm.create_pit().map_err(Error::CreatePit)?; vm.create_irq_chip().map_err(Error::CreateIrqChip)?; + } else { + for i in 0..kvm::NUM_IOAPIC_PINS { + // Add dummy MSI routes to replace the default IRQChip routes. + let route = IrqRoute { + gsi: i as u32, + source: IrqSource::Msi { + address: 0, + data: 0, + }, + }; + // Safe to ignore errors because errors are caused by the default routes and dummy + // MSI routes will always be registered. + let _ = vm.add_irq_route_entry(route); + } } Ok(vm) } -- cgit 1.4.1 From 2647a1916da0220deab4ad1ae89a92c1ba81dcf4 Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Fri, 6 Dec 2019 18:50:49 +0800 Subject: Vfio: Disable msix bar's mmap If vfio device's bar is mmappable, vcpu could access it directly through ept without trapping. But msix's table and pba exist on pci bar, they must be trapped and emulated by crosvm, so these bars mmappable must be disabled. BUG=chromium:992270 TEST=pass through a device with msix cap to guest, then test device function in guest. Change-Id: If7504a924902c940e00cc759c1ca64a116bbca17 Signed-off-by: Xiong Zhang Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1987815 Tested-by: kokoro Reviewed-by: Daniel Verkamp --- devices/src/pci/vfio_pci.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index 178d4ae..c031793 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -415,6 +415,14 @@ impl VfioMsixCap { self.config.write_pba_entries(offset, data); } + fn is_msix_bar(&self, bar_index: u32) -> bool { + if bar_index == self.table_pci_bar || bar_index == self.pba_pci_bar { + true + } else { + false + } + } + fn get_msix_irqfds(&self) -> Option> { let mut irqfds = Vec::new(); @@ -683,6 +691,14 @@ impl VfioPciDevice { fn add_bar_mmap(&self, index: u32, bar_addr: u64) -> Vec { let mut mem_map: Vec = Vec::new(); if self.device.get_region_flags(index) & VFIO_REGION_INFO_FLAG_MMAP != 0 { + // the bar storing msix table and pba couldn't mmap. + // these bars should be trapped, so that msix could be emulated. + if let Some(msix_cap) = &self.msix_cap { + if msix_cap.is_msix_bar(index) { + return mem_map; + } + } + let mmaps = self.device.get_region_mmap(index); if mmaps.is_empty() { return mem_map; -- cgit 1.4.1 From 4f48eab6027a3ab3e4b87893f7ec275f67f62922 Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Wed, 19 Feb 2020 12:24:43 -0800 Subject: build_test.py: set env vars for cross compile Now that minijail-sys is added a simple run of ./build_test fails with messages like: error: failed to run custom build command for `minijail-sys v0.0.11 (/mnt/host/source/src/aosp/external/minijail)` thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: CrossCompilation', src/libcore/result.rs:1165:5 This change adds various environment variables to address the problem. BUG=None TEST=./build_test.py --arm-sysroot /build/cheza/ --aarch64-sysroot /build/kevin64/ Change-Id: Iba15ceafa35ba3ab2d08dc5827af3cb8ee07530c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2064821 Reviewed-by: Daniel Verkamp Tested-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Matt Delco --- build_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build_test.py b/build_test.py index 81d09db..f0a78c4 100755 --- a/build_test.py +++ b/build_test.py @@ -160,10 +160,20 @@ def check_build(sysroot, triple, kind, test_it, clean): is_release = kind == 'release' + # The pkgconfig dir could be in either lib or lib64 depending on the target. + # Rather than checking to see which one is valid, just add both and let + # pkg-config search. + libdir = os.path.join(sysroot, 'usr', 'lib', 'pkgconfig') + lib64dir = os.path.join(sysroot, 'usr', 'lib64', 'pkgconfig') + env = os.environ.copy() env['TARGET_CC'] = '%s-clang'%triple env['SYSROOT'] = sysroot env['CARGO_TARGET_DIR'] = target_path + env['PKG_CONFIG_ALLOW_CROSS'] = '1' + env['PKG_CONFIG_LIBDIR'] = libdir + ':' + lib64dir + env['PKG_CONFIG_SYSROOT_DIR'] = sysroot + env['RUSTFLAGS'] = '-C linker=' + env['TARGET_CC'] if test_it: if not test_target(triple, is_release, env): -- cgit 1.4.1 From 9ca6039b030a5c83062cfec9a5ff52f42814fa13 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Mon, 23 Dec 2019 18:27:11 -0800 Subject: Support generating and opening backing files The new functionality can be invoked through "crosvm create_qcow2 --backing_file=backing new_file". The old behavior of creating a qcow image with a particular size is still available with its original syntax. This is relevant to implement as by default something like qemu-img will create a new image that assumes the backing file is raw or qcow, while crosvm can use its knowledge of other formats (such as composite disk, and later android sparse) to determine the true size of the backing file. TEST=unit tests BUG=b:140069322 Change-Id: I22de6a79c6d8566a9fcb0bc8124e2d74fea9ca55 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1982833 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Daniel Verkamp --- disk/src/qcow/mod.rs | 162 +++++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 80 ++++++++++++++++++++----- 2 files changed, 195 insertions(+), 47 deletions(-) diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs index add4f48..1e3a451 100644 --- a/disk/src/qcow/mod.rs +++ b/disk/src/qcow/mod.rs @@ -10,31 +10,35 @@ use data_model::{VolatileMemory, VolatileSlice}; use libc::{EINVAL, ENOSPC, ENOTSUP}; use remain::sorted; use sys_util::{ - error, FileAllocate, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen, FileSync, - PunchHole, SeekHole, WriteZeroesAt, + error, AsRawFds, FileAllocate, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen, + FileSync, PunchHole, SeekHole, WriteZeroesAt, }; use std::cmp::{max, min}; use std::fmt::{self, Display}; -use std::fs::File; +use std::fs::{File, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::size_of; use std::os::unix::io::{AsRawFd, RawFd}; +use std::str; use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::refcount::RefCount; use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache}; -use crate::{DiskFile, DiskGetLen}; +use crate::{create_disk_file, DiskFile, DiskGetLen}; #[sorted] #[derive(Debug)] pub enum Error { - BackingFilesNotSupported, + BackingFileIo(io::Error), + BackingFileOpen(Box), + BackingFileTooLong(usize), CompressedBlocksNotSupported, EvictingCache(io::Error), FileTooBig(u64), GettingFileSize(io::Error), GettingRefcount(refcount::Error), + InvalidBackingFileName(str::Utf8Error), InvalidClusterIndex, InvalidClusterSize, InvalidIndex, @@ -74,7 +78,11 @@ impl Display for Error { #[sorted] match self { - BackingFilesNotSupported => write!(f, "backing files not supported"), + BackingFileIo(e) => write!(f, "backing file io error: {}", e), + BackingFileOpen(e) => write!(f, "backing file open error: {}", *e), + BackingFileTooLong(len) => { + write!(f, "backing file name is too long: {} bytes over", len) + } CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"), EvictingCache(e) => write!(f, "failed to evict cache: {}", e), FileTooBig(size) => write!( @@ -84,6 +92,7 @@ impl Display for Error { ), GettingFileSize(e) => write!(f, "failed to get file size: {}", e), GettingRefcount(e) => write!(f, "failed to get refcount: {}", e), + InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e), InvalidClusterIndex => write!(f, "invalid cluster index"), InvalidClusterSize => write!(f, "invalid cluster size"), InvalidIndex => write!(f, "invalid index"), @@ -144,8 +153,14 @@ const COMPRESSED_FLAG: u64 = 1 << 62; const CLUSTER_USED_FLAG: u64 = 1 << 63; const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1 << 0; +// The format supports a "header extension area", that crosvm does not use. +const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8; + +// Defined by the specification +const MAX_BACKING_FILE_SIZE: u32 = 1023; + /// Contains the information from the header of a qcow file. -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct QcowHeader { pub magic: u32, pub version: u32, @@ -172,6 +187,9 @@ pub struct QcowHeader { pub autoclear_features: u64, pub refcount_order: u32, pub header_size: u32, + + // Post-header entries + pub backing_file_path: Option, } // Reads the next u16 from the file. @@ -211,7 +229,7 @@ impl QcowHeader { return Err(Error::InvalidMagic); } - Ok(QcowHeader { + let mut header = QcowHeader { magic, version: read_u32_from_file(f)?, backing_file_offset: read_u64_from_file(f)?, @@ -230,24 +248,50 @@ impl QcowHeader { autoclear_features: read_u64_from_file(f)?, refcount_order: read_u32_from_file(f)?, header_size: read_u32_from_file(f)?, - }) + backing_file_path: None, + }; + if header.backing_file_size > MAX_BACKING_FILE_SIZE { + return Err(Error::BackingFileTooLong(header.backing_file_size as usize)); + } + if header.backing_file_offset != 0 { + f.seek(SeekFrom::Start(header.backing_file_offset)) + .map_err(Error::ReadingHeader)?; + let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize]; + f.read_exact(&mut backing_file_name_bytes) + .map_err(Error::ReadingHeader)?; + header.backing_file_path = Some( + String::from_utf8(backing_file_name_bytes) + .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?, + ); + } + Ok(header) } - /// Create a header for the given `size`. - pub fn create_for_size(size: u64) -> QcowHeader { + pub fn create_for_size_and_path(size: u64, backing_file: Option<&str>) -> Result { let cluster_bits: u32 = DEFAULT_CLUSTER_BITS; let cluster_size: u32 = 0x01 << cluster_bits; + let max_length: usize = + (cluster_size - V3_BARE_HEADER_SIZE - QCOW_EMPTY_HEADER_EXTENSION_SIZE) as usize; + if let Some(path) = backing_file { + if path.len() > max_length { + return Err(Error::BackingFileTooLong(path.len() - max_length)); + } + } // L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses. let l2_size: u32 = cluster_size / size_of::() as u32; let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32; let num_l2_clusters: u32 = div_round_up_u32(num_clusters, l2_size); let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, cluster_size); let header_clusters = div_round_up_u32(size_of::() as u32, cluster_size); - QcowHeader { + Ok(QcowHeader { magic: QCOW_MAGIC, version: 3, - backing_file_offset: 0, - backing_file_size: 0, + backing_file_offset: (if backing_file.is_none() { + 0 + } else { + V3_BARE_HEADER_SIZE + QCOW_EMPTY_HEADER_EXTENSION_SIZE + }) as u64, + backing_file_size: backing_file.map_or(0, |x| x.len()) as u32, cluster_bits: DEFAULT_CLUSTER_BITS, size, crypt_method: 0, @@ -277,7 +321,8 @@ impl QcowHeader { autoclear_features: 0, refcount_order: DEFAULT_REFCOUNT_ORDER, header_size: V3_BARE_HEADER_SIZE, - } + backing_file_path: backing_file.map(|x| String::from(x)), + }) } /// Write the header to `file`. @@ -312,6 +357,11 @@ impl QcowHeader { write_u64_to_file(file, self.autoclear_features)?; write_u32_to_file(file, self.refcount_order)?; write_u32_to_file(file, self.header_size)?; + write_u32_to_file(file, 0)?; // header extension type: end of header extension area + write_u32_to_file(file, 0)?; // length of header extension data: 0 + if let Some(backing_file_path) = self.backing_file_path.as_ref() { + write!(file, "{}", backing_file_path).map_err(Error::WritingHeader)?; + } // Set the file length by seeking and writing a zero to the last byte. This avoids needing // a `File` instead of anything that implements seek as the `file` argument. @@ -365,7 +415,7 @@ pub struct QcowFile { // List of unreferenced clusters available to be used. unref clusters become available once the // removal of references to them have been synced to disk. avail_clusters: Vec, - //TODO(dgreid) Add support for backing files. - backing_file: Option>>, + backing_file: Option>, } impl QcowFile { @@ -394,10 +444,18 @@ impl QcowFile { return Err(Error::FileTooBig(header.size)); } - // No current support for backing files. - if header.backing_file_offset != 0 { - return Err(Error::BackingFilesNotSupported); - } + let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() { + let path = backing_file_path.clone(); + let backing_raw_file = OpenOptions::new() + .read(true) + .open(path) + .map_err(Error::BackingFileIo)?; + let backing_file = create_disk_file(backing_raw_file) + .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; + Some(backing_file) + } else { + None + }; // Only support two byte refcounts. let refcount_bits: u64 = 0x01u64 @@ -412,7 +470,6 @@ impl QcowFile { if header.refcount_table_clusters == 0 { return Err(Error::NoRefcountClusters); } - offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?; offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; // refcount table must be a cluster boundary, and within the file's virtual or actual size. @@ -444,7 +501,7 @@ impl QcowFile { let mut raw_file = QcowRawFile::from(file, cluster_size).ok_or(Error::InvalidClusterSize)?; if refcount_rebuild_required { - QcowFile::rebuild_refcounts(&mut raw_file, header)?; + QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?; } let l2_size = cluster_size / size_of::() as u64; @@ -500,6 +557,7 @@ impl QcowFile { current_offset: 0, unref_clusters: Vec::new(), avail_clusters: Vec::new(), + backing_file, }; // Check that the L1 and refcount tables fit in a 64bit address space. @@ -518,8 +576,27 @@ impl QcowFile { } /// Creates a new QcowFile at the given path. - pub fn new(mut file: File, virtual_size: u64) -> Result { - let header = QcowHeader::create_for_size(virtual_size); + pub fn new(file: File, virtual_size: u64) -> Result { + let header = QcowHeader::create_for_size_and_path(virtual_size, None)?; + QcowFile::new_from_header(file, header) + } + + /// Creates a new QcowFile at the given path. + pub fn new_from_backing(file: File, backing_file_name: &str) -> Result { + let backing_raw_file = OpenOptions::new() + .read(true) + .open(backing_file_name) + .map_err(Error::BackingFileIo)?; + let backing_file = + create_disk_file(backing_raw_file).map_err(|e| Error::BackingFileOpen(Box::new(e)))?; + let size = backing_file.get_len().map_err(Error::BackingFileIo)?; + let header = QcowHeader::create_for_size_and_path(size, Some(backing_file_name))?; + let mut result = QcowFile::new_from_header(file, header)?; + result.backing_file = Some(backing_file); + Ok(result) + } + + fn new_from_header(mut file: File, header: QcowHeader) -> Result { file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?; header.write_to(&mut file)?; @@ -862,9 +939,9 @@ impl QcowFile { // Find all references clusters and rebuild refcounts. set_header_refcount(&mut refcounts, cluster_size)?; - set_l1_refcounts(&mut refcounts, header, cluster_size)?; - set_data_refcounts(&mut refcounts, header, cluster_size, raw_file)?; - set_refcount_table_refcounts(&mut refcounts, header, cluster_size)?; + set_l1_refcounts(&mut refcounts, header.clone(), cluster_size)?; + set_data_refcounts(&mut refcounts, header.clone(), cluster_size, raw_file)?; + set_refcount_table_refcounts(&mut refcounts, header.clone(), cluster_size)?; // Allocate clusters to store the new reference count blocks. let ref_table = alloc_refblocks( @@ -1424,9 +1501,13 @@ impl Drop for QcowFile { } } -impl AsRawFd for QcowFile { - fn as_raw_fd(&self) -> RawFd { - self.raw_file.file().as_raw_fd() +impl AsRawFds for QcowFile { + fn as_raw_fds(&self) -> Vec { + let mut fds = vec![self.raw_file.file().as_raw_fd()]; + if let Some(backing) = &self.backing_file { + fds.append(&mut backing.as_raw_fds()); + } + fds } } @@ -1739,10 +1820,11 @@ mod tests { #[test] fn default_header() { - let header = QcowHeader::create_for_size(0x10_0000); + let header = QcowHeader::create_for_size_and_path(0x10_0000, None); let shm = SharedMemory::anon().unwrap(); let mut disk_file: File = shm.into(); header + .expect("Failed to create header.") .write_to(&mut disk_file) .expect("Failed to write header to shm."); disk_file.seek(SeekFrom::Start(0)).unwrap(); @@ -1756,6 +1838,24 @@ mod tests { }); } + #[test] + fn header_with_backing() { + let header = QcowHeader::create_for_size_and_path(0x10_0000, Some("/my/path/to/a/file")) + .expect("Failed to create header."); + let shm = SharedMemory::anon().unwrap(); + let mut disk_file: File = shm.into(); + header + .write_to(&mut disk_file) + .expect("Failed to write header to shm."); + disk_file.seek(SeekFrom::Start(0)).unwrap(); + let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header."); + assert_eq!( + header.backing_file_path, + Some(String::from("/my/path/to/a/file")) + ); + assert_eq!(read_header.backing_file_path, header.backing_file_path); + } + #[test] fn invalid_magic() { let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb]; diff --git a/src/main.rs b/src/main.rs index 569935c..ce3e660 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1385,34 +1385,82 @@ fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> { vms_request(&VmRequest::BalloonCommand(command), args) } -fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> { - if args.len() != 2 { - print_help("crosvm create_qcow2", "PATH SIZE", &[]); - println!("Create a new QCOW2 image at `PATH` of the specified `SIZE` in bytes."); +fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> { + let arguments = [ + Argument::positional("PATH", "where to create the qcow2 image"), + Argument::positional("[SIZE]", "the expanded size of the image"), + Argument::value( + "backing_file", + "path/to/file", + " the file to back the image", + ), + ]; + let mut positional_index = 0; + let mut file_path = String::from(""); + let mut size: Option = None; + let mut backing_file: Option = None; + set_arguments(args, &arguments[..], |name, value| { + match (name, positional_index) { + ("", 0) => { + // NAME + positional_index += 1; + file_path = value.unwrap().to_owned(); + } + ("", 1) => { + // [SIZE] + positional_index += 1; + size = Some(value.unwrap().parse::().map_err(|_| { + argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: "SIZE should be a nonnegative integer", + } + })?); + } + ("", _) => { + return Err(argument::Error::TooManyArguments( + "Expected at most 2 positional arguments".to_owned(), + )); + } + ("backing_file", _) => { + backing_file = value.map(|x| x.to_owned()); + } + _ => unreachable!(), + }; + Ok(()) + }) + .map_err(|e| { + error!("Unable to parse command line arguments: {}", e); + })?; + if file_path.len() == 0 || !(size.is_some() ^ backing_file.is_some()) { + print_help("crosvm create_qcow2", "PATH [SIZE]", &arguments); + println!( + "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or +with a '--backing_file'." + ); return Err(()); } - let file_path = args.nth(0).unwrap(); - let size: u64 = match args.nth(0).unwrap().parse::() { - Ok(n) => n, - Err(_) => { - error!("Failed to parse size of the disk."); - return Err(()); - } - }; let file = OpenOptions::new() .create(true) .read(true) .write(true) + .truncate(true) .open(&file_path) .map_err(|e| { error!("Failed opening qcow file at '{}': {}", file_path, e); })?; - QcowFile::new(file, size).map_err(|e| { - error!("Failed to create qcow file at '{}': {}", file_path, e); - })?; - + match (size, backing_file) { + (Some(size), None) => QcowFile::new(file, size).map_err(|e| { + error!("Failed to create qcow file at '{}': {}", file_path, e); + })?, + (None, Some(backing_file)) => { + QcowFile::new_from_backing(file, &backing_file).map_err(|e| { + error!("Failed to create qcow file at '{}': {}", file_path, e); + })? + } + _ => unreachable!(), + }; Ok(()) } -- cgit 1.4.1 From d8144a56e26ca09e2c7ff97ed63c57e7e7965674 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Thu, 26 Dec 2019 12:28:11 -0800 Subject: Read from the backing file if present on read miss Reads to qcow files with backing files will fall through to the backing file if there is no allocated cluster. As of this change, a write will still trash the cluster and hide any data already present. TEST=unit tests BUG=b:140069322 Change-Id: Iba353fa1e7c25bb6267eb96b30b8f5a6ac61d423 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1982831 Tested-by: kokoro Commit-Queue: Daniel Verkamp Reviewed-by: Daniel Verkamp --- disk/src/qcow/mod.rs | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs index 1e3a451..dfc8acd 100644 --- a/disk/src/qcow/mod.rs +++ b/disk/src/qcow/mod.rs @@ -620,6 +620,10 @@ impl QcowFile { Ok(qcow) } + pub fn set_backing_file(&mut self, backing: Option>) { + self.backing_file = backing; + } + /// Returns the `QcowHeader` for this file. pub fn header(&self) -> &QcowHeader { &self.header @@ -1459,6 +1463,8 @@ impl QcowFile { if let Some(offset) = file_offset { cb(Some(self.raw_file.file_mut()), nread, offset, count)?; + } else if let Some(backing) = self.backing_file.as_mut() { + cb(Some(backing.as_mut()), nread, curr_addr, count)?; } else { cb(None, nread, 0, count)?; } @@ -1795,17 +1801,20 @@ mod tests { ] } - fn with_basic_file(header: &[u8], mut testfn: F) - where - F: FnMut(File), - { + fn basic_file(header: &[u8]) -> File { let shm = SharedMemory::anon().unwrap(); let mut disk_file: File = shm.into(); disk_file.write_all(&header).unwrap(); disk_file.set_len(0x1_0000_0000).unwrap(); disk_file.seek(SeekFrom::Start(0)).unwrap(); + disk_file + } - testfn(disk_file); // File closed when the function exits. + fn with_basic_file(header: &[u8], mut testfn: F) + where + F: FnMut(File), + { + testfn(basic_file(header)); // File closed when the function exits. } fn with_default_file(file_size: u64, mut testfn: F) @@ -1970,6 +1979,22 @@ mod tests { }); } + #[test] + fn write_read_start_backing() { + let disk_file = basic_file(&valid_header()); + let mut backing = QcowFile::from(disk_file).unwrap(); + backing + .write(b"test first bytes") + .expect("Failed to write test string."); + let mut buf = [0u8; 4]; + let wrapping_disk_file = basic_file(&valid_header()); + let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap(); + wrapping.set_backing_file(Some(Box::new(backing))); + wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek."); + wrapping.read(&mut buf).expect("Failed to read."); + assert_eq!(&buf, b"test"); + } + #[test] fn offset_write_read() { with_basic_file(&valid_header(), |disk_file: File| { -- cgit 1.4.1 From f84c2298e9d7138be0998c289825128144234862 Mon Sep 17 00:00:00 2001 From: Chirantan Ekbote Date: Fri, 21 Feb 2020 16:37:27 +0900 Subject: linux.rs: Don't pivot_root when using host's root directory pivot_root(2) will fail with EBUSY if we try to pivot_root to "/". Check for this case and skip the pivot_root if necessary. BUG=b:147258662 TEST=`tast run vm.Virtiofs` Change-Id: I1d7645844e183222a561578677fc5f59c080d58c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2067823 Auto-Submit: Chirantan Ekbote Tested-by: kokoro Reviewed-by: Daniel Verkamp Commit-Queue: Chirantan Ekbote --- src/linux.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index 662dea5..ba1ccf0 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -335,9 +335,13 @@ fn create_base_minijail( if let Some(gid_map) = config.gid_map { j.gidmap(gid_map).map_err(Error::SettingGidMap)?; } + // Run in a new mount namespace. + j.namespace_vfs(); + // Run in an empty network namespace. j.namespace_net(); - // Apply the block device seccomp policy. + + // Don't allow the device to gain new privileges. j.no_new_privs(); // By default we'll prioritize using the pre-compiled .bpf over the .policy @@ -367,9 +371,12 @@ fn create_base_minijail( j.run_as_init(); } - // Create a new mount namespace with an empty root FS. - j.namespace_vfs(); - j.enter_pivot_root(root).map_err(Error::DevicePivotRoot)?; + // Only pivot_root if we are not re-using the current root directory. + if root != Path::new("/") { + // It's safe to call `namespace_vfs` multiple times. + j.namespace_vfs(); + j.enter_pivot_root(root).map_err(Error::DevicePivotRoot)?; + } // Most devices don't need to open many fds. let limit = if let Some(r) = r_limit { r } else { 1024u64 }; -- cgit 1.4.1 From 0275efb3a00b2cbe15ec92a314bf163a3ca1433e Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Thu, 20 Feb 2020 12:13:09 -0800 Subject: x86_64: use __cpuid intrinsic Use the Rust __cpuid and __cpuid_count intrinsics to replace the C implementation in host_cpuid.c. These are defined in core, but they are also re-exported in std, despite being undocumented there due to technical reasons: https://github.com/rust-lang/rust/pull/57808#issuecomment-457390549 Use the std version for consistency (we don't currently use anything from core anywhere else in crosvm). BUG=None TEST=cargo test -p x86_64 TEST=Boot crosvm on x86_64 Change-Id: Ic7a1094d1b804304a2944f8ee1fe55c5e2db23e9 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2067159 Reviewed-by: Zach Reizner Tested-by: kokoro Commit-Queue: Daniel Verkamp --- Cargo.lock | 1 - x86_64/Cargo.toml | 4 --- x86_64/build.rs | 7 ------ x86_64/host_cpuid.c | 11 --------- x86_64/src/cpuid.rs | 70 ++++++++++++----------------------------------------- 5 files changed, 15 insertions(+), 78 deletions(-) delete mode 100644 x86_64/build.rs delete mode 100644 x86_64/host_cpuid.c diff --git a/Cargo.lock b/Cargo.lock index 0ec0a39..879c5bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,7 +639,6 @@ version = "0.1.0" dependencies = [ "arch 0.1.0", "assertions 0.1.0", - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "data_model 0.1.0", "devices 0.1.0", "io_jail 0.1.0", diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml index e5d5e14..3f5ba3e 100644 --- a/x86_64/Cargo.toml +++ b/x86_64/Cargo.toml @@ -3,7 +3,6 @@ name = "x86_64" version = "0.1.0" authors = ["The Chromium OS Authors"] edition = "2018" -build = "build.rs" [dependencies] arch = { path = "../arch" } @@ -20,6 +19,3 @@ remain = "*" resources = { path = "../resources" } sync = { path = "../sync" } sys_util = { path = "../sys_util" } - -[build-dependencies] -cc = "=1.0.25" diff --git a/x86_64/build.rs b/x86_64/build.rs deleted file mode 100644 index 5f2c1eb..0000000 --- a/x86_64/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -fn main() { - cc::Build::new().file("host_cpuid.c").compile("host_cpuid"); -} diff --git a/x86_64/host_cpuid.c b/x86_64/host_cpuid.c deleted file mode 100644 index 3230c90..0000000 --- a/x86_64/host_cpuid.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -void host_cpuid(uint32_t func, uint32_t func2, uint32_t *pEax, - uint32_t *pEbx, uint32_t *pEcx, uint32_t *pEdx) { - asm volatile("cpuid" : "=a"(*pEax), "=b"(*pEbx), "=c"(*pEcx), "=d"(*pEdx) : - "0"(func), "2"(func2) : "cc"); -} diff --git a/x86_64/src/cpuid.rs b/x86_64/src/cpuid.rs index a42b662..46294b2 100644 --- a/x86_64/src/cpuid.rs +++ b/x86_64/src/cpuid.rs @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::arch::x86_64::{__cpuid, __cpuid_count}; use std::fmt::{self, Display}; use std::result; @@ -28,19 +29,6 @@ impl Display for Error { } } -// This function is implemented in C because stable rustc does not -// support inline assembly. -extern "C" { - fn host_cpuid( - func: u32, - func2: u32, - rEax: *mut u32, - rEbx: *mut u32, - rEcx: *mut u32, - rEdx: *mut u32, - ) -> (); -} - // CPUID bits in ebx, ecx, and edx. const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size. const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH. @@ -77,25 +65,19 @@ fn filter_cpuid( } } 2 | 0x80000005 | 0x80000006 => unsafe { - host_cpuid( - entry.function, - 0, - &mut entry.eax as *mut u32, - &mut entry.ebx as *mut u32, - &mut entry.ecx as *mut u32, - &mut entry.edx as *mut u32, - ); + let result = __cpuid(entry.function); + entry.eax = result.eax; + entry.ebx = result.ebx; + entry.ecx = result.ecx; + entry.edx = result.edx; }, 4 => { unsafe { - host_cpuid( - entry.function, - entry.index, - &mut entry.eax as *mut u32, - &mut entry.ebx as *mut u32, - &mut entry.ecx as *mut u32, - &mut entry.edx as *mut u32, - ); + let result = __cpuid_count(entry.function, entry.index); + entry.eax = result.eax; + entry.ebx = result.ebx; + entry.ecx = result.ecx; + entry.edx = result.edx; } entry.eax &= !0xFC000000; } @@ -132,34 +114,12 @@ pub fn setup_cpuid(kvm: &kvm::Kvm, vcpu: &kvm::Vcpu, cpu_id: u64, nrcpus: u64) - /// get host cpu max physical address bits pub fn phy_max_address_bits() -> u32 { - let mut eax: u32 = 0; - let mut ebx: u32 = 0; - let mut ecx: u32 = 0; - let mut edx: u32 = 0; let mut phys_bits: u32 = 36; - unsafe { - host_cpuid( - 0x80000000, - 0, - &mut eax as *mut u32, - &mut ebx as *mut u32, - &mut ecx as *mut u32, - &mut edx as *mut u32, - ); - } - if eax >= 0x80000008 { - unsafe { - host_cpuid( - 0x80000008, - 0, - &mut eax as *mut u32, - &mut ebx as *mut u32, - &mut ecx as *mut u32, - &mut edx as *mut u32, - ); - } - phys_bits = eax & 0xff; + let highest_ext_function = unsafe { __cpuid(0x80000000) }; + if highest_ext_function.eax >= 0x80000008 { + let addr_size = unsafe { __cpuid(0x80000008) }; + phys_bits = addr_size.eax & 0xff; } phys_bits -- cgit 1.4.1 From 5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Thu, 26 Dec 2019 13:05:10 -0800 Subject: Initialize qcow clusters off the backing file when present This preserves any data that the backing file had on a cluster when doing a write to a subset of that cluster. These writes cause a performance penalty on creating new clusters if a backing file is present. TEST=unit tests BUG=b:140069322 Change-Id: I724990225617c05e5f2dea39e39ce84c940328fc Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1982832 Tested-by: kokoro Commit-Queue: Cody Schuffelen Reviewed-by: Daniel Verkamp --- disk/src/qcow/mod.rs | 54 +++++++++++++++++++++++++++++++++++------- disk/src/qcow/qcow_raw_file.rs | 12 +++++++++- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs index dfc8acd..c5e119d 100644 --- a/disk/src/qcow/mod.rs +++ b/disk/src/qcow/mod.rs @@ -1065,7 +1065,7 @@ impl QcowFile { let l2_table = if l2_addr_disk == 0 { // Allocate a new cluster to store the L2 table and update the L1 table to point // to the new table. - let new_addr: u64 = self.get_new_cluster()?; + let new_addr: u64 = self.get_new_cluster(None)?; // The cluster refcount starts at one meaning it is used but doesn't need COW. set_refcounts.push((new_addr, 1)); self.l1_table[l1_index] = new_addr; @@ -1086,8 +1086,19 @@ impl QcowFile { let cluster_addr = match self.l2_cache.get(&l1_index).unwrap()[l2_index] { 0 => { + let initial_data = if let Some(backing) = self.backing_file.as_mut() { + let cluster_size = self.raw_file.cluster_size(); + let cluster_begin = address - (address % cluster_size); + let mut cluster_data = vec![0u8; cluster_size as usize]; + let raw_slice = cluster_data.as_mut_slice(); + let volatile_slice = raw_slice.get_slice(0, cluster_size).unwrap(); + backing.read_exact_at_volatile(volatile_slice, cluster_begin)?; + Some(cluster_data) + } else { + None + }; // Need to allocate a data cluster - let cluster_addr = self.append_data_cluster()?; + let cluster_addr = self.append_data_cluster(initial_data)?; self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; cluster_addr } @@ -1124,7 +1135,7 @@ impl QcowFile { // Allocate a new cluster to store the L2 table and update the L1 table to point // to the new table. The cluster will be written when the cache is flushed, no // need to copy the data now. - let new_addr: u64 = self.get_new_cluster()?; + let new_addr: u64 = self.get_new_cluster(None)?; // The cluster refcount starts at one indicating it is used but doesn't need // COW. set_refcounts.push((new_addr, 1)); @@ -1136,15 +1147,22 @@ impl QcowFile { } // Allocate a new cluster and return its offset within the raw file. - fn get_new_cluster(&mut self) -> std::io::Result { + fn get_new_cluster(&mut self, initial_data: Option>) -> std::io::Result { // First use a pre allocated cluster if one is available. if let Some(free_cluster) = self.avail_clusters.pop() { - self.raw_file.zero_cluster(free_cluster)?; + if let Some(initial_data) = initial_data { + self.raw_file.write_cluster(free_cluster, initial_data)?; + } else { + self.raw_file.zero_cluster(free_cluster)?; + } return Ok(free_cluster); } let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset(); if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? { + if let Some(initial_data) = initial_data { + self.raw_file.write_cluster(new_cluster, initial_data)?; + } Ok(new_cluster) } else { error!("No free clusters in get_new_cluster()"); @@ -1154,8 +1172,8 @@ impl QcowFile { // Allocate and initialize a new data cluster. Returns the offset of the // cluster in to the file on success. - fn append_data_cluster(&mut self) -> std::io::Result { - let new_addr: u64 = self.get_new_cluster()?; + fn append_data_cluster(&mut self, initial_data: Option>) -> std::io::Result { + let new_addr: u64 = self.get_new_cluster(initial_data)?; // The cluster refcount starts at one indicating it is used but doesn't need COW. let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?; self.unref_clusters.append(&mut newly_unref); @@ -1386,7 +1404,7 @@ impl QcowFile { } Err(refcount::Error::NeedNewCluster) => { // Allocate the cluster and call set_cluster_refcount again. - let addr = self.get_new_cluster()?; + let addr = self.get_new_cluster(None)?; added_clusters.push(addr); new_cluster = Some(( addr, @@ -1995,6 +2013,26 @@ mod tests { assert_eq!(&buf, b"test"); } + #[test] + fn write_read_start_backing_overlap() { + let disk_file = basic_file(&valid_header()); + let mut backing = QcowFile::from(disk_file).unwrap(); + backing + .write(b"test first bytes") + .expect("Failed to write test string."); + let wrapping_disk_file = basic_file(&valid_header()); + let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap(); + wrapping.set_backing_file(Some(Box::new(backing))); + wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek."); + wrapping + .write(b"TEST") + .expect("Failed to write second test string."); + let mut buf = [0u8; 10]; + wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek."); + wrapping.read(&mut buf).expect("Failed to read."); + assert_eq!(&buf, b"TEST first"); + } + #[test] fn offset_write_read() { with_basic_file(&valid_header(), |disk_file: File| { diff --git a/disk/src/qcow/qcow_raw_file.rs b/disk/src/qcow/qcow_raw_file.rs index ede28d8..09d2176 100644 --- a/disk/src/qcow/qcow_raw_file.rs +++ b/disk/src/qcow/qcow_raw_file.rs @@ -6,7 +6,8 @@ use std::fs::File; use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write}; use std::mem::size_of; -use sys_util::WriteZeroes; +use data_model::VolatileMemory; +use sys_util::{FileReadWriteAtVolatile, WriteZeroes}; /// A qcow file. Allows reading/writing clusters and appending clusters. #[derive(Debug)] @@ -145,4 +146,13 @@ impl QcowRawFile { self.file.write_zeroes_all(cluster_size)?; Ok(()) } + + /// Writes + pub fn write_cluster(&mut self, address: u64, mut initial_data: Vec) -> io::Result<()> { + let raw_slice = initial_data.as_mut_slice(); + let volatile_slice = raw_slice + .get_slice(0, self.cluster_size) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?; + self.file.write_all_at_volatile(volatile_slice, address) + } } -- cgit 1.4.1 From f1f20f59be90ae38b859aee0d3bf26f44c33a40d Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Fri, 6 Dec 2019 19:06:46 -0800 Subject: Support the Android Sparse disk format Android defines its own "sparse disk" format, which its images are usually published in. Cuttlefish has special-cased this to build raw images in the android build system, but it still causes a performance hit when downloading and extracting the image zip files. Experimentally, running bsdtar on the zip file of raw images is about 50 seconds slower than bsdtar on the equivalent zip file of android sparse images. These disks can only be opened as read-only, as the Android Sparse format is designed around writing once then interpreting the contents while flashing a physical device through e.g. fastboot. TEST=Run with aosp/1184800 on cuttlefish, unit tests BUG=b:145841395 Change-Id: I13337b042e92841bd3cba88dc8b231fde88c091e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1956487 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Cody Schuffelen --- disk/src/android_sparse.rs | 515 +++++++++++++++++++++++++++++++++++++++++++++ disk/src/disk.rs | 12 ++ 2 files changed, 527 insertions(+) create mode 100644 disk/src/android_sparse.rs diff --git a/disk/src/android_sparse.rs b/disk/src/android_sparse.rs new file mode 100644 index 0000000..07e5714 --- /dev/null +++ b/disk/src/android_sparse.rs @@ -0,0 +1,515 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://android.googlesource.com/platform/system/core/+/7b444f0/libsparse/sparse_format.h + +use std::collections::BTreeMap; +use std::fmt::{self, Display}; +use std::fs::File; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom}; +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; + +use crate::DiskGetLen; +use data_model::{DataInit, Le16, Le32, VolatileSlice}; +use remain::sorted; +use sys_util::{ + FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole, WriteZeroesAt, +}; + +#[sorted] +#[derive(Debug)] +pub enum Error { + InvalidMagicHeader, + InvalidSpecification(String), + ReadSpecificationError(io::Error), +} + +impl Display for Error { + #[remain::check] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + #[sorted] + match self { + InvalidMagicHeader => write!(f, "invalid magic header for android sparse format"), + InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s), + ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e), + } + } +} + +pub type Result = std::result::Result; + +pub const SPARSE_HEADER_MAGIC: u32 = 0xed26ff3a; +const MAJOR_VERSION: u16 = 1; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct SparseHeader { + magic: Le32, /* SPARSE_HEADER_MAGIC */ + major_version: Le16, /* (0x1) - reject images with higher major versions */ + minor_version: Le16, /* (0x0) - allow images with higer minor versions */ + file_hdr_sz: Le16, /* 28 bytes for first revision of the file format */ + chunk_hdr_size: Le16, /* 12 bytes for first revision of the file format */ + blk_sz: Le32, /* block size in bytes, must be a multiple of 4 (4096) */ + total_blks: Le32, /* total blocks in the non-sparse output image */ + total_chunks: Le32, /* total chunks in the sparse input image */ + image_checksum: Le32, /* CRC32 checksum of the original data, counting "don't care" */ + /* as 0. Standard 802.3 polynomial, use a Public Domain */ + /* table implementation */ +} + +unsafe impl DataInit for SparseHeader {} + +const CHUNK_TYPE_RAW: u16 = 0xCAC1; +const CHUNK_TYPE_FILL: u16 = 0xCAC2; +const CHUNK_TYPE_DONT_CARE: u16 = 0xCAC3; +const CHUNK_TYPE_CRC32: u16 = 0xCAC4; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct ChunkHeader { + chunk_type: Le16, /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ + reserved1: u16, + chunk_sz: Le32, /* in blocks in output image */ + total_sz: Le32, /* in bytes of chunk input file including chunk header and data */ +} + +unsafe impl DataInit for ChunkHeader {} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Chunk { + Raw(u64), // Offset into the file + Fill(Vec), + DontCare, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ChunkWithSize { + chunk: Chunk, + expanded_size: u64, +} + +/* Following a Raw or Fill or CRC32 chunk is data. + * For a Raw chunk, it's the data in chunk_sz * blk_sz. + * For a Fill chunk, it's 4 bytes of the fill data. + * For a CRC32 chunk, it's 4 bytes of CRC32 + */ +#[derive(Debug)] +pub struct AndroidSparse { + file: File, + total_size: u64, + chunks: BTreeMap, +} + +fn parse_chunk( + mut input: &mut T, + chunk_hdr_size: u64, + blk_sz: u64, +) -> Result> { + let current_offset = input + .seek(SeekFrom::Current(0)) + .map_err(Error::ReadSpecificationError)?; + let chunk_header = + ChunkHeader::from_reader(&mut input).map_err(Error::ReadSpecificationError)?; + let chunk = match chunk_header.chunk_type.to_native() { + CHUNK_TYPE_RAW => { + input + .seek(SeekFrom::Current( + chunk_header.total_sz.to_native() as i64 - chunk_hdr_size as i64, + )) + .map_err(Error::ReadSpecificationError)?; + Chunk::Raw(current_offset + chunk_hdr_size as u64) + } + CHUNK_TYPE_FILL => { + if chunk_header.total_sz == chunk_hdr_size as u32 { + return Err(Error::InvalidSpecification(format!( + "Fill chunk did not have any data to fill" + ))); + } + let fill_size = chunk_header.total_sz.to_native() as u64 - chunk_hdr_size as u64; + let mut fill_bytes = vec![0u8; fill_size as usize]; + input + .read_exact(&mut fill_bytes) + .map_err(Error::ReadSpecificationError)?; + Chunk::Fill(fill_bytes) + } + CHUNK_TYPE_DONT_CARE => Chunk::DontCare, + CHUNK_TYPE_CRC32 => return Ok(None), // TODO(schuffelen): Validate crc32s in input + unknown_type => { + return Err(Error::InvalidSpecification(format!( + "Chunk had invalid type, was {:x}", + unknown_type + ))) + } + }; + let expanded_size = chunk_header.chunk_sz.to_native() as u64 * blk_sz; + Ok(Some(ChunkWithSize { + chunk, + expanded_size, + })) +} + +impl AndroidSparse { + pub fn from_file(mut file: File) -> Result { + file.seek(SeekFrom::Start(0)) + .map_err(Error::ReadSpecificationError)?; + let sparse_header = + SparseHeader::from_reader(&mut file).map_err(Error::ReadSpecificationError)?; + if sparse_header.magic != SPARSE_HEADER_MAGIC { + return Err(Error::InvalidSpecification(format!( + "Header did not match magic constant. Expected {:x}, was {:x}", + SPARSE_HEADER_MAGIC, + sparse_header.magic.to_native() + ))); + } else if sparse_header.major_version != MAJOR_VERSION { + return Err(Error::InvalidSpecification(format!( + "Header major version did not match. Expected {}, was {}", + MAJOR_VERSION, + sparse_header.major_version.to_native(), + ))); + } else if (sparse_header.chunk_hdr_size.to_native() as usize) + < mem::size_of::() + { + return Err(Error::InvalidSpecification(format!( + "Chunk header size does not fit chunk header struct, expected >={}, was {}", + sparse_header.chunk_hdr_size.to_native(), + mem::size_of::() + ))); + } + let header_size = sparse_header.chunk_hdr_size.to_native() as u64; + let block_size = sparse_header.blk_sz.to_native() as u64; + let chunks = (0..sparse_header.total_chunks.to_native()) + .filter_map(|_| parse_chunk(&mut file, header_size, block_size).transpose()) + .collect::>>()?; + let total_size = + sparse_header.total_blks.to_native() as u64 * sparse_header.blk_sz.to_native() as u64; + AndroidSparse::from_parts(file, total_size, chunks) + } + + fn from_parts(file: File, size: u64, chunks: Vec) -> Result { + let mut chunks_map: BTreeMap = BTreeMap::new(); + let mut expanded_location: u64 = 0; + for chunk_with_size in chunks { + let size = chunk_with_size.expanded_size; + if chunks_map + .insert(expanded_location, chunk_with_size) + .is_some() + { + return Err(Error::InvalidSpecification(format!( + "Two chunks were at {}", + expanded_location + ))); + } + expanded_location += size; + } + let image = AndroidSparse { + file, + total_size: size, + chunks: chunks_map, + }; + let calculated_len = image.get_len().map_err(Error::ReadSpecificationError)?; + if calculated_len != size { + return Err(Error::InvalidSpecification(format!( + "Header promised size {}, chunks added up to {}", + size, calculated_len + ))); + } + Ok(image) + } +} + +impl DiskGetLen for AndroidSparse { + fn get_len(&self) -> io::Result { + Ok(self.total_size) + } +} + +impl FileSetLen for AndroidSparse { + fn set_len(&self, _len: u64) -> io::Result<()> { + Err(io::Error::new( + ErrorKind::PermissionDenied, + "unsupported operation", + )) + } +} + +impl FileSync for AndroidSparse { + fn fsync(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl PunchHole for AndroidSparse { + fn punch_hole(&mut self, _offset: u64, _length: u64) -> io::Result<()> { + Err(io::Error::new( + ErrorKind::PermissionDenied, + "unsupported operation", + )) + } +} + +impl WriteZeroesAt for AndroidSparse { + fn write_zeroes_at(&mut self, _offset: u64, _length: usize) -> io::Result { + Err(io::Error::new( + ErrorKind::PermissionDenied, + "unsupported operation", + )) + } +} + +impl AsRawFd for AndroidSparse { + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + +impl FileAllocate for AndroidSparse { + fn allocate(&mut self, _offset: u64, _length: u64) -> io::Result<()> { + Err(io::Error::new( + ErrorKind::PermissionDenied, + "unsupported operation", + )) + } +} + +// Performs reads up to the chunk boundary. +impl FileReadWriteAtVolatile for AndroidSparse { + fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result { + let found_chunk = self.chunks.range(..=offset).next_back(); + let ( + chunk_start, + ChunkWithSize { + chunk, + expanded_size, + }, + ) = found_chunk.ok_or(io::Error::new( + ErrorKind::UnexpectedEof, + format!("no chunk for offset {}", offset), + ))?; + let chunk_offset = offset - chunk_start; + let chunk_size = *expanded_size; + let subslice = if chunk_offset + slice.size() > chunk_size { + slice + .sub_slice(0, chunk_size - chunk_offset) + .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))? + } else { + slice + }; + match chunk { + Chunk::DontCare => { + subslice.write_bytes(0); + Ok(subslice.size() as usize) + } + Chunk::Raw(file_offset) => self + .file + .read_at_volatile(subslice, *file_offset + chunk_offset), + Chunk::Fill(fill_bytes) => { + let filled_memory: Vec = fill_bytes + .iter() + .cloned() + .cycle() + .skip(chunk_offset as usize) + .take(subslice.size() as usize) + .collect(); + subslice.copy_from(&filled_memory); + Ok(subslice.size() as usize) + } + } + } + fn write_at_volatile(&mut self, _slice: VolatileSlice, _offset: u64) -> io::Result { + Err(io::Error::new( + ErrorKind::PermissionDenied, + "unsupported operation", + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use data_model::VolatileMemory; + use std::io::{Cursor, Write}; + use sys_util::SharedMemory; + + const CHUNK_SIZE: usize = mem::size_of::(); + + #[test] + fn parse_raw() { + let chunk_raw = ChunkHeader { + chunk_type: CHUNK_TYPE_RAW.into(), + reserved1: 0, + chunk_sz: 1.into(), + total_sz: (CHUNK_SIZE as u32 + 123).into(), + }; + let header_bytes = chunk_raw.as_slice(); + let mut chunk_bytes: Vec = Vec::new(); + chunk_bytes.extend_from_slice(header_bytes); + chunk_bytes.extend_from_slice(&[0u8; 123]); + let mut chunk_cursor = Cursor::new(chunk_bytes); + let chunk = parse_chunk(&mut chunk_cursor, CHUNK_SIZE as u64, 123) + .expect("Failed to parse") + .expect("Failed to determine chunk type"); + let expected_chunk = ChunkWithSize { + chunk: Chunk::Raw(CHUNK_SIZE as u64), + expanded_size: 123, + }; + assert_eq!(expected_chunk, chunk); + } + + #[test] + fn parse_dont_care() { + let chunk_raw = ChunkHeader { + chunk_type: CHUNK_TYPE_DONT_CARE.into(), + reserved1: 0, + chunk_sz: 100.into(), + total_sz: (CHUNK_SIZE as u32).into(), + }; + let header_bytes = chunk_raw.as_slice(); + let mut chunk_cursor = Cursor::new(header_bytes); + let chunk = parse_chunk(&mut chunk_cursor, CHUNK_SIZE as u64, 123) + .expect("Failed to parse") + .expect("Failed to determine chunk type"); + let expected_chunk = ChunkWithSize { + chunk: Chunk::DontCare, + expanded_size: 12300, + }; + assert_eq!(expected_chunk, chunk); + } + + #[test] + fn parse_fill() { + let chunk_raw = ChunkHeader { + chunk_type: CHUNK_TYPE_FILL.into(), + reserved1: 0, + chunk_sz: 100.into(), + total_sz: (CHUNK_SIZE as u32 + 4).into(), + }; + let header_bytes = chunk_raw.as_slice(); + let mut chunk_bytes: Vec = Vec::new(); + chunk_bytes.extend_from_slice(header_bytes); + chunk_bytes.extend_from_slice(&[123u8; 4]); + let mut chunk_cursor = Cursor::new(chunk_bytes); + let chunk = parse_chunk(&mut chunk_cursor, CHUNK_SIZE as u64, 123) + .expect("Failed to parse") + .expect("Failed to determine chunk type"); + let expected_chunk = ChunkWithSize { + chunk: Chunk::Fill(vec![123, 123, 123, 123]), + expanded_size: 12300, + }; + assert_eq!(expected_chunk, chunk); + } + + #[test] + fn parse_crc32() { + let chunk_raw = ChunkHeader { + chunk_type: CHUNK_TYPE_CRC32.into(), + reserved1: 0, + chunk_sz: 0.into(), + total_sz: (CHUNK_SIZE as u32 + 4).into(), + }; + let header_bytes = chunk_raw.as_slice(); + let mut chunk_bytes: Vec = Vec::new(); + chunk_bytes.extend_from_slice(header_bytes); + chunk_bytes.extend_from_slice(&[123u8; 4]); + let mut chunk_cursor = Cursor::new(chunk_bytes); + let chunk = + parse_chunk(&mut chunk_cursor, CHUNK_SIZE as u64, 123).expect("Failed to parse"); + assert_eq!(None, chunk); + } + + fn test_image(chunks: Vec) -> AndroidSparse { + let file: File = SharedMemory::anon().unwrap().into(); + let size = chunks.iter().map(|x| x.expanded_size).sum(); + AndroidSparse::from_parts(file, size, chunks).expect("Could not create image") + } + + #[test] + fn read_dontcare() { + let chunks = vec![ChunkWithSize { + chunk: Chunk::DontCare, + expanded_size: 100, + }]; + let mut image = test_image(chunks); + let mut input_memory = [55u8; 100]; + let input_volatile_memory = &mut input_memory[..]; + image + .read_exact_at_volatile(input_volatile_memory.get_slice(0, 100).unwrap(), 0) + .expect("Could not read"); + let input_vec: Vec = input_memory.into_iter().cloned().collect(); + assert_eq!(input_vec, vec![0u8; 100]); + } + + #[test] + fn read_fill_simple() { + let chunks = vec![ChunkWithSize { + chunk: Chunk::Fill(vec![10, 20]), + expanded_size: 8, + }]; + let mut image = test_image(chunks); + let mut input_memory = [55u8; 8]; + let input_volatile_memory = &mut input_memory[..]; + image + .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0) + .expect("Could not read"); + let input_vec: Vec = input_memory.into_iter().cloned().collect(); + assert_eq!(input_vec, vec![10, 20, 10, 20, 10, 20, 10, 20]); + } + + #[test] + fn read_fill_edges() { + let chunks = vec![ChunkWithSize { + chunk: Chunk::Fill(vec![10, 20, 30]), + expanded_size: 8, + }]; + let mut image = test_image(chunks); + let mut input_memory = [55u8; 6]; + let input_volatile_memory = &mut input_memory[..]; + image + .read_exact_at_volatile(input_volatile_memory.get_slice(0, 6).unwrap(), 1) + .expect("Could not read"); + let input_vec: Vec = input_memory.into_iter().cloned().collect(); + assert_eq!(input_vec, vec![20, 30, 10, 20, 30, 10]); + } + + #[test] + fn read_raw() { + let chunks = vec![ChunkWithSize { + chunk: Chunk::Raw(0), + expanded_size: 100, + }]; + let mut image = test_image(chunks); + write!(image.file, "hello").expect("Failed to write into internal file"); + let mut input_memory = [55u8; 5]; + let input_volatile_memory = &mut input_memory[..]; + image + .read_exact_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0) + .expect("Could not read"); + let input_vec: Vec = input_memory.into_iter().cloned().collect(); + assert_eq!(input_vec, vec![104, 101, 108, 108, 111]); + } + + #[test] + fn read_two_fills() { + let chunks = vec![ + ChunkWithSize { + chunk: Chunk::Fill(vec![10, 20]), + expanded_size: 4, + }, + ChunkWithSize { + chunk: Chunk::Fill(vec![30, 40]), + expanded_size: 4, + }, + ]; + let mut image = test_image(chunks); + let mut input_memory = [55u8; 8]; + let input_volatile_memory = &mut input_memory[..]; + image + .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0) + .expect("Could not read"); + let input_vec: Vec = input_memory.into_iter().cloned().collect(); + assert_eq!(input_vec, vec![10, 20, 10, 20, 30, 40, 30, 40]); + } +} diff --git a/disk/src/disk.rs b/disk/src/disk.rs index 2f9ad72..e00e843 100644 --- a/disk/src/disk.rs +++ b/disk/src/disk.rs @@ -22,11 +22,15 @@ mod composite; #[cfg(feature = "composite-disk")] use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN}; +mod android_sparse; +use android_sparse::{AndroidSparse, SPARSE_HEADER_MAGIC}; + #[sorted] #[derive(Debug)] pub enum Error { BlockDeviceNew(sys_util::Error), ConversionNotSupported, + CreateAndroidSparseDisk(android_sparse::Error), #[cfg(feature = "composite-disk")] CreateCompositeDisk(composite::Error), QcowError(qcow::Error), @@ -95,6 +99,7 @@ impl Display for Error { match self { BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e), ConversionNotSupported => write!(f, "requested file conversion not supported"), + CreateAndroidSparseDisk(e) => write!(f, "failure in android sparse disk: {}", e), #[cfg(feature = "composite-disk")] CreateCompositeDisk(e) => write!(f, "failure in composite disk: {}", e), QcowError(e) => write!(f, "failure in qcow: {}", e), @@ -114,6 +119,7 @@ pub enum ImageType { Raw, Qcow2, CompositeDisk, + AndroidSparse, } fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()> @@ -248,6 +254,8 @@ pub fn detect_image_type(file: &File) -> Result { } let image_type = if magic == QCOW_MAGIC { ImageType::Qcow2 + } else if magic == SPARSE_HEADER_MAGIC.to_be() { + ImageType::AndroidSparse } else { ImageType::Raw }; @@ -272,5 +280,9 @@ pub fn create_disk_file(raw_image: File) -> Result> { } #[cfg(not(feature = "composite-disk"))] ImageType::CompositeDisk => return Err(Error::UnknownType), + ImageType::AndroidSparse => { + Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?) + as Box + } }) } -- cgit 1.4.1 From dfd0139d7cf2935add342c76cec66702800e95b7 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Tue, 25 Feb 2020 11:53:32 -0800 Subject: Better errors on missing composite disk components. When there is an error opening one of the composite disk components now, it gives the message `failed to open component file: "No such file or directory (os error 2)"` without specifying the file path it tried to use. Exposing the file path will make it faster to act on errors, rather than trying to examine the composite disk file for paths. TEST=n/a BUG=b:150150052 Change-Id: I9341b330e7e6dcd517d5bfb5262b1657a2da46fe Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2072738 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Cody Schuffelen --- disk/src/composite.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/disk/src/composite.rs b/disk/src/composite.rs index cd048c1..e95c8e9 100644 --- a/disk/src/composite.rs +++ b/disk/src/composite.rs @@ -24,7 +24,7 @@ pub enum Error { InvalidMagicHeader, InvalidProto(protobuf::ProtobufError), InvalidSpecification(String), - OpenFile(io::Error), + OpenFile(io::Error, String), ReadSpecificationError(io::Error), UnknownVersion(u64), UnsupportedComponent(ImageType), @@ -41,7 +41,7 @@ impl Display for Error { InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"), InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e), InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s), - OpenFile(e) => write!(f, "failed to open component file: \"{}\"", e), + OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e), ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e), UnknownVersion(v) => write!(f, "unknown version {} in specification", v), UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c), @@ -142,7 +142,7 @@ impl CompositeDiskFile { ); let file = open_options .open(disk.get_file_path()) - .map_err(Error::OpenFile)?; + .map_err(|e| Error::OpenFile(e, disk.get_file_path().to_string()))?; Ok(ComponentDiskPart { file: create_disk_file(file).map_err(|e| Error::DiskError(Box::new(e)))?, offset: disk.get_offset(), -- cgit 1.4.1 From 72ccaefe0f384f708b3d2fd71aa3f3b40ab4e3df Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Mon, 13 Jan 2020 01:59:25 -0800 Subject: msg_socket: Add async receiving of messages Add a member to MsgSocket that effectively returns an async iterator over messages received on the socket. This is done by setting the socket as non-blocking and registering with the async infrastructure when the socket would block. This feature will be used by devices that wish to handle messages in an async fn context. Change-Id: I47c6e83922068820cd19ffd9ef604ed8a16b755e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1997243 Reviewed-by: Dylan Reid Tested-by: Dylan Reid Tested-by: kokoro Commit-Queue: Dylan Reid --- Cargo.lock | 204 ++++++++++++++++++++++++++++++++-------- msg_socket/Cargo.toml | 3 + msg_socket/src/lib.rs | 88 ++++++++++++++--- msg_socket/src/msg_on_socket.rs | 6 ++ 4 files changed, 252 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 879c5bd..f63785c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ dependencies = [ "kvm 0.1.0", "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", @@ -83,6 +83,18 @@ dependencies = [ "data_model 0.1.0", ] +[[package]] +name = "cros_async" +version = "0.1.0" +dependencies = [ + "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "sys_util 0.1.0", + "syscall_defines 0.1.0", +] + [[package]] name = "crosvm" version = "0.1.0" @@ -113,7 +125,7 @@ dependencies = [ "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "protos 0.1.0", "rand_ish 0.1.0", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", @@ -165,7 +177,7 @@ dependencies = [ "net_util 0.1.0", "p9 0.1.0", "protos 0.1.0", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", @@ -187,7 +199,7 @@ dependencies = [ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "protos 0.1.0", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "sys_util 0.1.0", ] @@ -200,6 +212,88 @@ dependencies = [ "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-channel" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-executor" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-io" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-sink" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-task" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "getopts" version = "0.2.18" @@ -311,6 +405,11 @@ dependencies = [ "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "minijail-sys" version = "0.0.11" @@ -332,7 +431,10 @@ dependencies = [ name = "msg_socket" version = "0.1.0" dependencies = [ + "cros_async 0.1.0", "data_model 0.1.0", + "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", "msg_on_socket_derive 0.1.0", "sys_util 0.1.0", ] @@ -370,6 +472,31 @@ dependencies = [ "wire_format_derive 0.1.0", ] +[[package]] +name = "paste" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "paste-impl" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "pkg-config" version = "0.3.11" @@ -385,13 +512,20 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "0.4.21" +name = "proc-macro-hack" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro-nested" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "1.0.8" @@ -451,14 +585,6 @@ dependencies = [ "sys_util 0.1.0", ] -[[package]] -name = "quote" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "quote" version = "1.0.2" @@ -473,12 +599,12 @@ version = "0.1.0" [[package]] name = "remain" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -492,14 +618,9 @@ dependencies = [ ] [[package]] -name = "syn" -version = "0.15.26" +name = "slab" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "syn" @@ -558,11 +679,6 @@ name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unicode-xid" version = "0.2.0" @@ -582,7 +698,7 @@ dependencies = [ "assertions 0.1.0", "data_model 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "sys_util 0.1.0", "usb_sys 0.1.0", ] @@ -647,7 +763,7 @@ dependencies = [ "kvm 0.1.0", "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", - "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", @@ -657,22 +773,34 @@ dependencies = [ "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" +"checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" +"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" +"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" +"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" +"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" +"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" +"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" +"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" "checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311" "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" "checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238" +"checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" +"checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" +"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" -"checksum proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ab2fc21ba78ac73e4ff6b3818ece00be4e175ffbef4d0a717d978b48b24150c4" +"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" +"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" "checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" "checksum protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" "checksum protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a" "checksum protoc 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3998c4bc0af8ccbd3cc68245ee9f72663c5ae2fb78bc48ff7719aef11562edea" "checksum protoc-rust 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "234c97039c32bb58a883d0deafa57db37e59428ce536f3bdfe1c46cffec04113" -"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3bec2543b50be4539fdc27fde082e218cf4c3895358ca77f5c52fe930589e209" -"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9" +"checksum remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/msg_socket/Cargo.toml b/msg_socket/Cargo.toml index dcfccfc..c803bed 100644 --- a/msg_socket/Cargo.toml +++ b/msg_socket/Cargo.toml @@ -5,6 +5,9 @@ authors = ["The Chromium OS Authors"] edition = "2018" [dependencies] +cros_async = { path = "../cros_async" } data_model = { path = "../data_model" } +futures = "*" +libc = "*" msg_on_socket_derive = { path = "msg_on_socket_derive" } sys_util = { path = "../sys_util" } diff --git a/msg_socket/src/lib.rs b/msg_socket/src/lib.rs index 5b9f9ce..ea817f0 100644 --- a/msg_socket/src/lib.rs +++ b/msg_socket/src/lib.rs @@ -7,8 +7,17 @@ mod msg_on_socket; use std::io::Result; use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, RawFd}; +use std::pin::Pin; +use std::task::{Context, Poll}; -use sys_util::{handle_eintr, net::UnixSeqpacket, Error as SysError, ScmSocket}; +use futures::Stream; +use libc::{EWOULDBLOCK, O_NONBLOCK}; + +use cros_async::fd_executor::add_read_waker; +use sys_util::{ + add_fd_flags, clear_fd_flags, error, handle_eintr, net::UnixSeqpacket, Error as SysError, + ScmSocket, +}; pub use crate::msg_on_socket::*; pub use msg_on_socket_derive::*; @@ -18,16 +27,8 @@ pub use msg_on_socket_derive::*; pub fn pair( ) -> Result<(MsgSocket, MsgSocket)> { let (sock1, sock2) = UnixSeqpacket::pair()?; - let requester = MsgSocket { - sock: sock1, - _i: PhantomData, - _o: PhantomData, - }; - let responder = MsgSocket { - sock: sock2, - _i: PhantomData, - _o: PhantomData, - }; + let requester = MsgSocket::new(sock1); + let responder = MsgSocket::new(sock2); Ok((requester, responder)) } @@ -47,6 +48,11 @@ impl MsgSocket { _o: PhantomData, } } + + // Creates an async receiver that implements `futures::Stream`. + pub fn async_receiver(&mut self) -> MsgResult> { + AsyncReceiver::new(self) + } } /// One direction socket that only supports sending. @@ -191,3 +197,63 @@ impl MsgSender for Sender { impl MsgReceiver for Receiver { type M = O; } + +/// Asynchronous adaptor for `MsgSocket`. +pub struct AsyncReceiver<'a, I: MsgOnSocket, O: MsgOnSocket> { + inner: &'a mut MsgSocket, + done: bool, // Have hit an error and the Stream should return null when polled. +} + +impl<'a, I: MsgOnSocket, O: MsgOnSocket> AsyncReceiver<'a, I, O> { + fn new(msg_socket: &mut MsgSocket) -> MsgResult> { + add_fd_flags(msg_socket.as_raw_fd(), O_NONBLOCK).map_err(MsgError::SettingFdFlags)?; + Ok(AsyncReceiver { + inner: msg_socket, + done: false, + }) + } +} + +impl<'a, I: MsgOnSocket, O: MsgOnSocket> Drop for AsyncReceiver<'a, I, O> { + fn drop(&mut self) { + if let Err(e) = clear_fd_flags(self.inner.as_raw_fd(), O_NONBLOCK) { + error!( + "Failed to restore non-blocking behavior to message socket: {}", + e + ); + } + } +} + +impl<'a, I: MsgOnSocket, O: MsgOnSocket> Stream for AsyncReceiver<'a, I, O> { + type Item = MsgResult; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + if self.done { + return Poll::Ready(None); + } + + let ret = match self.inner.recv() { + Ok(msg) => Ok(Poll::Ready(Some(Ok(msg)))), + Err(MsgError::Recv(e)) => { + if e.errno() == EWOULDBLOCK { + add_read_waker(self.inner.as_raw_fd(), cx.waker().clone()) + .map(|_| Poll::Pending) + .map_err(MsgError::AddingWaker) + } else { + Err(MsgError::Recv(e)) + } + } + Err(e) => Err(e), + }; + + match ret { + Ok(p) => p, + Err(e) => { + // Indicate something went wrong and no more events will be provided. + self.done = true; + Poll::Ready(Some(Err(e))) + } + } + } +} diff --git a/msg_socket/src/msg_on_socket.rs b/msg_socket/src/msg_on_socket.rs index 2924dc6..f03c36f 100644 --- a/msg_socket/src/msg_on_socket.rs +++ b/msg_socket/src/msg_on_socket.rs @@ -15,6 +15,8 @@ use sys_util::{Error as SysError, EventFd}; #[derive(Debug, PartialEq)] /// An error during transaction or serialization/deserialization. pub enum MsgError { + /// Error adding a waker for async read. + AddingWaker(cros_async::fd_executor::Error), /// Error while sending a request or response. Send(SysError), /// Error while receiving a request or response. @@ -28,6 +30,8 @@ pub enum MsgError { ExpectFd, /// There was some associated file descriptor received but not used when deserialize. NotExpectFd, + /// Failed to set flags on the file descriptor. + SettingFdFlags(SysError), /// Trying to serialize/deserialize, but fd buffer size is too small. This typically happens /// when max_fd_count() returns a value that is too small. WrongFdBufferSize, @@ -43,6 +47,7 @@ impl Display for MsgError { use self::MsgError::*; match self { + AddingWaker(e) => write!(f, "failed to add a waker: {}", e), Send(e) => write!(f, "failed to send request or response: {}", e), Recv(e) => write!(f, "failed to receive request or response: {}", e), InvalidType => write!(f, "invalid type"), @@ -53,6 +58,7 @@ impl Display for MsgError { ), ExpectFd => write!(f, "missing associated file descriptor for request"), NotExpectFd => write!(f, "unexpected file descriptor is unused"), + SettingFdFlags(e) => write!(f, "failed setting flags on the message FD: {}", e), WrongFdBufferSize => write!(f, "fd buffer size too small"), WrongMsgBufferSize => write!(f, "msg buffer size too small"), } -- cgit 1.4.1 From ec8aacb1343108c399e423f430864ee84cff8515 Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Wed, 23 Oct 2019 09:56:09 -0700 Subject: sys_util: cleanup shared mem file after test Running "build_test" fails if it's been previously run using sudo. The failure is: thread 'main' panicked at 'error creating shared memory;' and errno is 17, so apparently the shared memory name is left behind. BUG=None TEST=Verified that "sudo build_teat" followed by "build_test" results in a failure, while with this change it reports success. Change-Id: I09748b9c0b89ac953e054de852277d819ad85287 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1876662 Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Matt Delco --- sys_util/src/syslog.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sys_util/src/syslog.rs b/sys_util/src/syslog.rs index 82b7137..232f046 100644 --- a/sys_util/src/syslog.rs +++ b/sys_util/src/syslog.rs @@ -610,6 +610,7 @@ mod tests { shm_unlink(shm_name.as_ptr()); let fd = shm_open(shm_name.as_ptr(), O_RDWR | O_CREAT | O_EXCL, 0666); assert!(fd >= 0, "error creating shared memory;"); + shm_unlink(shm_name.as_ptr()); File::from_raw_fd(fd) }; -- cgit 1.4.1 From 91e8403ddf1dd5abf5611127fa07c578568be752 Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Fri, 1 Nov 2019 15:18:13 +0800 Subject: acpi_tables: Add initial ACPI tables support The basic ACPI table support for creating XSDT. It refers to the implementation of the Cloud-hypervisor's ACPI commit: - Cloud-hypervisor: acpi_tables: Add initial ACPI tables support BUG=chromium:1018674 TEST=cargo test -p acpi_tables Change-Id: Ia3b597936fef214fcb92fce28c91152dfa03bec9 Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035350 Reviewed-by: Tomasz Jeznach Tested-by: kokoro --- Cargo.lock | 8 ++++ Cargo.toml | 1 + acpi_tables/Cargo.toml | 8 ++++ acpi_tables/src/lib.rs | 10 +++++ acpi_tables/src/rsdp.rs | 67 ++++++++++++++++++++++++++++++ acpi_tables/src/sdt.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 acpi_tables/Cargo.toml create mode 100644 acpi_tables/src/lib.rs create mode 100644 acpi_tables/src/rsdp.rs create mode 100644 acpi_tables/src/sdt.rs diff --git a/Cargo.lock b/Cargo.lock index f63785c..80d5b5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,13 @@ dependencies = [ "sys_util 0.1.0", ] +[[package]] +name = "acpi_tables" +version = "0.1.0" +dependencies = [ + "data_model 0.1.0", +] + [[package]] name = "arch" version = "0.1.0" @@ -100,6 +107,7 @@ name = "crosvm" version = "0.1.0" dependencies = [ "aarch64 0.1.0", + "acpi_tables 0.1.0", "arch 0.1.0", "assertions 0.1.0", "audio_streams 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 7eb7215..66a616a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ sync = { path = "sync" } sys_util = "*" vhost = { path = "vhost" } vm_control = { path = "vm_control" } +acpi_tables = { path = "acpi_tables" } [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { path = "x86_64" } diff --git a/acpi_tables/Cargo.toml b/acpi_tables/Cargo.toml new file mode 100644 index 0000000..80518bb --- /dev/null +++ b/acpi_tables/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "acpi_tables" +version = "0.1.0" +authors = ["The Chromium OS Authors"] +edition = "2018" + +[dependencies] +data_model = { path = "../data_model" } diff --git a/acpi_tables/src/lib.rs b/acpi_tables/src/lib.rs new file mode 100644 index 0000000..73dd9e8 --- /dev/null +++ b/acpi_tables/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +pub mod rsdp; +pub mod sdt; + +fn generate_checksum(data: &[u8]) -> u8 { + (255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1) +} diff --git a/acpi_tables/src/rsdp.rs b/acpi_tables/src/rsdp.rs new file mode 100644 index 0000000..4bf64c9 --- /dev/null +++ b/acpi_tables/src/rsdp.rs @@ -0,0 +1,67 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use data_model::DataInit; + +#[repr(packed)] +#[derive(Clone, Copy, Default)] +pub struct RSDP { + pub signature: [u8; 8], + pub checksum: u8, + pub oem_id: [u8; 6], + pub revision: u8, + _rsdt_addr: u32, + pub length: u32, + pub xsdt_addr: u64, + pub extended_checksum: u8, + _reserved: [u8; 3], +} + +// Safe as RSDP structure only contains raw data +unsafe impl DataInit for RSDP {} + +impl RSDP { + pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self { + let mut rsdp = RSDP { + signature: *b"RSD PTR ", + checksum: 0, + oem_id, + revision: 2, + _rsdt_addr: 0, + length: std::mem::size_of::() as u32, + xsdt_addr, + extended_checksum: 0, + _reserved: [0; 3], + }; + + rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]); + rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice()); + rsdp + } + + pub fn len() -> usize { + std::mem::size_of::() + } +} + +#[cfg(test)] +mod tests { + use super::RSDP; + use data_model::DataInit; + + #[test] + fn test_rsdp() { + let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef); + let sum = rsdp + .as_slice() + .iter() + .fold(0u8, |acc, x| acc.wrapping_add(*x)); + assert_eq!(sum, 0); + let sum: u8 = rsdp + .as_slice() + .iter() + .fold(0u8, |acc, x| acc.wrapping_add(*x)); + assert_eq!(sum, 0); + } +} diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs new file mode 100644 index 0000000..ca1133c --- /dev/null +++ b/acpi_tables/src/sdt.rs @@ -0,0 +1,107 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use data_model::DataInit; + +/// SDT represents for System Description Table. The structure SDT is a +/// generic format for creating various ACPI tables like DSDT/FADT/MADT. +pub struct SDT { + data: Vec, +} + +const HEADER_LEN: u32 = 36; +const LENGTH_OFFSET: usize = 4; +const CHECKSUM_OFFSET: usize = 9; + +#[allow(clippy::len_without_is_empty)] +impl SDT { + /// Set up the ACPI table header at the front of the SDT. + /// The arguments correspond to the elements in the ACPI + /// table headers. + pub fn new( + signature: [u8; 4], + length: u32, + revision: u8, + oem_id: [u8; 6], + oem_table: [u8; 8], + oem_revision: u32, + ) -> Self { + // The length represents for the length of the entire table + // which includes this header. And the header is 36 bytes, so + // lenght should be >= 36. For the case who gives a number less + // than the header len, use the header len directly. + let len: u32 = if length < HEADER_LEN { + HEADER_LEN + } else { + length + }; + let mut data = Vec::with_capacity(length as usize); + data.extend_from_slice(&signature); + data.extend_from_slice(&len.to_le_bytes()); + data.push(revision); + data.push(0); // checksum + data.extend_from_slice(&oem_id); + data.extend_from_slice(&oem_table); + data.extend_from_slice(&oem_revision.to_le_bytes()); + data.extend_from_slice(b"CROS"); + data.extend_from_slice(&0u32.to_le_bytes()); + + data.resize(length as usize, 0); + let mut sdt = SDT { data }; + + sdt.update_checksum(); + sdt + } + + fn update_checksum(&mut self) { + self.data[CHECKSUM_OFFSET] = 0; + let checksum = super::generate_checksum(self.data.as_slice()); + self.data[CHECKSUM_OFFSET] = checksum; + } + + pub fn as_slice(&self) -> &[u8] { + &self.data.as_slice() + } + + pub fn append(&mut self, value: T) { + self.data.extend_from_slice(value.as_slice()); + self.write(LENGTH_OFFSET, self.data.len() as u32); + } + + /// Write a value at the given offset + pub fn write(&mut self, offset: usize, value: T) { + let value_len = std::mem::size_of::(); + if (offset + value_len) > self.data.len() { + return; + } + + self.data[offset..offset + value_len].copy_from_slice(&value.as_slice()); + self.update_checksum(); + } + + pub fn len(&self) -> usize { + self.data.len() + } +} + +#[cfg(test)] +mod tests { + use super::SDT; + + #[test] + fn test_sdt() { + let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CROSVM", *b"TESTTEST", 1); + let sum: u8 = sdt + .as_slice() + .iter() + .fold(0u8, |acc, x| acc.wrapping_add(*x)); + assert_eq!(sum, 0); + sdt.write(36, 0x12345678 as u32); + let sum: u8 = sdt + .as_slice() + .iter() + .fold(0u8, |acc, x| acc.wrapping_add(*x)); + assert_eq!(sum, 0); + } +} -- cgit 1.4.1 From 1f9c1bb88b68961c2a0167993f6df401d49a2e9f Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Thu, 20 Feb 2020 14:55:24 -0800 Subject: devices: pci: add error handling for msix_enable This more gracefully handles failure of msix_enable; in particular, if it fails, the self.enabled state is returned to false so that future device operations won't try to access uninitialized parts of self.irq_vec. In addition, the AddMsiRoute response is now checked (previously, it was just ignored), and errors are bubbled up via MsixError rather than just printing a message. BUG=chromium:1041351 TEST=Boot Crostini on nami Change-Id: I9999f149817bc9f49176487446b52e74fb8be9a9 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2067964 Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Daniel Verkamp --- devices/src/pci/msix.rs | 97 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs index b1dc672..6c79b4a 100644 --- a/devices/src/pci/msix.rs +++ b/devices/src/pci/msix.rs @@ -3,11 +3,12 @@ // found in the LICENSE file. use crate::pci::{PciCapability, PciCapabilityID}; -use msg_socket::{MsgReceiver, MsgSender}; +use msg_socket::{MsgError, MsgReceiver, MsgSender}; use std::convert::TryInto; +use std::fmt::{self, Display}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::Arc; -use sys_util::{error, EventFd}; +use sys_util::{error, Error as SysError, EventFd}; use vm_control::{MaybeOwnedFd, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse}; use data_model::DataInit; @@ -60,6 +61,34 @@ pub struct MsixConfig { msix_num: u16, } +enum MsixError { + AddMsiRoute(SysError), + AddMsiRouteRecv(MsgError), + AddMsiRouteSend(MsgError), + AllocateOneMsi(SysError), + AllocateOneMsiRecv(MsgError), + AllocateOneMsiSend(MsgError), +} + +impl Display for MsixError { + #[remain::check] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::MsixError::*; + + #[sorted] + match self { + AddMsiRoute(e) => write!(f, "AddMsiRoute failed: {}", e), + AddMsiRouteRecv(e) => write!(f, "failed to receive AddMsiRoute response: {}", e), + AddMsiRouteSend(e) => write!(f, "failed to send AddMsiRoute request: {}", e), + AllocateOneMsi(e) => write!(f, "AllocateOneMsi failed: {}", e), + AllocateOneMsiRecv(e) => write!(f, "failed to receive AllocateOneMsi response: {}", e), + AllocateOneMsiSend(e) => write!(f, "failed to send AllocateOneMsi request: {}", e), + } + } +} + +type MsixResult = std::result::Result; + impl MsixConfig { pub fn new(msix_vectors: u16, vm_socket: Arc) -> Self { assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE); @@ -128,7 +157,10 @@ impl MsixConfig { self.enabled = (reg & MSIX_ENABLE_BIT) == MSIX_ENABLE_BIT; if !old_enabled && self.enabled { - self.msix_enable(); + if let Err(e) = self.msix_enable() { + error!("failed to enable MSI-X: {}", e); + self.enabled = false; + } } // If the Function Mask bit was set, and has just been cleared, it's @@ -150,7 +182,7 @@ impl MsixConfig { } } - fn add_msi_route(&self, index: u16, gsi: u32) { + fn add_msi_route(&self, index: u16, gsi: u32) -> MsixResult<()> { let mut data: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; self.read_msix_table((index * 16).into(), data.as_mut()); let msi_address: u64 = u64::from_le_bytes(data); @@ -159,44 +191,53 @@ impl MsixConfig { let msi_data: u32 = u32::from_le_bytes(data); if msi_address == 0 { - return; + return Ok(()); } - if let Err(e) = self.msi_device_socket.send(&VmIrqRequest::AddMsiRoute { - gsi, - msi_address, - msi_data, - }) { - error!("failed to send AddMsiRoute request: {:?}", e); - return; - } - if self.msi_device_socket.recv().is_err() { - error!("Faied to receive AddMsiRoute Response"); + self.msi_device_socket + .send(&VmIrqRequest::AddMsiRoute { + gsi, + msi_address, + msi_data, + }) + .map_err(MsixError::AddMsiRouteSend)?; + if let VmIrqResponse::Err(e) = self + .msi_device_socket + .recv() + .map_err(MsixError::AddMsiRouteRecv)? + { + return Err(MsixError::AddMsiRoute(e)); } + Ok(()) } - fn msix_enable(&mut self) { + fn msix_enable(&mut self) -> MsixResult<()> { self.irq_vec.clear(); for i in 0..self.msix_num { let irqfd = EventFd::new().unwrap(); - if let Err(e) = self.msi_device_socket.send(&VmIrqRequest::AllocateOneMsi { - irqfd: MaybeOwnedFd::Borrowed(irqfd.as_raw_fd()), - }) { - error!("failed to send AllocateOneMsi request: {:?}", e); - continue; - } + self.msi_device_socket + .send(&VmIrqRequest::AllocateOneMsi { + irqfd: MaybeOwnedFd::Borrowed(irqfd.as_raw_fd()), + }) + .map_err(MsixError::AllocateOneMsiSend)?; let irq_num: u32; - match self.msi_device_socket.recv() { - Ok(VmIrqResponse::AllocateOneMsi { gsi }) => irq_num = gsi, - _ => continue, + match self + .msi_device_socket + .recv() + .map_err(MsixError::AllocateOneMsiRecv)? + { + VmIrqResponse::AllocateOneMsi { gsi } => irq_num = gsi, + VmIrqResponse::Err(e) => return Err(MsixError::AllocateOneMsi(e)), + _ => unreachable!(), } self.irq_vec.push(IrqfdGsi { irqfd, gsi: irq_num, }); - self.add_msi_route(i, irq_num); + self.add_msi_route(i, irq_num)?; } + Ok(()) } /// Read MSI-X table @@ -305,7 +346,9 @@ impl MsixConfig { || old_entry.msg_data != new_entry.msg_data) { let irq_num = self.irq_vec[index].gsi; - self.add_msi_route(index as u16, irq_num); + if let Err(e) = self.add_msi_route(index as u16, irq_num) { + error!("add_msi_route failed: {}", e); + } } // After the MSI-X table entry has been updated, it is necessary to -- cgit 1.4.1 From 767333820014495f77b09f54de254e004bd8a5f9 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Tue, 25 Feb 2020 13:31:28 -0800 Subject: devices: block: let resize convert to non-sparse Change the behavior of the resize operation on virtio-block devices so that it causes a disk to become fully allocated (non-sparse) even if it had previously been sparse. This means that we could have a disk that was previously sparse and is now non-sparse, so treat discard requests for sparse disks as a no-op instead of an error. This is acceptable since discard is a hint and doing nothing is a valid implementation. BUG=chromium:858815 TEST=`crosvm disk resize` a sparse disk Change-Id: I8b79e460e5432cc71bed98172527fe1cd2e726ed Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2070846 Reviewed-by: David Munro Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Daniel Verkamp --- devices/src/virtio/block.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index 1e9c63e..094586f 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -343,14 +343,14 @@ impl Worker { return DiskControlResult::Err(SysError::new(libc::EIO)); } - if !self.sparse { - // Allocate new space if the disk image is not sparse. - if let Err(e) = self.disk_image.allocate(0, new_size) { - error!("Allocating disk space after resize failed! {}", e); - return DiskControlResult::Err(SysError::new(libc::EIO)); - } + // Allocate new space if the disk image is not sparse. + if let Err(e) = self.disk_image.allocate(0, new_size) { + error!("Allocating disk space after resize failed! {}", e); + return DiskControlResult::Err(SysError::new(libc::EIO)); } + self.sparse = false; + if let Ok(new_disk_size) = self.disk_image.get_len() { let mut disk_size = self.disk_size.lock(); *disk_size = new_disk_size; @@ -624,7 +624,8 @@ impl Block { } VIRTIO_BLK_T_DISCARD | VIRTIO_BLK_T_WRITE_ZEROES => { if req_type == VIRTIO_BLK_T_DISCARD && !sparse { - return Err(ExecuteError::Unsupported(req_type)); + // Discard is a hint; if this is a non-sparse disk, just ignore it. + return Ok(()); } while reader.available_bytes() >= size_of::() { -- cgit 1.4.1 From 46d61ba80df1ccf8364d9589170b3a7bff1268ee Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Tue, 25 Feb 2020 10:17:50 -0800 Subject: linux: add disk path to open error message Previously, if a disk could not be opened, the error message did not include the path of the disk, e.g.: The architecture failed to build the vm: error creating devices: failed to load disk image: Read-only file system (os error 30) To make debugging easier, add the path to Error::Disk. BUG=b:150181514 TEST=crosvm run --rwdisk ro.img vm_kernel Change-Id: I7b319c419b889334ecadbb0497dc4b3dc5115aa6 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2070844 Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Daniel Verkamp --- src/linux.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index ba1ccf0..f5d2d1c 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -100,7 +100,7 @@ pub enum Error { CreateVfioDevice(devices::vfio::VfioError), DeviceJail(io_jail::Error), DevicePivotRoot(io_jail::Error), - Disk(io::Error), + Disk(PathBuf, io::Error), DiskImageLock(sys_util::Error), DropCapabilities(sys_util::Error), FsDeviceNew(virtio::fs::Error), @@ -187,7 +187,7 @@ impl Display for Error { CreateVfioDevice(e) => write!(f, "Failed to create vfio device {}", e), DeviceJail(e) => write!(f, "failed to jail device: {}", e), DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e), - Disk(e) => write!(f, "failed to load disk image: {}", e), + Disk(p, e) => write!(f, "failed to load disk image {}: {}", p.display(), e), DiskImageLock(e) => write!(f, "failed to lock disk image: {}", e), DropCapabilities(e) => write!(f, "failed to drop process capabilities: {}", e), FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e), @@ -424,7 +424,7 @@ fn create_block_device( .read(true) .write(!disk.read_only) .open(&disk.path) - .map_err(Error::Disk)? + .map_err(|e| Error::Disk(disk.path.to_path_buf(), e))? }; // Lock the disk image to prevent other crosvm instances from using it. let lock_op = if disk.read_only { @@ -883,10 +883,11 @@ fn create_pmem_device( .read(true) .write(!disk.read_only) .open(&disk.path) - .map_err(Error::Disk)?; + .map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?; let (disk_size, arena_size) = { - let metadata = std::fs::metadata(&disk.path).map_err(Error::Disk)?; + let metadata = + std::fs::metadata(&disk.path).map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?; let disk_len = metadata.len(); // Linux requires pmem region sizes to be 2 MiB aligned. Linux will fill any partial page // at the end of an mmap'd file and won't write back beyond the actual file length, but if -- cgit 1.4.1 From 020fbf04c2ac112f34b87306b5fbb75e7a02a81a Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Thu, 27 Feb 2020 13:58:26 +0800 Subject: x86_64: generate ACPI tables Add acpi_rsdp_addr in boot_params to allow crosvm to pass a physicall address of RSDP to the Linux guest kernel, so that the linux guest kernel can parse the constructed ACPI tables and enable the ACPI. Although there is ACPI tables but as we still have "acpi=off" in command line parameter, there is still no ACPI in guest kernel. The ACPI construction refers to the implementation of the Cloud-hypervisor commit: - arch: x86_64: Generate basic ACPI tables BUG=chromium:1018674 TEST=None Change-Id: Ibcb2ae98c43da8ef8c07a07eda9213f61570d14c Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035351 Reviewed-by: Tomasz Jeznach Tested-by: kokoro Commit-Queue: Tomasz Jeznach --- Cargo.lock | 1 + acpi_tables/src/lib.rs | 2 + acpi_tables/src/sdt.rs | 2 +- devices/src/acpi.rs | 4 +- x86_64/Cargo.toml | 1 + x86_64/src/acpi.rs | 212 ++++++++++++++++++++++++++++++++++++++++++++++++ x86_64/src/bootparam.rs | 3 +- x86_64/src/lib.rs | 8 +- x86_64/src/mptable.rs | 6 +- 9 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 x86_64/src/acpi.rs diff --git a/Cargo.lock b/Cargo.lock index 80d5b5e..6210b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,6 +761,7 @@ dependencies = [ name = "x86_64" version = "0.1.0" dependencies = [ + "acpi_tables 0.1.0", "arch 0.1.0", "assertions 0.1.0", "data_model 0.1.0", diff --git a/acpi_tables/src/lib.rs b/acpi_tables/src/lib.rs index 73dd9e8..49cf760 100644 --- a/acpi_tables/src/lib.rs +++ b/acpi_tables/src/lib.rs @@ -5,6 +5,8 @@ pub mod rsdp; pub mod sdt; +pub use self::sdt::HEADER_LEN; + fn generate_checksum(data: &[u8]) -> u8 { (255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1) } diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs index ca1133c..e8a9ea2 100644 --- a/acpi_tables/src/sdt.rs +++ b/acpi_tables/src/sdt.rs @@ -10,7 +10,7 @@ pub struct SDT { data: Vec, } -const HEADER_LEN: u32 = 36; +pub const HEADER_LEN: u32 = 36; const LENGTH_OFFSET: usize = 4; const CHECKSUM_OFFSET: usize = 9; diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs index 6d36353..990a782 100644 --- a/devices/src/acpi.rs +++ b/devices/src/acpi.rs @@ -31,7 +31,9 @@ impl ACPIPMResource { /// the ACPI PM register's base and length. pub const ACPIPM_RESOURCE_BASE: u64 = 0x600; -pub const ACPIPM_RESOURCE_LEN: u64 = 8; +pub const ACPIPM_RESOURCE_LEN: u8 = 8; +pub const ACPIPM_RESOURCE_EVENTBLK_LEN: u8 = 4; +pub const ACPIPM_RESOURCE_CONTROLBLK_LEN: u8 = 2; /// ACPI PM register value definations const PM1_STATUS: u16 = 0; diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml index 3f5ba3e..bb1c445 100644 --- a/x86_64/Cargo.toml +++ b/x86_64/Cargo.toml @@ -19,3 +19,4 @@ remain = "*" resources = { path = "../resources" } sync = { path = "../sync" } sys_util = { path = "../sys_util" } +acpi_tables = {path = "../acpi_tables" } diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs new file mode 100644 index 0000000..7030bd8 --- /dev/null +++ b/x86_64/src/acpi.rs @@ -0,0 +1,212 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use acpi_tables::{rsdp::RSDP, sdt::SDT}; +use data_model::DataInit; +use sys_util::{GuestAddress, GuestMemory}; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct LocalAPIC { + _type: u8, + _length: u8, + _processor_id: u8, + _apic_id: u8, + _flags: u32, +} + +// Safe as LocalAPIC structure only contains raw data +unsafe impl DataInit for LocalAPIC {} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct IOAPIC { + _type: u8, + _length: u8, + _ioapic_id: u8, + _reserved: u8, + _apic_address: u32, + _gsi_base: u32, +} + +// Safe as IOAPIC structure only contains raw data +unsafe impl DataInit for IOAPIC {} + +const OEM_REVISION: u32 = 1; +//DSDT +const DSDT_REVISION: u8 = 6; +// FADT +const FADT_LEN: u32 = 276; +const FADT_REVISION: u8 = 6; +const FADT_MINOR_REVISION: u8 = 3; +// FADT flags +const FADT_POWER_BUTTON: u32 = (1 << 4); +const FADT_SLEEP_BUTTON: u32 = (1 << 5); +// FADT fields offset +const FADT_FIELD_SCI_INTERRUPT: usize = 46; +const FADT_FIELD_PM1A_EVENT_BLK_ADDR: usize = 56; +const FADT_FIELD_PM1A_CONTROL_BLK_ADDR: usize = 64; +const FADT_FIELD_PM1A_EVENT_BLK_LEN: usize = 88; +const FADT_FIELD_PM1A_CONTROL_BLK_LEN: usize = 89; +const FADT_FIELD_FLAGS: usize = 112; +const FADT_FIELD_MINOR_REVISION: usize = 131; +const FADT_FIELD_DSDT_ADDR: usize = 140; +const FADT_FIELD_HYPERVISOR_ID: usize = 268; +// MADT +const MADT_LEN: u32 = 44; +const MADT_REVISION: u8 = 5; +// MADT fields offset +const MADT_FIELD_LAPIC_ADDR: usize = 36; +// MADT types +const MADT_TYPE_LOCAL_APIC: u8 = 0; +const MADT_TYPE_IO_APIC: u8 = 1; +// MADT flags +const MADT_ENABLED: u32 = 1; +// XSDT +const XSDT_REVISION: u8 = 1; + +/// Create ACPI tables and return the RSDP. +/// The basic tables DSDT/FACP/MADT/XSDT are constructed in this function. +/// # Arguments +/// +/// * `guest_mem` - The guest memory where the tables will be stored. +/// * `num_cpus` - Used to construct the MADT. +/// * `sci_irq` - Used to fill the FACP SCI_INTERRUPT field, which +/// is going to be used by the ACPI drivers to register +/// sci handler. +pub fn create_acpi_tables(guest_mem: &GuestMemory, num_cpus: u8, sci_irq: u32) -> GuestAddress { + // RSDP is at the HI RSDP WINDOW + let rsdp_offset = GuestAddress(super::ACPI_HI_RSDP_WINDOW_BASE); + let mut tables: Vec = Vec::new(); + + // DSDT + let dsdt = SDT::new( + *b"DSDT", + acpi_tables::HEADER_LEN, + DSDT_REVISION, + *b"CROSVM", + *b"CROSVMDT", + OEM_REVISION, + ); + let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap(); + guest_mem + .write_at_addr(dsdt.as_slice(), dsdt_offset) + .expect("Error writing DSDT table"); + + // FACP aka FADT + // Revision 6 of the ACPI FADT table is 276 bytes long + let mut facp = SDT::new( + *b"FACP", + FADT_LEN, + FADT_REVISION, + *b"CROSVM", + *b"CROSVMDT", + OEM_REVISION, + ); + + let fadt_flags: u32 = FADT_POWER_BUTTON | FADT_SLEEP_BUTTON; // mask POWER and SLEEP BUTTON + facp.write(FADT_FIELD_FLAGS, fadt_flags); + + // SCI Interrupt + facp.write(FADT_FIELD_SCI_INTERRUPT, sci_irq as u16); + + // PM1A Event Block Address + facp.write( + FADT_FIELD_PM1A_EVENT_BLK_ADDR, + devices::acpi::ACPIPM_RESOURCE_BASE as u32, + ); + + // PM1A Control Block Address + facp.write( + FADT_FIELD_PM1A_CONTROL_BLK_ADDR, + devices::acpi::ACPIPM_RESOURCE_BASE as u32 + + devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u32, + ); + + // PM1 Event Block Length + facp.write( + FADT_FIELD_PM1A_EVENT_BLK_LEN, + devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u8, + ); + + // PM1 Control Block Length + facp.write( + FADT_FIELD_PM1A_CONTROL_BLK_LEN, + devices::acpi::ACPIPM_RESOURCE_CONTROLBLK_LEN as u8, + ); + + facp.write(FADT_FIELD_MINOR_REVISION, FADT_MINOR_REVISION); // FADT minor version + facp.write(FADT_FIELD_DSDT_ADDR, dsdt_offset.0 as u64); // X_DSDT + + facp.write(FADT_FIELD_HYPERVISOR_ID, *b"CROSVM"); // Hypervisor Vendor Identity + + let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap(); + guest_mem + .write_at_addr(facp.as_slice(), facp_offset) + .expect("Error writing FACP table"); + tables.push(facp_offset.0); + + // MADT + let mut madt = SDT::new( + *b"APIC", + MADT_LEN, + MADT_REVISION, + *b"CROSVM", + *b"CROSVMDT", + OEM_REVISION, + ); + madt.write( + MADT_FIELD_LAPIC_ADDR, + super::mptable::APIC_DEFAULT_PHYS_BASE as u32, + ); + + for cpu in 0..num_cpus { + let lapic = LocalAPIC { + _type: MADT_TYPE_LOCAL_APIC, + _length: std::mem::size_of::() as u8, + _processor_id: cpu, + _apic_id: cpu, + _flags: MADT_ENABLED, + }; + madt.append(lapic); + } + + madt.append(IOAPIC { + _type: MADT_TYPE_IO_APIC, + _length: std::mem::size_of::() as u8, + _apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE, + ..Default::default() + }); + + let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap(); + guest_mem + .write_at_addr(madt.as_slice(), madt_offset) + .expect("Error writing MADT table"); + tables.push(madt_offset.0); + + // XSDT + let mut xsdt = SDT::new( + *b"XSDT", + acpi_tables::HEADER_LEN, + XSDT_REVISION, + *b"CROSVM", + *b"CROSVMDT", + OEM_REVISION, + ); + for table in tables { + xsdt.append(table); + } + + let xsdt_offset = madt_offset.checked_add(madt.len() as u64).unwrap(); + guest_mem + .write_at_addr(xsdt.as_slice(), xsdt_offset) + .expect("Error writing XSDT table"); + + // RSDP + let rsdp = RSDP::new(*b"CROSVM", xsdt_offset.0); + guest_mem + .write_at_addr(rsdp.as_slice(), rsdp_offset) + .expect("Error writing RSDP"); + + rsdp_offset +} diff --git a/x86_64/src/bootparam.rs b/x86_64/src/bootparam.rs index 33bd90a..fd8e1d5 100644 --- a/x86_64/src/bootparam.rs +++ b/x86_64/src/bootparam.rs @@ -401,7 +401,8 @@ pub struct boot_params { pub _pad2: [__u8; 4usize], pub tboot_addr: __u64, pub ist_info: ist_info, - pub _pad3: [__u8; 16usize], + pub acpi_rsdp_addr: __u64, + pub _pad3: [__u8; 8usize], pub hd0_info: [__u8; 16usize], pub hd1_info: [__u8; 16usize], pub sys_desc_table: sys_desc_table, diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 487273e..20831cd 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -35,6 +35,7 @@ unsafe impl data_model::DataInit for mpspec::mpc_table {} unsafe impl data_model::DataInit for mpspec::mpc_lintsrc {} unsafe impl data_model::DataInit for mpspec::mpf_intel {} +mod acpi; mod bzimage; mod cpuid; mod gdt; @@ -178,6 +179,7 @@ const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET; const X86_64_SERIAL_1_3_IRQ: u32 = 4; const X86_64_SERIAL_2_4_IRQ: u32 = 3; const X86_64_IRQ_BASE: u32 = 5; +const ACPI_HI_RSDP_WINDOW_BASE: u64 = 0x000E0000; fn configure_system( guest_mem: &GuestMemory, @@ -252,6 +254,10 @@ fn configure_system( guest_mem .write_obj_at_addr(params, zero_page_addr) .map_err(|_| Error::ZeroPageSetup)?; + + let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, 9); + params.acpi_rsdp_addr = rsdp_addr.0; + Ok(()) } @@ -750,7 +756,7 @@ impl X8664arch { .insert( pm.clone(), devices::acpi::ACPIPM_RESOURCE_BASE, - devices::acpi::ACPIPM_RESOURCE_LEN, + devices::acpi::ACPIPM_RESOURCE_LEN as u64, false, ) .unwrap(); diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs index 8b754bd..9aded3f 100644 --- a/x86_64/src/mptable.rs +++ b/x86_64/src/mptable.rs @@ -77,8 +77,10 @@ const MPC_OEM: [c_char; 8] = char_array!(c_char; 'C', 'R', 'O', 'S', 'V', 'M', ' const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12]; const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' '); const BUS_TYPE_PCI: [u8; 6] = char_array!(u8; 'P', 'C', 'I', ' ', ' ', ' '); -const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h -const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h +// source: linux/arch/x86/include/asm/apicdef.h +pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; +// source: linux/arch/x86/include/asm/apicdef.h +pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; const APIC_VERSION: u8 = 0x14; const CPU_STEPPING: u32 = 0x600; const CPU_FEATURE_APIC: u32 = 0x200; -- cgit 1.4.1 From 7eae7735ee3485605d2ec7ba0685588b7a38b37b Mon Sep 17 00:00:00 2001 From: Fletcher Woodruff Date: Mon, 12 Aug 2019 11:46:47 -0600 Subject: ac97: switch to ShmStreamSource Convert playback and capture for the AC97 device to use the zero-copy ShmStreamSource instead of the old StreamSource. In the process, rework start_playback and start_capture unit tests so they rely less on sleep statements. BUG=chromium:968724 BUG=chromium:1006035 TEST="sox -n -r 48000 -b 16 output.raw synth 5 sine 330 && aplay -f dat output.raw" within a VM, check that sine wave is played accurately. Change-Id: Ie9cddbc5285a9505872c9951a8a1da01de70eb88 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1749950 Tested-by: kokoro Commit-Queue: Fletcher Woodruff Reviewed-by: Dylan Reid Reviewed-by: Chih-Yang Hsia --- devices/src/pci/ac97.rs | 8 +- devices/src/pci/ac97_bus_master.rs | 553 +++++++++++++++++++++++-------------- src/linux.rs | 4 +- sys_util/src/guest_memory.rs | 7 + 4 files changed, 354 insertions(+), 218 deletions(-) diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs index eb19b5f..792df24 100644 --- a/devices/src/pci/ac97.rs +++ b/devices/src/pci/ac97.rs @@ -4,7 +4,7 @@ use std::os::unix::io::RawFd; -use audio_streams::StreamSource; +use audio_streams::shm_streams::ShmStreamSource; use resources::{Alloc, MmioType, SystemAllocator}; use sys_util::{error, EventFd, GuestMemory}; @@ -39,7 +39,7 @@ pub struct Ac97Dev { impl Ac97Dev { /// Creates an 'Ac97Dev' that uses the given `GuestMemory` and starts with all registers at /// default values. - pub fn new(mem: GuestMemory, audio_server: Box) -> Self { + pub fn new(mem: GuestMemory, audio_server: Box) -> Self { let config_regs = PciConfiguration::new( 0x8086, PCI_DEVICE_ID_INTEL_82801AA_5, @@ -236,13 +236,13 @@ impl PciDevice for Ac97Dev { #[cfg(test)] mod tests { use super::*; - use audio_streams::DummyStreamSource; + use audio_streams::shm_streams::MockShmStreamSource; use sys_util::GuestAddress; #[test] fn create() { let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)]).unwrap(); - let mut ac97_dev = Ac97Dev::new(mem, Box::new(DummyStreamSource::new())); + let mut ac97_dev = Ac97Dev::new(mem, Box::new(MockShmStreamSource::new())); let mut allocator = SystemAllocator::builder() .add_io_addresses(0x1000_0000, 0x1000_0000) .add_low_mmio_addresses(0x2000_0000, 0x1000_0000) diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index d3d2f85..c13913a 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -3,21 +3,21 @@ // found in the LICENSE file. use std; +use std::collections::VecDeque; +use std::convert::AsRef; use std::error::Error; use std::fmt::{self, Display}; -use std::io::Write; -use std::os::unix::io::RawFd; +use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; -use std::time::Instant; +use std::time::{Duration, Instant}; use audio_streams::{ - capture::{CaptureBuffer, CaptureBufferStream}, - PlaybackBuffer, PlaybackBufferStream, SampleFormat, StreamControl, StreamSource, + shm_streams::{ShmStream, ShmStreamSource}, + DummyStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect, }; -use data_model::{VolatileMemory, VolatileSlice}; -use sync::Mutex; +use sync::{Condvar, Mutex}; use sys_util::{ self, error, set_rt_prio_limit, set_rt_round_robin, warn, EventFd, GuestAddress, GuestMemory, }; @@ -26,6 +26,7 @@ use crate::pci::ac97_mixer::Ac97Mixer; use crate::pci::ac97_regs::*; const DEVICE_SAMPLE_RATE: usize = 48000; +const DEVICE_CHANNEL_COUNT: usize = 2; // Bus Master registers. Keeps the state of the bus master register values. Used to share the state // between the main and audio threads. @@ -76,8 +77,6 @@ impl Ac97BusMasterRegs { enum GuestMemoryError { // Failure getting the address of the audio buffer. ReadingGuestBufferAddress(sys_util::GuestMemoryError), - // Failure reading samples from guest memory. - ReadingGuestSamples(data_model::VolatileMemoryError), } impl std::error::Error for GuestMemoryError {} @@ -90,7 +89,6 @@ impl Display for GuestMemoryError { ReadingGuestBufferAddress(e) => { write!(f, "Failed to get the address of the audio buffer: {}.", e) } - ReadingGuestSamples(e) => write!(f, "Failed to read samples from guest memory: {}.", e), } } } @@ -106,12 +104,16 @@ type GuestMemoryResult = std::result::Result; // Internal error type used for reporting errors from the audio thread. #[derive(Debug)] enum AudioError { + // Failed to create a new stream. + CreateStream(Box), + // Guest did not provide a buffer when needed. + NoBufferAvailable, // Failure to read guest memory. ReadingGuestError(GuestMemoryError), - // Failure to get an buffer from the stream. - StreamError(Box), - // Failure writing to the audio output. - WritingOutput(std::io::Error), + // Failure to respond to the ServerRequest. + RespondRequest(Box), + // Failure to wait for a request from the stream. + WaitForAction(Box), } impl std::error::Error for AudioError {} @@ -121,9 +123,11 @@ impl Display for AudioError { use self::AudioError::*; match self { + CreateStream(e) => write!(f, "Failed to create audio stream: {}.", e), + NoBufferAvailable => write!(f, "No buffer was available from the Guest"), ReadingGuestError(e) => write!(f, "Failed to read guest memory: {}.", e), - StreamError(e) => write!(f, "Failed to get a buffer from the stream: {}", e), - WritingOutput(e) => write!(f, "Failed to write audio output: {}.", e), + RespondRequest(e) => write!(f, "Failed to respond to the ServerRequest: {}", e), + WaitForAction(e) => write!(f, "Failed to wait for a message from the stream: {}", e), } } } @@ -134,6 +138,7 @@ type AudioResult = std::result::Result; struct AudioThreadInfo { thread: Option>, thread_run: Arc, + thread_semaphore: Arc, stream_control: Option>, } @@ -142,6 +147,7 @@ impl AudioThreadInfo { Self { thread: None, thread_run: Arc::new(AtomicBool::new(false)), + thread_semaphore: Arc::new(Condvar::new()), stream_control: None, } } @@ -160,7 +166,7 @@ pub struct Ac97BusMaster { pi_info: AudioThreadInfo, // Audio server used to create playback or capture streams. - audio_server: Box, + audio_server: Box, // Thread for hadlind IRQ resample events from the guest. irq_resample_thread: Option>, @@ -169,7 +175,7 @@ pub struct Ac97BusMaster { impl Ac97BusMaster { /// Creates an Ac97BusMaster` object that plays audio from `mem` to streams provided by /// `audio_server`. - pub fn new(mem: GuestMemory, audio_server: Box) -> Self { + pub fn new(mem: GuestMemory, audio_server: Box) -> Self { Ac97BusMaster { mem, regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())), @@ -186,7 +192,9 @@ impl Ac97BusMaster { /// Returns any file descriptors that need to be kept open when entering a jail. pub fn keep_fds(&self) -> Option> { - self.audio_server.keep_fds() + let mut fds = self.audio_server.keep_fds(); + fds.push(self.mem.as_raw_fd()); + Some(fds) } /// Provides the events needed to raise interrupts in the guest. @@ -386,7 +394,18 @@ impl Ac97BusMaster { && func_regs.sr & SR_DCH == SR_DCH && func_regs.civ != func_regs.lvi { + if func_regs.sr & SR_CELV != 0 { + // CELV means we'd already processed the buffer at CIV. + // Move CIV to the next buffer now that LVI has moved. + func_regs.move_to_next_buffer(); + } func_regs.sr &= !(SR_DCH | SR_CELV); + + match func { + Ac97Function::Input => self.pi_info.thread_semaphore.notify_one(), + Ac97Function::Output => self.po_info.thread_semaphore.notify_one(), + Ac97Function::Microphone => (), + } } } @@ -459,74 +478,79 @@ impl Ac97BusMaster { self.regs.lock().glob_cnt = new_glob_cnt; } - fn start_audio(&mut self, func: Ac97Function, mixer: &Ac97Mixer) -> Result<(), Box> { + fn start_audio(&mut self, func: Ac97Function, mixer: &Ac97Mixer) -> AudioResult<()> { const AUDIO_THREAD_RTPRIO: u16 = 10; // Matches other cros audio clients. - let thread_info = match func { + let (direction, thread_info) = match func { Ac97Function::Microphone => return Ok(()), - Ac97Function::Input => &mut self.pi_info, - Ac97Function::Output => &mut self.po_info, + Ac97Function::Input => (StreamDirection::Capture, &mut self.pi_info), + Ac97Function::Output => (StreamDirection::Playback, &mut self.po_info), }; - let num_channels = 2; let buffer_samples = current_buffer_size(self.regs.lock().func_regs(func), &self.mem)?; - let buffer_frames = buffer_samples / num_channels; + let buffer_frames = buffer_samples / DEVICE_CHANNEL_COUNT; thread_info.thread_run.store(true, Ordering::Relaxed); let thread_run = thread_info.thread_run.clone(); + let thread_semaphore = thread_info.thread_semaphore.clone(); let thread_mem = self.mem.clone(); let thread_regs = self.regs.clone(); - match func { - Ac97Function::Input => { - let (stream_control, input_stream) = self.audio_server.new_capture_stream( - num_channels, - SampleFormat::S16LE, - DEVICE_SAMPLE_RATE, - buffer_frames, - )?; - self.pi_info.stream_control = Some(stream_control); - self.update_mixer_settings(mixer); - - self.pi_info.thread = Some(thread::spawn(move || { - if set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO)).is_err() - || set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)).is_err() - { - warn!("Failed to set audio thread to real time."); - } - if let Err(e) = - audio_in_thread(thread_regs, thread_mem, &thread_run, input_stream) - { - error!("Capture error: {}", e); - } - thread_run.store(false, Ordering::Relaxed); - })); - } - Ac97Function::Output => { - let (stream_control, output_stream) = self.audio_server.new_playback_stream( - num_channels, - SampleFormat::S16LE, - DEVICE_SAMPLE_RATE, - buffer_frames, - )?; - self.po_info.stream_control = Some(stream_control); - self.update_mixer_settings(mixer); - - self.po_info.thread = Some(thread::spawn(move || { - if set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO)).is_err() - || set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)).is_err() - { - warn!("Failed to set audio thread to real time."); - } - if let Err(e) = - audio_out_thread(thread_regs, thread_mem, &thread_run, output_stream) - { - error!("Playback error: {}", e); - } - thread_run.store(false, Ordering::Relaxed); - })); + let mut pending_buffers = VecDeque::with_capacity(2); + let starting_offsets = match direction { + StreamDirection::Capture => { + let mut offsets = [0, 0]; + let mut locked_regs = self.regs.lock(); + for i in 0..2 { + let buffer = next_guest_buffer(&mut locked_regs, &self.mem, func, 0)? + .ok_or(AudioError::NoBufferAvailable)?; + offsets[i] = buffer.offset as u64; + pending_buffers.push_back(Some(buffer)); + } + offsets } - Ac97Function::Microphone => (), + StreamDirection::Playback => [0, 0], }; + let stream = self + .audio_server + .new_stream( + direction, + DEVICE_CHANNEL_COUNT, + SampleFormat::S16LE, + DEVICE_SAMPLE_RATE, + buffer_frames, + StreamEffect::NoEffect, + self.mem.as_ref(), + starting_offsets, + ) + .map_err(AudioError::CreateStream)?; + + thread_info.stream_control = Some(Box::new(DummyStreamControl::new())); + thread_info.thread = Some(thread::spawn(move || { + if let Err(e) = set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO)) + .and_then(|_| set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO))) + { + warn!("Failed to set audio thread to real time: {}", e); + } + + let message_interval = + Duration::from_secs_f64(buffer_frames as f64 / DEVICE_SAMPLE_RATE as f64); + + if let Err(e) = audio_thread( + func, + thread_regs, + thread_mem, + &thread_run, + thread_semaphore, + message_interval, + stream, + pending_buffers, + ) { + error!("{:?} error: {}", func, e); + } + thread_run.store(false, Ordering::Relaxed); + })); + self.update_mixer_settings(mixer); + Ok(()) } @@ -537,6 +561,7 @@ impl Ac97BusMaster { Ac97Function::Output => &mut self.po_info, }; thread_info.thread_run.store(false, Ordering::Relaxed); + thread_info.thread_semaphore.notify_one(); if let Some(thread) = thread_info.thread.take() { if let Err(e) = thread.join() { error!("Failed to join {:?} thread: {:?}.", func, e); @@ -565,62 +590,88 @@ impl Ac97BusMaster { } } -// Gets the next buffer from the guest. This will return `None` if the DMA controlled stopped bit is -// set, such as after an underrun where CIV hits LVI. -fn next_guest_buffer<'a>( - func_regs: &mut Ac97FunctionRegs, - mem: &'a GuestMemory, -) -> GuestMemoryResult>> { - let sample_size = 2; +#[derive(Debug)] +struct GuestBuffer { + index: u8, + offset: usize, + frames: usize, +} - if func_regs.sr & SR_DCH != 0 { - return Ok(None); - } - let next_buffer = func_regs.civ; - let descriptor_addr = func_regs.bdbar + u32::from(next_buffer) * DESCRIPTOR_LENGTH as u32; +fn get_buffer_offset( + func_regs: &Ac97FunctionRegs, + mem: &GuestMemory, + index: u8, +) -> GuestMemoryResult { + let descriptor_addr = func_regs.bdbar + u32::from(index) * DESCRIPTOR_LENGTH as u32; let buffer_addr_reg: u32 = mem .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr))) .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; - let buffer_addr = buffer_addr_reg & !0x03u32; // The address must be aligned to four bytes. + let buffer_addr = GuestAddress((buffer_addr_reg & !0x03u32) as u64); // The address must be aligned to four bytes. + + mem.offset_from_base(buffer_addr) + .map_err(GuestMemoryError::ReadingGuestBufferAddress) +} + +fn get_buffer_samples( + func_regs: &Ac97FunctionRegs, + mem: &GuestMemory, + index: u8, +) -> GuestMemoryResult { + let descriptor_addr = func_regs.bdbar + u32::from(index) * DESCRIPTOR_LENGTH as u32; let control_reg: u32 = mem .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr) + 4)) .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; - let buffer_samples: usize = control_reg as usize & 0x0000_ffff; + let buffer_samples = control_reg as usize & 0x0000_ffff; + Ok(buffer_samples) +} - func_regs.picb = buffer_samples as u16; +// Gets the start address and length of the buffer at `civ + offset` from the +// guest. +// This will return `None` if `civ + offset` is past LVI; if the DMA controlled +// stopped bit is set, such as after an underrun where CIV hits LVI; or if +// `civ + offset == LVI and the CELV flag is set. +fn next_guest_buffer<'a>( + regs: &Ac97BusMasterRegs, + mem: &GuestMemory, + func: Ac97Function, + offset: usize, +) -> AudioResult> { + let func_regs = regs.func_regs(func); + let offset = (offset % 32) as u8; + let index = (func_regs.civ + offset) % 32; + + // Check that value is between `low` and `high` modulo some `n`. + fn check_between(low: u8, high: u8, value: u8) -> bool { + // If low <= high, value must be in the interval between them: + // 0 l h n + // ......+++++++...... + (low <= high && (low <= value && value <= high)) || + // If low > high, value must not be in the interval between them: + // 0 h l n + // +++++++++......++++ + (low > high && (low <= value || value <= high)) + }; - let samples_remaining = func_regs.picb as usize; - if samples_remaining == 0 { + // Check if + // * we're halted + // * `index` is not between CIV and LVI (mod 32) + // * `index is LVI and we've already processed LVI (SR_CELV is set) + // if any of these are true `index` isn't valid. + if func_regs.sr & SR_DCH != 0 + || !check_between(func_regs.civ, func_regs.lvi, index) + || func_regs.sr & SR_CELV != 0 + { return Ok(None); } - let read_pos = u64::from(buffer_addr); - Ok(Some( - mem.get_slice(read_pos, samples_remaining as u64 * sample_size) - .map_err(GuestMemoryError::ReadingGuestSamples)?, - )) -} -// Reads the next buffer from guest memory and writes it to `out_buffer`. -fn play_buffer( - regs: &mut Ac97BusMasterRegs, - mem: &GuestMemory, - out_buffer: &mut PlaybackBuffer, -) -> AudioResult<()> { - // If the current buffer had any samples in it, mark it as done. - if regs.func_regs_mut(Ac97Function::Output).picb > 0 { - buffer_completed(regs, mem, Ac97Function::Output)? - } - let func_regs = regs.func_regs_mut(Ac97Function::Output); - let buffer_len = func_regs.picb * 2; - if let Some(buffer) = next_guest_buffer(func_regs, mem)? { - out_buffer.copy_cb(buffer.size() as usize, |out| buffer.copy_to(out)); - } else { - let zeros = vec![0u8; buffer_len as usize]; - out_buffer - .write(&zeros) - .map_err(AudioError::WritingOutput)?; - } - Ok(()) + let offset = get_buffer_offset(func_regs, mem, index)?; + let frames = get_buffer_samples(func_regs, mem, index)? / DEVICE_CHANNEL_COUNT; + + Ok(Some(GuestBuffer { + index, + offset, + frames, + })) } // Marks the current buffer completed and moves to the next buffer for the given @@ -629,7 +680,7 @@ fn buffer_completed( regs: &mut Ac97BusMasterRegs, mem: &GuestMemory, func: Ac97Function, -) -> GuestMemoryResult<()> { +) -> AudioResult<()> { // check if the completed descriptor wanted an interrupt on completion. let civ = regs.func_regs(func).civ; let descriptor_addr = regs.func_regs(func).bdbar + u32::from(civ) * DESCRIPTOR_LENGTH as u32; @@ -661,51 +712,115 @@ fn buffer_completed( Ok(()) } -// Runs, playing back audio from the guest to `output_stream` until stopped or an error occurs. -fn audio_out_thread( +// Runs and updates the offset within the stream shm where samples can be +// found/placed for shm playback/capture streams, respectively +fn audio_thread( + func: Ac97Function, regs: Arc>, mem: GuestMemory, thread_run: &AtomicBool, - mut output_stream: Box, + lvi_semaphore: Arc, + message_interval: Duration, + mut stream: Box, + // A queue of the pending buffers at the server. + mut pending_buffers: VecDeque>, ) -> AudioResult<()> { - while thread_run.load(Ordering::Relaxed) { - output_stream - .next_playback_buffer() - .map_err(AudioError::StreamError) - .and_then(|mut pb_buf| play_buffer(&mut regs.lock(), &mem, &mut pb_buf))?; + if func == Ac97Function::Microphone { + return Ok(()); } - Ok(()) -} -// Reads samples from `in_buffer` and writes it to the next buffer from guest memory. -fn capture_buffer( - regs: &mut Ac97BusMasterRegs, - mem: &GuestMemory, - in_buffer: &mut CaptureBuffer, -) -> AudioResult<()> { - // If the current buffer had any samples in it, mark it as done. - if regs.func_regs_mut(Ac97Function::Input).picb > 0 { - buffer_completed(regs, mem, Ac97Function::Input)? - } - let func_regs = regs.func_regs_mut(Ac97Function::Input); - if let Some(buffer) = next_guest_buffer(func_regs, mem)? { - in_buffer.copy_cb(buffer.size() as usize, |inb| buffer.copy_from(inb)) + // Set up picb. + { + let mut locked_regs = regs.lock(); + locked_regs.func_regs_mut(func).picb = + current_buffer_size(locked_regs.func_regs(func), &mem)? as u16; } - Ok(()) -} -// Runs, capturing audio from `input_stream` to the guest until stopped or an error occurs. -fn audio_in_thread( - regs: Arc>, - mem: GuestMemory, - thread_run: &AtomicBool, - mut input_stream: Box, -) -> AudioResult<()> { - while thread_run.load(Ordering::Relaxed) { - input_stream - .next_capture_buffer() - .map_err(AudioError::StreamError) - .and_then(|mut cp_buf| capture_buffer(&mut regs.lock(), &mem, &mut cp_buf))?; + 'audio_loop: while thread_run.load(Ordering::Relaxed) { + { + let mut locked_regs = regs.lock(); + while locked_regs.func_regs(func).sr & SR_DCH != 0 { + locked_regs = lvi_semaphore.wait(locked_regs); + if !thread_run.load(Ordering::Relaxed) { + break 'audio_loop; + } + } + } + + let timeout = Duration::from_secs(1); + let action = stream + .wait_for_next_action_with_timeout(timeout) + .map_err(AudioError::WaitForAction)?; + + let request = match action { + None => { + warn!("No audio message received within timeout of {:?}", timeout); + continue; + } + Some(request) => request, + }; + let start = Instant::now(); + + let next_buffer = { + let mut locked_regs = regs.lock(); + if pending_buffers.len() == 2 { + // When we have two pending buffers and receive a request for + // another, we know that oldest buffer has been completed. + // However, if that old buffer was an empty buffer we sent + // because the guest driver had no available buffers, we don't + // want to mark a buffer complete. + if let Some(Some(_)) = pending_buffers.pop_front() { + buffer_completed(&mut locked_regs, &mem, func)?; + } + } + + // We count the number of pending, real buffers at the server, and + // then use that as our offset from CIV. + let offset = pending_buffers.iter().filter(|e| e.is_some()).count(); + + // Get a buffer to respond to our request. If there's no buffer + // available, we'll wait one buffer interval and check again. + loop { + if let Some(buffer) = next_guest_buffer(&mut locked_regs, &mem, func, offset)? { + break Some(buffer); + } + let elapsed = start.elapsed(); + if elapsed > message_interval { + break None; + } + locked_regs = lvi_semaphore + .wait_timeout(locked_regs, message_interval - elapsed) + .0; + } + }; + + match next_buffer { + Some(ref buffer) => { + let requested_frames = request.requested_frames(); + if requested_frames != buffer.frames { + // We should be able to handle when the number of frames in + // the buffer doesn't match the number of frames requested, + // but we don't yet. + warn!( + "Stream requested {} frames but buffer had {} frames: {:?}", + requested_frames, buffer.frames, buffer + ); + } + + request + .set_buffer_offset_and_frames( + buffer.offset, + std::cmp::min(requested_frames, buffer.frames), + ) + .map_err(AudioError::RespondRequest)?; + } + None => { + request + .ignore_request() + .map_err(AudioError::RespondRequest)?; + } + } + pending_buffers.push_back(next_buffer); } Ok(()) } @@ -753,27 +868,20 @@ fn current_buffer_size( mem: &GuestMemory, ) -> GuestMemoryResult { let civ = func_regs.civ; - let descriptor_addr = func_regs.bdbar + u32::from(civ) * DESCRIPTOR_LENGTH as u32; - let control_reg: u32 = mem - .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr) + 4)) - .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; - let buffer_len: usize = control_reg as usize & 0x0000_ffff; - Ok(buffer_len) + get_buffer_samples(func_regs, mem, civ) } #[cfg(test)] mod test { use super::*; - use std::time; - - use audio_streams::DummyStreamSource; + use audio_streams::shm_streams::MockShmStreamSource; #[test] fn bm_bdbar() { let mut bm = Ac97BusMaster::new( GuestMemory::new(&[]).expect("Creating guest memory failed."), - Box::new(DummyStreamSource::new()), + Box::new(MockShmStreamSource::new()), ); let bdbars = [0x00u64, 0x10, 0x20]; @@ -797,7 +905,7 @@ mod test { fn bm_status_reg() { let mut bm = Ac97BusMaster::new( GuestMemory::new(&[]).expect("Creating guest memory failed."), - Box::new(DummyStreamSource::new()), + Box::new(MockShmStreamSource::new()), ); let sr_addrs = [0x06u64, 0x16, 0x26]; @@ -813,7 +921,7 @@ mod test { fn bm_global_control() { let mut bm = Ac97BusMaster::new( GuestMemory::new(&[]).expect("Creating guest memory failed."), - Box::new(DummyStreamSource::new()), + Box::new(MockShmStreamSource::new()), ); assert_eq!(bm.readl(GLOB_CNT_2C), 0x0000_0000); @@ -839,6 +947,7 @@ mod test { #[test] fn start_playback() { + const TIMEOUT: Duration = Duration::from_millis(500); const LVI_MASK: u8 = 0x1f; // Five bits for 32 total entries. const IOC_MASK: u32 = 0x8000_0000; // Interrupt on completion. let num_buffers = LVI_MASK as usize + 1; @@ -848,7 +957,8 @@ mod test { const GUEST_ADDR_BASE: u32 = 0x100_0000; let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 1024)]) .expect("Creating guest memory failed."); - let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(DummyStreamSource::new())); + let stream_source = MockShmStreamSource::new(); + let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(stream_source.clone())); let mixer = Ac97Mixer::new(); // Release cold reset. @@ -871,33 +981,26 @@ mod test { } bm.writeb(PO_LVI_15, LVI_MASK, &mixer); + assert_eq!(bm.readb(PO_CIV_14), 0); // Start. bm.writeb(PO_CR_1B, CR_IOCE | CR_RPBM, &mixer); + assert_eq!(bm.readw(PO_PICB_18), 0); + + let mut stream = stream_source.get_last_stream(); + // Trigger callback and see that CIV has not changed, since only 1 + // buffer has been sent. + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); - std::thread::sleep(time::Duration::from_millis(50)); - let picb = bm.readw(PO_PICB_18); let mut civ = bm.readb(PO_CIV_14); assert_eq!(civ, 0); - let pos = (FRAGMENT_SIZE - (picb as usize * 2)) / 4; - // Check that frames are consumed at least at a reasonable rate. - // This wont be exact as during unit tests the thread scheduling is highly variable, so the - // test only checks that some samples are consumed. - assert!(pos > 1000); - - assert!(bm.readw(PO_SR_16) & SR_DCH == 0); // DMA is running. - - // civ should move eventually. - for _i in 0..30 { - if civ != 0 { - break; - } - std::thread::sleep(time::Duration::from_millis(20)); - civ = bm.readb(PO_CIV_14); - } - - assert_ne!(0, civ); + // After two more callbacks, CIV should now be 1 since we know that the + // first buffer must have been played. + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + civ = bm.readb(PO_CIV_14); + assert_eq!(civ, 1); // Buffer complete should be set as the IOC bit was set in the descriptor. assert!(bm.readw(PO_SR_16) & SR_BCIS != 0); @@ -905,17 +1008,30 @@ mod test { bm.writew(PO_SR_16, SR_BCIS); assert!(bm.readw(PO_SR_16) & SR_BCIS == 0); - // Set last valid to the next and wait until it is hit. - bm.writeb(PO_LVI_15, civ + 1, &mixer); - std::thread::sleep(time::Duration::from_millis(500)); + std::thread::sleep(Duration::from_millis(50)); + let picb = bm.readw(PO_PICB_18); + let pos = (FRAGMENT_SIZE - (picb as usize * 2)) / 4; + + // Check that frames are consumed at least at a reasonable rate. + // This can't be exact as during unit tests the thread scheduling is highly variable, so the + // test only checks that some samples are consumed. + assert!(pos > 0); + assert!(bm.readw(PO_SR_16) & SR_DCH == 0); // DMA is running. + + // Set last valid to next buffer to be sent and trigger callback so we hit it. + bm.writeb(PO_LVI_15, civ + 2, &mixer); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); assert!(bm.readw(PO_SR_16) & SR_LVBCI != 0); // Hit last buffer assert!(bm.readw(PO_SR_16) & SR_DCH == SR_DCH); // DMA stopped because of lack of buffers. - assert_eq!(bm.readw(PO_SR_16) & SR_CELV, SR_CELV); + assert!(bm.readw(PO_SR_16) & SR_CELV == SR_CELV); // Processed the last buffer assert_eq!(bm.readb(PO_LVI_15), bm.readb(PO_CIV_14)); assert!( bm.readl(GLOB_STA_30) & GS_POINT != 0, "POINT bit should be set." ); + // Clear the LVB bit bm.writeb(PO_SR_16, SR_LVBCI as u8, &mixer); assert!(bm.readw(PO_SR_16) & SR_LVBCI == 0); @@ -924,9 +1040,11 @@ mod test { assert!(bm.readw(PO_SR_16) & SR_DCH == 0); // DMA restarts. assert_eq!(bm.readw(PO_SR_16) & SR_CELV, 0); - let (restart_civ, restart_picb) = (bm.readb(PO_CIV_14), bm.readw(PO_PICB_18)); - std::thread::sleep(time::Duration::from_millis(20)); - assert!(bm.readw(PO_PICB_18) != restart_picb || bm.readb(PO_CIV_14) != restart_civ); + let restart_civ = bm.readb(PO_CIV_14); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(bm.readb(PO_CIV_14) != restart_civ); // Stop. bm.writeb(PO_CR_1B, 0, &mixer); @@ -940,6 +1058,7 @@ mod test { #[test] fn start_capture() { + const TIMEOUT: Duration = Duration::from_millis(500); const LVI_MASK: u8 = 0x1f; // Five bits for 32 total entries. const IOC_MASK: u32 = 0x8000_0000; // Interrupt on completion. let num_buffers = LVI_MASK as usize + 1; @@ -949,7 +1068,8 @@ mod test { const GUEST_ADDR_BASE: u32 = 0x100_0000; let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 1024)]) .expect("Creating guest memory failed."); - let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(DummyStreamSource::new())); + let stream_source = MockShmStreamSource::new(); + let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(stream_source.clone())); let mixer = Ac97Mixer::new(); // Release cold reset. @@ -972,25 +1092,32 @@ mod test { bm.writeb(PI_CR_0B, CR_IOCE | CR_RPBM, &mixer); assert_eq!(bm.readw(PI_PICB_08), 0); - std::thread::sleep(time::Duration::from_millis(50)); + let mut stream = stream_source.get_last_stream(); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + + // CIV is 1 here since we preemptively sent two buffer indices to the + // server before creating the stream. When we triggered the callback + // above, that means the first of those buffers was filled, so CIV + // increments to 1. + let civ = bm.readb(PI_CIV_04); + assert_eq!(civ, 1); + std::thread::sleep(Duration::from_millis(20)); let picb = bm.readw(PI_PICB_08); - assert!(picb > 1000); + assert!(picb > 0); assert!(bm.readw(PI_SR_06) & SR_DCH == 0); // DMA is running. - // civ should move eventually. - for _i in 0..10 { - let civ = bm.readb(PI_CIV_04); - if civ != 0 { - break; - } - std::thread::sleep(time::Duration::from_millis(20)); - } - assert_ne!(bm.readb(PI_CIV_04), 0); + // Trigger 2 callbacks so that we'll move to buffer 3 since at that + // point we can be certain that buffers 1 and 2 have been captured to. + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert_eq!(bm.readb(PI_CIV_04), 3); let civ = bm.readb(PI_CIV_04); - // Sets LVI to CIV + 1 to trigger last buffer hit - bm.writeb(PI_LVI_05, civ + 1, &mixer); - std::thread::sleep(time::Duration::from_millis(5000)); + // Sets LVI to CIV + 2 to trigger last buffer hit + bm.writeb(PI_LVI_05, civ + 2, &mixer); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); assert_ne!(bm.readw(PI_SR_06) & SR_LVBCI, 0); // Hit last buffer assert_eq!(bm.readw(PI_SR_06) & SR_DCH, SR_DCH); // DMA stopped because of lack of buffers. assert_eq!(bm.readw(PI_SR_06) & SR_CELV, SR_CELV); @@ -1009,7 +1136,9 @@ mod test { assert_eq!(bm.readw(PI_SR_06) & SR_CELV, 0); let restart_civ = bm.readb(PI_CIV_04); - std::thread::sleep(time::Duration::from_millis(200)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert!(stream.trigger_callback_with_timeout(TIMEOUT)); assert_ne!(bm.readb(PI_CIV_04), restart_civ); // Stop. diff --git a/src/linux.rs b/src/linux.rs index f5d2d1c..be2df22 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -27,7 +27,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use libc::{self, c_int, gid_t, uid_t}; -use audio_streams::DummyStreamSource; +use audio_streams::shm_streams::NullShmStreamSource; #[cfg(feature = "gpu")] use devices::virtio::EventDevice; use devices::virtio::{self, VirtioDevice}; @@ -1171,7 +1171,7 @@ fn create_devices( } if cfg.null_audio { - let server = Box::new(DummyStreamSource::new()); + let server = Box::new(NullShmStreamSource::new()); let null_audio = devices::Ac97Dev::new(mem.clone(), server); pci_devices.push(( diff --git a/sys_util/src/guest_memory.rs b/sys_util/src/guest_memory.rs index 6399f8c..2390b92 100644 --- a/sys_util/src/guest_memory.rs +++ b/sys_util/src/guest_memory.rs @@ -4,6 +4,7 @@ //! Track memory regions that are mapped to the guest VM. +use std::convert::AsRef; use std::convert::TryFrom; use std::fmt::{self, Display}; use std::os::unix::io::{AsRawFd, RawFd}; @@ -107,6 +108,12 @@ impl AsRawFd for GuestMemory { } } +impl AsRef for GuestMemory { + fn as_ref(&self) -> &SharedMemory { + &self.memfd + } +} + impl GuestMemory { /// Creates backing memfd for GuestMemory regions fn create_memfd(ranges: &[(GuestAddress, u64)]) -> Result { -- cgit 1.4.1 From 05d30607bc70b2c3f79337405a35bc4be43a1660 Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Fri, 1 Nov 2019 15:50:58 +0800 Subject: acpi: allocate sci_irq instead of use fixed number sci_irq can be allocated so not to use the fixed number 9. Actually this irq is not used for injecting any event but the Linux guest OS requires to see meaning value from the FADP table. So just fill it to satisfy. BUG=chromium:1018674 TEST=None Change-Id: If3ea3bb2844fc7fc1c24a577b7098d2a3e6f1c7f Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035352 Tested-by: kokoro Reviewed-by: Tomasz Jeznach --- x86_64/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 20831cd..b6f18e3 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -66,6 +66,7 @@ use sys_util::{Clock, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; #[sorted] #[derive(Debug)] pub enum Error { + AllocateIrq, CloneEventFd(sys_util::Error), Cmdline(kernel_cmdline::Error), ConfigureSystem, @@ -112,6 +113,7 @@ impl Display for Error { #[sorted] match self { + AllocateIrq => write!(f, "error allocating a single irq"), CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e), Cmdline(e) => write!(f, "the given kernel command line was invalid: {}", e), ConfigureSystem => write!(f, "error configuring the system"), @@ -192,6 +194,7 @@ fn configure_system( setup_data: Option, initrd: Option<(GuestAddress, usize)>, mut params: boot_params, + sci_irq: u32, ) -> Result<()> { const EBDA_START: u64 = 0x0009fc00; const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; @@ -255,7 +258,7 @@ fn configure_system( .write_obj_at_addr(params, zero_page_addr) .map_err(|_| Error::ZeroPageSetup)?; - let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, 9); + let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, sci_irq); params.acpi_rsdp_addr = rsdp_addr.0; Ok(()) @@ -368,6 +371,8 @@ impl arch::LinuxArch for X8664arch { // Event used to notify crosvm that guest OS is trying to suspend. let suspend_evt = EventFd::new().map_err(Error::CreateEventFd)?; + // allocate sci_irq to fill the ACPI FACP table + let sci_irq = resources.allocate_irq().ok_or(Error::AllocateIrq)?; let mut io_bus = Self::setup_io_bus( &mut vm, @@ -430,6 +435,7 @@ impl arch::LinuxArch for X8664arch { components.android_fstab, kernel_end, params, + sci_irq, )?; } } @@ -514,6 +520,7 @@ impl X8664arch { android_fstab: Option, kernel_end: u64, params: boot_params, + sci_irq: u32, ) -> Result<()> { kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline) .map_err(Error::LoadCmdline)?; @@ -575,6 +582,7 @@ impl X8664arch { setup_data, initrd, params, + sci_irq, )?; Ok(()) } -- cgit 1.4.1 From 93cb038258eb4b29bc42f273181986e0c5702bf6 Mon Sep 17 00:00:00 2001 From: Chuanxiao Dong Date: Sat, 22 Feb 2020 09:32:30 +0800 Subject: acpi: add S1 capability in DSDT S1 is a sleep state which is lighter than S3, but enough to put the devices into the suspended state. With this DSDT, the guest kernel is able to see the S1 capability and to put the VM into virtual S1. During the virtual S1, guest kernel will call each suspend callbacks of the devices driver to put the devices into the suspend state, including the pass through device. BUG=chromium:1018674 TEST=boot linux guest with ACPI enabled, and able to see "shallow" by "cat /sys/power/mem_sleep", which means the S1 Change-Id: I232609f6f2474895fd9ec4046d88236c413e51af Signed-off-by: Chuanxiao Dong Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2035353 Tested-by: kokoro Reviewed-by: Tomasz Jeznach --- x86_64/src/acpi.rs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs index 7030bd8..3600d16 100644 --- a/x86_64/src/acpi.rs +++ b/x86_64/src/acpi.rs @@ -65,6 +65,34 @@ const MADT_ENABLED: u32 = 1; // XSDT const XSDT_REVISION: u8 = 1; +fn create_dsdt_table() -> SDT { + // The hex tables in this file are generated from the ASL below with: + // "iasl -tc " + // Below is the tables represents by the pm_dsdt_data + // Name (_S1, Package (0x04) // _S1_: S1 System State + // { + // One, + // One, + // Zero, + // Zero + // }) + let pm_dsdt_data = [ + 0x08u8, 0x5F, 0x53, 0x31, 0x5f, 0x12, 0x06, 0x04, 0x01, 0x01, 0x00, 0x00, + ]; + + let mut dsdt = SDT::new( + *b"DSDT", + acpi_tables::HEADER_LEN, + DSDT_REVISION, + *b"CROSVM", + *b"CROSVMDT", + OEM_REVISION, + ); + dsdt.append(pm_dsdt_data); + + dsdt +} + /// Create ACPI tables and return the RSDP. /// The basic tables DSDT/FACP/MADT/XSDT are constructed in this function. /// # Arguments @@ -80,14 +108,7 @@ pub fn create_acpi_tables(guest_mem: &GuestMemory, num_cpus: u8, sci_irq: u32) - let mut tables: Vec = Vec::new(); // DSDT - let dsdt = SDT::new( - *b"DSDT", - acpi_tables::HEADER_LEN, - DSDT_REVISION, - *b"CROSVM", - *b"CROSVMDT", - OEM_REVISION, - ); + let dsdt = create_dsdt_table(); let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap(); guest_mem .write_at_addr(dsdt.as_slice(), dsdt_offset) -- cgit 1.4.1 From 80fb0753b356d544675f475a770f6bfcb21e8a1c Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Thu, 2 Jan 2020 16:04:36 +0800 Subject: Virtio: Add queue ack_features() interface Virtio spec define some features like VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_PACKED. If one feature is enabled in guest, device's queue will have corresponding behavior for this feature. Queue's ack_features() interface let queue know which features is enabled in guest. BUG=None TEST=build_test Change-Id: I865f93940a1f4db8ca6da8829136127353cc1c6f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2008340 Tested-by: kokoro Commit-Queue: Xiong Zhang Reviewed-by: Daniel Verkamp --- devices/src/virtio/queue.rs | 10 ++++++++++ devices/src/virtio/virtio_pci_common_config.rs | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index 15dc3e1..e33e3ba 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -224,6 +224,9 @@ pub struct Queue { next_avail: Wrapping, next_used: Wrapping, + + // Device feature bits accepted by the driver + features: u64, } impl Queue { @@ -239,6 +242,7 @@ impl Queue { used_ring: GuestAddress(0), next_avail: Wrapping(0), next_used: Wrapping(0), + features: 0, } } @@ -258,6 +262,7 @@ impl Queue { self.used_ring = GuestAddress(0); self.next_avail = Wrapping(0); self.next_used = Wrapping(0); + self.features = 0; } pub fn is_valid(&self, mem: &GuestMemory) -> bool { @@ -413,4 +418,9 @@ impl Queue { interrupt.signal_used_queue(self.vector); } } + + /// Acknowledges that this set of features should be enabled on this queue. + pub fn ack_features(&mut self, features: u64) { + self.features |= features; + } } diff --git a/devices/src/virtio/virtio_pci_common_config.rs b/devices/src/virtio/virtio_pci_common_config.rs index 78af4ff..97d7001 100644 --- a/devices/src/virtio/virtio_pci_common_config.rs +++ b/devices/src/virtio/virtio_pci_common_config.rs @@ -185,7 +185,11 @@ impl VirtioPciCommonConfig { 0x08 => self.driver_feature_select = value, 0x0c => { if self.driver_feature_select < 2 { - device.ack_features((value as u64) << (self.driver_feature_select * 32)); + let features: u64 = (value as u64) << (self.driver_feature_select * 32); + device.ack_features(features); + for queue in queues.iter_mut() { + queue.ack_features(features); + } } else { warn!( "invalid ack_features (page {}, value 0x{:x})", -- cgit 1.4.1 From 50740cece43671cc42035f92cde460aad3d29494 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Fri, 28 Feb 2020 12:36:56 -0800 Subject: linux: fix unused code warning without gpu feature When the gpu feature is not enabled, the compiler warns about unused imports of DEFAULT_TOUCH_DEVICE_HEIGHT and DEFAULT_TOUCH_DEVICE_WIDTH. Put these imports behind a cfg check to silence the warning. BUG=None TEST=cargo build TEST=emerge-nami crosvm # test with gpu feature enabled Change-Id: Ib60072cc78a8ffd0ac39dcb4d3a60ba4c9d424b5 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2080645 Reviewed-by: Dylan Reid Tested-by: kokoro Commit-Queue: Daniel Verkamp --- src/linux.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/linux.rs b/src/linux.rs index be2df22..007e18e 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -62,10 +62,10 @@ use vm_control::{ VmRunMode, }; -use crate::{ - Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption, - DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH, -}; +use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption}; + +#[cfg(feature = "gpu")] +use crate::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH}; use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; -- cgit 1.4.1 From f2e90bf0b0ca101d2925e91ca50d3e9e5ea2fdb7 Mon Sep 17 00:00:00 2001 From: Zhuocheng Ding Date: Mon, 2 Dec 2019 15:50:20 +0800 Subject: Add logic to setup PIC/IOAPIC. TODO: Route irqfd to PIC/IOAPIC to make them fully work. BUG=chromium:908689 TEST=None Change-Id: I301287b1cf32cfccffce6c52ebbb5e123931178e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1945796 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Zhuocheng Ding --- Cargo.lock | 3 +++ aarch64/Cargo.toml | 1 + aarch64/src/lib.rs | 3 +++ arch/Cargo.toml | 1 + arch/src/lib.rs | 3 +++ devices/src/ioapic.rs | 14 ++++++-------- devices/src/lib.rs | 2 +- kvm/src/lib.rs | 9 +++++++++ src/linux.rs | 14 ++++++++++++++ x86_64/Cargo.toml | 1 + x86_64/src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 11 files changed, 80 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6210b6c..bb75235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", + "vm_control 0.1.0", ] [[package]] @@ -37,6 +38,7 @@ dependencies = [ "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", + "vm_control 0.1.0", ] [[package]] @@ -776,6 +778,7 @@ dependencies = [ "resources 0.1.0", "sync 0.1.0", "sys_util 0.1.0", + "vm_control 0.1.0", ] [metadata] diff --git a/aarch64/Cargo.toml b/aarch64/Cargo.toml index 8c754d1..7e346ac 100644 --- a/aarch64/Cargo.toml +++ b/aarch64/Cargo.toml @@ -17,3 +17,4 @@ remain = "*" resources = { path = "../resources" } sync = { path = "../sync" } sys_util = { path = "../sys_util" } +vm_control = { path = "../vm_control" } diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index 67fad72..d4c4a50 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -21,6 +21,7 @@ use remain::sorted; use resources::SystemAllocator; use sync::Mutex; use sys_util::{EventFd, GuestAddress, GuestMemory, GuestMemoryError}; +use vm_control::VmIrqRequestSocket; use kvm::*; use kvm_sys::kvm_device_attr; @@ -195,6 +196,7 @@ impl arch::LinuxArch for AArch64 { fn build_vm( mut components: VmComponents, _split_irqchip: bool, + _ioapic_device_socket: VmIrqRequestSocket, serial_parameters: &BTreeMap, serial_jail: Option, create_devices: F, @@ -314,6 +316,7 @@ impl arch::LinuxArch for AArch64 { vcpus, vcpu_affinity, irq_chip, + split_irqchip: None, io_bus, mmio_bus, pid_debug_label_map, diff --git a/arch/Cargo.toml b/arch/Cargo.toml index bf28560..6b4e529 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -13,3 +13,4 @@ libc = "*" resources = { path = "../resources" } sync = { path = "../sync" } sys_util = { path = "../sys_util" } +vm_control = { path = "../vm_control" } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 6af78e3..5112798 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -25,6 +25,7 @@ use kvm::{IoeventAddress, Kvm, Vcpu, Vm}; use resources::SystemAllocator; use sync::Mutex; use sys_util::{syslog, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; +use vm_control::VmIrqRequestSocket; pub enum VmImage { Kernel(File), @@ -60,6 +61,7 @@ pub struct RunnableLinuxVm { pub vcpus: Vec, pub vcpu_affinity: Vec, pub irq_chip: Option, + pub split_irqchip: Option<(Arc>, Arc>)>, pub io_bus: Bus, pub mmio_bus: Bus, pub pid_debug_label_map: BTreeMap, @@ -88,6 +90,7 @@ pub trait LinuxArch { fn build_vm( components: VmComponents, split_irqchip: bool, + ioapic_device_socket: VmIrqRequestSocket, serial_parameters: &BTreeMap, serial_jail: Option, create_devices: F, diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index 3b5ea80..e1fbfb8 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -40,11 +40,9 @@ pub enum DeliveryStatus { } const IOAPIC_VERSION_ID: u32 = 0x00170011; -#[allow(dead_code)] -const IOAPIC_BASE_ADDRESS: u32 = 0xfec00000; +pub const IOAPIC_BASE_ADDRESS: u64 = 0xfec00000; // The Intel manual does not specify this size, but KVM uses it. -#[allow(dead_code)] -const IOAPIC_MEM_LENGTH_BYTES: usize = 0x100; +pub const IOAPIC_MEM_LENGTH_BYTES: u64 = 0x100; // Constants for IOAPIC direct register offset. const IOAPIC_REG_ID: u8 = 0x00; @@ -52,10 +50,10 @@ const IOAPIC_REG_VERSION: u8 = 0x01; const IOAPIC_REG_ARBITRATION_ID: u8 = 0x02; // Register offsets -pub const IOREGSEL_OFF: u8 = 0x0; -pub const IOREGSEL_DUMMY_UPPER_32_BITS_OFF: u8 = 0x4; -pub const IOWIN_OFF: u8 = 0x10; -pub const IOWIN_SCALE: u8 = 0x2; +const IOREGSEL_OFF: u8 = 0x0; +const IOREGSEL_DUMMY_UPPER_32_BITS_OFF: u8 = 0x4; +const IOWIN_OFF: u8 = 0x10; +const IOWIN_SCALE: u8 = 0x2; /// Given an IRQ and whether or not the selector should refer to the high bits, return a selector /// suitable to use as an offset to read to/write from. diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 174b956..2319d86 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -28,7 +28,7 @@ pub use self::bus::Error as BusError; pub use self::bus::{Bus, BusDevice, BusRange, BusResumeDevice}; pub use self::cmos::Cmos; pub use self::i8042::I8042Device; -pub use self::ioapic::Ioapic; +pub use self::ioapic::{Ioapic, IOAPIC_BASE_ADDRESS, IOAPIC_MEM_LENGTH_BYTES}; pub use self::pci::{ Ac97Dev, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, VfioPciDevice, diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index b900505..66ebd20 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1121,6 +1121,9 @@ pub enum VcpuExit { size: usize, data: [u8; 8], }, + IoapicEoi { + vector: u8, + }, Unknown, Exception, Hypercall, @@ -1811,6 +1814,12 @@ impl RunnableVcpu { Ok(VcpuExit::MmioRead { address, size }) } } + KVM_EXIT_IOAPIC_EOI => { + // Safe because the exit_reason (which comes from the kernel) told us which + // union field to use. + let vector = unsafe { run.__bindgen_anon_1.eoi.vector }; + Ok(VcpuExit::IoapicEoi { vector }) + } KVM_EXIT_UNKNOWN => Ok(VcpuExit::Unknown), KVM_EXIT_EXCEPTION => Ok(VcpuExit::Exception), KVM_EXIT_HYPERCALL => Ok(VcpuExit::Hypercall), diff --git a/src/linux.rs b/src/linux.rs index 007e18e..ad35407 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1356,6 +1356,7 @@ fn run_vcpu( start_barrier: Arc, io_bus: devices::Bus, mmio_bus: devices::Bus, + split_irqchip: Option<(Arc>, Arc>)>, exit_evt: EventFd, requires_kvmclock_ctrl: bool, run_mode_arc: Arc, @@ -1417,6 +1418,13 @@ fn run_vcpu( }) => { mmio_bus.write(address, &data[..size]); } + Ok(VcpuExit::IoapicEoi{vector}) => { + if let Some((_, ioapic)) = &split_irqchip { + ioapic.lock().end_of_interrupt(vector); + } else { + panic!("userspace ioapic not found in split irqchip mode, should be impossible."); + } + }, Ok(VcpuExit::Hlt) => break, Ok(VcpuExit::Shutdown) => break, Ok(VcpuExit::FailEntry { @@ -1589,10 +1597,15 @@ pub fn run_config(cfg: Config) -> Result<()> { msg_socket::pair::().map_err(Error::CreateSocket)?; control_sockets.push(TaggedControlSocket::VmMemory(gpu_host_socket)); + let (ioapic_host_socket, ioapic_device_socket) = + msg_socket::pair::().map_err(Error::CreateSocket)?; + control_sockets.push(TaggedControlSocket::VmIrq(ioapic_host_socket)); + let sandbox = cfg.sandbox; let linux = Arch::build_vm( components, cfg.split_irqchip, + ioapic_device_socket, &cfg.serial_parameters, simple_jail(&cfg, "serial")?, |mem, vm, sys_allocator, exit_evt| { @@ -1739,6 +1752,7 @@ fn run_control( vcpu_thread_barrier.clone(), linux.io_bus.clone(), linux.mmio_bus.clone(), + linux.split_irqchip.clone(), linux.exit_evt.try_clone().map_err(Error::CloneEventFd)?, linux.vm.check_extension(Cap::KvmclockCtrl), run_mode_arc.clone(), diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml index bb1c445..49ef53f 100644 --- a/x86_64/Cargo.toml +++ b/x86_64/Cargo.toml @@ -20,3 +20,4 @@ resources = { path = "../resources" } sync = { path = "../sync" } sys_util = { path = "../sys_util" } acpi_tables = {path = "../acpi_tables" } +vm_control = { path = "../vm_control" } diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index b6f18e3..d9fe8a9 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -55,13 +55,17 @@ use std::sync::Arc; use crate::bootparam::boot_params; use arch::{RunnableLinuxVm, VmComponents, VmImage}; -use devices::{get_serial_tty_string, PciConfigIo, PciDevice, PciInterruptPin, SerialParameters}; +use devices::{ + get_serial_tty_string, Ioapic, PciConfigIo, PciDevice, PciInterruptPin, Pic, SerialParameters, + IOAPIC_BASE_ADDRESS, IOAPIC_MEM_LENGTH_BYTES, +}; use io_jail::Minijail; use kvm::*; use remain::sorted; use resources::SystemAllocator; use sync::Mutex; use sys_util::{Clock, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; +use vm_control::VmIrqRequestSocket; #[sorted] #[derive(Debug)] @@ -73,6 +77,7 @@ pub enum Error { CreateDevices(Box), CreateEventFd(sys_util::Error), CreateFdt(arch::fdt::Error), + CreateIoapicDevice(sys_util::Error), CreateIrqChip(sys_util::Error), CreateKvm(sys_util::Error), CreatePciRoot(arch::DeviceRegistrationError), @@ -120,6 +125,7 @@ impl Display for Error { CreateDevices(e) => write!(f, "error creating devices: {}", e), CreateEventFd(e) => write!(f, "unable to make an EventFd: {}", e), CreateFdt(e) => write!(f, "failed to create fdt: {}", e), + CreateIoapicDevice(e) => write!(f, "failed to create IOAPIC device: {}", e), CreateIrqChip(e) => write!(f, "failed to create irq chip: {}", e), CreateKvm(e) => write!(f, "failed to open /dev/kvm: {}", e), CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e), @@ -314,6 +320,7 @@ impl arch::LinuxArch for X8664arch { fn build_vm( mut components: VmComponents, split_irqchip: bool, + ioapic_device_socket: VmIrqRequestSocket, serial_parameters: &BTreeMap, serial_jail: Option, create_devices: F, @@ -362,6 +369,23 @@ impl arch::LinuxArch for X8664arch { let exit_evt = EventFd::new().map_err(Error::CreateEventFd)?; + let split_irqchip = if split_irqchip { + let pic = Arc::new(Mutex::new(Pic::new())); + let ioapic = Arc::new(Mutex::new( + Ioapic::new(&mut vm, ioapic_device_socket).map_err(Error::CreateIoapicDevice)?, + )); + mmio_bus + .insert( + ioapic.clone(), + IOAPIC_BASE_ADDRESS, + IOAPIC_MEM_LENGTH_BYTES, + false, + ) + .unwrap(); + Some((pic, ioapic)) + } else { + None + }; let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt) .map_err(|e| Error::CreateDevices(Box::new(e)))?; let (pci, pci_irqs, pid_debug_label_map) = @@ -376,7 +400,7 @@ impl arch::LinuxArch for X8664arch { let mut io_bus = Self::setup_io_bus( &mut vm, - split_irqchip, + split_irqchip.is_some(), exit_evt.try_clone().map_err(Error::CloneEventFd)?, Some(pci_bus.clone()), components.memory_size, @@ -394,6 +418,17 @@ impl arch::LinuxArch for X8664arch { None => None, }; + if let Some((pic, _)) = &split_irqchip { + io_bus.insert(pic.clone(), 0x20, 0x2, true).unwrap(); + io_bus.insert(pic.clone(), 0xa0, 0x2, true).unwrap(); + io_bus.insert(pic.clone(), 0x4d0, 0x2, true).unwrap(); + + let mut irq_num = resources.allocate_irq().unwrap(); + while irq_num < kvm::NUM_IOAPIC_PINS as u32 { + irq_num = resources.allocate_irq().unwrap(); + } + } + match components.vm_image { VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?, VmImage::Kernel(ref mut kernel_image) => { @@ -447,6 +482,7 @@ impl arch::LinuxArch for X8664arch { vcpus, vcpu_affinity, irq_chip, + split_irqchip, io_bus, mmio_bus, pid_debug_label_map, -- cgit 1.4.1 From db4c70d2151d054b6b4df58be432e500aeafecbe Mon Sep 17 00:00:00 2001 From: Zhuocheng Ding Date: Mon, 2 Dec 2019 15:50:24 +0800 Subject: devices: PIC: implement interrupt injection TODO: Route irqfd to PIC, and use signal to kick vCPU thread when interrupt is triggered. BUG=chromium:908689 TEST=Unit tests in file. Change-Id: I9a87502da57e725d3bb26d746a337d0ba44ef337 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1945797 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Zhuocheng Ding --- devices/src/pic.rs | 20 +++++++++++++------- kvm/src/lib.rs | 20 ++++++++++++++++++++ src/linux.rs | 25 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/devices/src/pic.rs b/devices/src/pic.rs index 9b8235f..c18abef 100644 --- a/devices/src/pic.rs +++ b/devices/src/pic.rs @@ -7,10 +7,10 @@ // modern OSs that use a legacy BIOS. // The PIC is connected to the Local APIC on CPU0. -// Terminology note: The 8259A spec refers to "master" and "slave" PITs; the "slave"s are daisy +// Terminology note: The 8259A spec refers to "master" and "slave" PICs; the "slave"s are daisy // chained to the "master"s. // For the purposes of both using more descriptive terms and avoiding terms with lots of charged -// emotional context, this file refers to them instead as "primary" and "secondary" PITs. +// emotional context, this file refers to them instead as "primary" and "secondary" PICs. use crate::BusDevice; use sys_util::{debug, warn}; @@ -56,9 +56,9 @@ struct PicState { } pub struct Pic { - // TODO(mutexlox): Implement an APIC and add necessary state to the Pic. - - // index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary. + // Indicates a pending INTR signal to LINT0 of vCPU, checked by vCPU thread. + interrupt_request: bool, + // Index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary. pics: [PicState; 2], } @@ -175,9 +175,9 @@ impl Pic { // The secondary PIC has IRQs 8-15, so we subtract 8 from the IRQ number to get the bit // that should be masked here. In this case, bits 8 - 8 = 0 and 13 - 8 = 5. secondary_pic.elcr_mask = !((1 << 0) | (1 << 5)); - // TODO(mutexlox): Add logic to initialize APIC interrupt-related fields. Pic { + interrupt_request: false, pics: [primary_pic, secondary_pic], } } @@ -205,8 +205,14 @@ impl Pic { self.get_irq(PicSelect::Primary).is_some() } + /// Determines whether the PIC has fired an interrupt to LAPIC. + pub fn interrupt_requested(&self) -> bool { + self.interrupt_request + } + /// Determines the external interrupt number that the PIC is prepared to inject, if any. pub fn get_external_interrupt(&mut self) -> Option { + self.interrupt_request = false; let irq_primary = if let Some(irq) = self.get_irq(PicSelect::Primary) { irq } else { @@ -403,7 +409,7 @@ impl Pic { } if self.get_irq(PicSelect::Primary).is_some() { - // TODO(mutexlox): Signal local interrupt on APIC bus. + self.interrupt_request = true; // Note: this does not check if the interrupt is succesfully injected into // the CPU, just whether or not one is fired. true diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index 66ebd20..2d951f2 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1313,6 +1313,26 @@ impl Vcpu { }); } + /// Request the VCPU to exit when it becomes possible to inject interrupts into the guest. + #[allow(clippy::cast_ptr_alignment)] + pub fn request_interrupt_window(&self) { + // Safe because we know we mapped enough memory to hold the kvm_run struct because the + // kernel told us how large it was. The pointer is page aligned so casting to a different + // type is well defined, hence the clippy allow attribute. + let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut kvm_run) }; + run.request_interrupt_window = 1; + } + + /// Checks if we can inject an interrupt into the VCPU. + #[allow(clippy::cast_ptr_alignment)] + pub fn ready_for_interrupt(&self) -> bool { + // Safe because we know we mapped enough memory to hold the kvm_run struct because the + // kernel told us how large it was. The pointer is page aligned so casting to a different + // type is well defined, hence the clippy allow attribute. + let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut kvm_run) }; + run.ready_for_interrupt_injection != 0 && run.if_flag != 0 + } + /// Gets the VCPU registers. #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] pub fn get_regs(&self) -> Result { diff --git a/src/linux.rs b/src/linux.rs index ad35407..a181e52 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1349,6 +1349,26 @@ fn runnable_vcpu(vcpu: Vcpu, use_kvm_signals: bool, cpu_id: u32) -> Option>, vcpu: &RunnableVcpu) { + let mut pic = pic.lock(); + if pic.interrupt_requested() && vcpu.ready_for_interrupt() { + if let Some(vector) = pic.get_external_interrupt() { + if let Err(e) = vcpu.interrupt(vector as u32) { + error!("PIC: failed to inject interrupt to vCPU0: {}", e); + } + } + // The second interrupt request should be handled immediately, so ask + // vCPU to exit as soon as possible. + if pic.interrupt_requested() { + vcpu.request_interrupt_window(); + } + } +} + +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +fn inject_interrupt(pic: &Arc>, vcpu: &RunnableVcpu) {} + fn run_vcpu( vcpu: Vcpu, cpu_id: u32, @@ -1480,6 +1500,11 @@ fn run_vcpu( run_mode_lock = run_mode_arc.cvar.wait(run_mode_lock); } } + + if cpu_id != 0 { continue; } + if let Some((pic, _)) = &split_irqchip { + inject_interrupt(pic, &vcpu); + } } } }) -- cgit 1.4.1 From 2f7dabbd6a0d8620e4b19b92cdae24c08e4c7ccc Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Thu, 2 Jan 2020 18:17:43 +0800 Subject: Virtio: Add blk VIRTIO_RING_F_EVENT_IDX feature Previous interrupt suppress patch only supply crude interrupt suppress, VIRTIO_RING_F_EVENT_IDX feature supply a more performant alternative: 1) where the driver specifies how far the device can progress before a notification is required 2) where the device specifies how far the driver can progress before a interrrupt is required. This patch add this feature into blk. For gpu and network, this could be added also, but gpu and network performance don't get better. BUG=None TEST=run benchmark for blk in guest Change-Id: I73fe3f8b72a9e88fd6073890bc6ab2bee891d51d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2008341 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Xiong Zhang --- devices/src/virtio/block.rs | 13 ++++++---- devices/src/virtio/net.rs | 2 +- devices/src/virtio/queue.rs | 62 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index 094586f..c9dda55 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -20,6 +20,7 @@ use sync::Mutex; use sys_util::Error as SysError; use sys_util::Result as SysResult; use sys_util::{error, info, iov_max, warn, EventFd, GuestMemory, PollContext, PollToken, TimerFd}; +use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_control::{DiskControlCommand, DiskControlResponseSocket, DiskControlResult}; use super::{ @@ -510,6 +511,7 @@ impl Block { } let mut avail_features: u64 = 1 << VIRTIO_BLK_F_FLUSH; + avail_features |= 1 << VIRTIO_RING_F_EVENT_IDX; if read_only { avail_features |= 1 << VIRTIO_BLK_F_RO; } else { @@ -871,8 +873,8 @@ mod tests { let b = Block::new(Box::new(f), false, true, 512, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE - // + VIRTIO_BLK_F_SEG_MAX - assert_eq!(0x100006244, b.features()); + // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX + assert_eq!(0x120006244, b.features()); } // read-write block device, non-sparse @@ -881,8 +883,8 @@ mod tests { let b = Block::new(Box::new(f), false, false, 512, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE - // + VIRTIO_BLK_F_SEG_MAX - assert_eq!(0x100004244, b.features()); + // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX + assert_eq!(0x120004244, b.features()); } // read-only block device @@ -891,7 +893,8 @@ mod tests { let b = Block::new(Box::new(f), true, true, 512, None).unwrap(); // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX - assert_eq!(0x100000264, b.features()); + // + VIRTIO_RING_F_EVENT_IDX + assert_eq!(0x120000264, b.features()); } } diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs index 38ba5a7..44a39ab 100644 --- a/devices/src/virtio/net.rs +++ b/devices/src/virtio/net.rs @@ -192,7 +192,7 @@ where }; if bytes_written > 0 { - self.rx_queue.pop_peeked(); + self.rx_queue.pop_peeked(&self.mem); self.rx_queue.add_used(&self.mem, index, bytes_written); needs_interrupt = true; } diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index e33e3ba..18ddd6d 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -7,6 +7,7 @@ use std::num::Wrapping; use std::sync::atomic::{fence, Ordering}; use sys_util::{error, GuestAddress, GuestMemory}; +use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use super::{Interrupt, VIRTIO_MSI_NO_VECTOR}; @@ -227,6 +228,7 @@ pub struct Queue { // Device feature bits accepted by the driver features: u64, + last_used: Wrapping, } impl Queue { @@ -243,6 +245,7 @@ impl Queue { next_avail: Wrapping(0), next_used: Wrapping(0), features: 0, + last_used: Wrapping(0), } } @@ -263,6 +266,7 @@ impl Queue { self.next_avail = Wrapping(0); self.next_used = Wrapping(0); self.features = 0; + self.last_used = Wrapping(0); } pub fn is_valid(&self, mem: &GuestMemory) -> bool { @@ -344,15 +348,22 @@ impl Queue { /// Remove the first available descriptor chain from the queue. /// This function should only be called immediately following `peek`. - pub fn pop_peeked(&mut self) { + pub fn pop_peeked(&mut self, mem: &GuestMemory) { self.next_avail += Wrapping(1); + if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 { + let avail_event_off = self + .used_ring + .unchecked_add((4 + 8 * self.actual_size()).into()); + mem.write_obj_at_addr(self.next_avail.0 as u16, avail_event_off) + .unwrap(); + } } /// If a new DescriptorHead is available, returns one and removes it from the queue. pub fn pop<'a>(&mut self, mem: &'a GuestMemory) -> Option> { let descriptor_chain = self.peek(mem); if descriptor_chain.is_some() { - self.pop_peeked(); + self.pop_peeked(mem); } descriptor_chain } @@ -393,28 +404,55 @@ impl Queue { /// Enable / Disable guest notify device that requests are available on /// the descriptor chain. pub fn set_notify(&mut self, mem: &GuestMemory, enable: bool) { - let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap(); - if enable { - used_flags &= !VIRTQ_USED_F_NO_NOTIFY; + if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 { + let avail_index_addr = mem.checked_offset(self.avail_ring, 2).unwrap(); + let avail_index: u16 = mem.read_obj_from_addr(avail_index_addr).unwrap(); + let avail_event_off = self + .used_ring + .unchecked_add((4 + 8 * self.actual_size()).into()); + mem.write_obj_at_addr(avail_index, avail_event_off).unwrap(); } else { - used_flags |= VIRTQ_USED_F_NO_NOTIFY; + let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap(); + if enable { + used_flags &= !VIRTQ_USED_F_NO_NOTIFY; + } else { + used_flags |= VIRTQ_USED_F_NO_NOTIFY; + } + mem.write_obj_at_addr(used_flags, self.used_ring).unwrap(); } - mem.write_obj_at_addr(used_flags, self.used_ring).unwrap(); } // Check Whether guest enable interrupt injection or not. fn available_interrupt_enabled(&self, mem: &GuestMemory) -> bool { - let avail_flags: u16 = mem.read_obj_from_addr(self.avail_ring).unwrap(); - if avail_flags & VIRTQ_AVAIL_F_NO_INTERRUPT == VIRTQ_AVAIL_F_NO_INTERRUPT { - false + if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 { + let used_event_off = self + .avail_ring + .unchecked_add((4 + 2 * self.actual_size()).into()); + let used_event: u16 = mem.read_obj_from_addr(used_event_off).unwrap(); + // if used_event >= self.last_used, driver handle interrupt quickly enough, new + // interrupt could be injected. + // if used_event < self.last_used, driver hasn't finished the last interrupt, + // so no need to inject new interrupt. + if self.next_used - Wrapping(used_event) - Wrapping(1) < self.next_used - self.last_used + { + true + } else { + false + } } else { - true + let avail_flags: u16 = mem.read_obj_from_addr(self.avail_ring).unwrap(); + if avail_flags & VIRTQ_AVAIL_F_NO_INTERRUPT == VIRTQ_AVAIL_F_NO_INTERRUPT { + false + } else { + true + } } } /// inject interrupt into guest on this queue - pub fn trigger_interrupt(&self, mem: &GuestMemory, interrupt: &Interrupt) { + pub fn trigger_interrupt(&mut self, mem: &GuestMemory, interrupt: &Interrupt) { if self.available_interrupt_enabled(mem) { + self.last_used = self.next_used; interrupt.signal_used_queue(self.vector); } } -- cgit 1.4.1 From b9f4c9bca30e65eacfb055951fa994ad5127a8f0 Mon Sep 17 00:00:00 2001 From: Zhuocheng Ding Date: Mon, 2 Dec 2019 15:50:28 +0800 Subject: crosvm: Add plumbing for split-irqchip interrupts Devices use irqfd to inject interrupts, we listen to them in the main thread and activate userspace pic/ioapic accordingly. BUG=chromium:908689 TEST=lanuch linux guest with `--split-irqchip` flag Change-Id: If30d17ce7ec9e26dba782c89cc1b9b2ff897a70d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1945798 Tested-by: kokoro Reviewed-by: Stephen Barber Reviewed-by: Daniel Verkamp Commit-Queue: Zhuocheng Ding --- aarch64/src/lib.rs | 12 ++++-- arch/src/lib.rs | 24 ++++++++++-- devices/src/ioapic.rs | 13 +++++++ devices/src/pic.rs | 37 ++++++++----------- devices/src/split_irqchip_common.rs | 34 +++++++++++++++++ src/linux.rs | 74 +++++++++++++++++++++++++++++++++++++ x86_64/src/lib.rs | 73 +++++++++++++++++++++++++----------- 7 files changed, 219 insertions(+), 48 deletions(-) diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index d4c4a50..f8a36b9 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -245,9 +245,14 @@ impl arch::LinuxArch for AArch64 { let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt) .map_err(|e| Error::CreateDevices(Box::new(e)))?; - let (pci, pci_irqs, pid_debug_label_map) = - arch::generate_pci_root(pci_devices, &mut mmio_bus, &mut resources, &mut vm) - .map_err(Error::CreatePciRoot)?; + let (pci, pci_irqs, pid_debug_label_map) = arch::generate_pci_root( + pci_devices, + &mut None, + &mut mmio_bus, + &mut resources, + &mut vm, + ) + .map_err(Error::CreatePciRoot)?; let pci_bus = Arc::new(Mutex::new(PciConfigMmio::new(pci))); // ARM doesn't really use the io bus like x86, so just create an empty bus. @@ -317,6 +322,7 @@ impl arch::LinuxArch for AArch64 { vcpu_affinity, irq_chip, split_irqchip: None, + gsi_relay: None, io_bus, mmio_bus, pid_debug_label_map, diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 5112798..ab08c21 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -15,6 +15,7 @@ use std::os::unix::io::AsRawFd; use std::path::PathBuf; use std::sync::Arc; +use devices::split_irqchip_common::GsiRelay; use devices::virtio::VirtioDevice; use devices::{ Bus, BusDevice, BusError, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, ProxyDevice, @@ -62,6 +63,7 @@ pub struct RunnableLinuxVm { pub vcpu_affinity: Vec, pub irq_chip: Option, pub split_irqchip: Option<(Arc>, Arc>)>, + pub gsi_relay: Option>, pub io_bus: Bus, pub mmio_bus: Bus, pub pid_debug_label_map: BTreeMap, @@ -118,6 +120,8 @@ pub enum DeviceRegistrationError { CreatePipe(sys_util::Error), // Unable to create serial device from serial parameters CreateSerialDevice(devices::SerialError), + /// Could not clone an event fd. + EventFdClone(sys_util::Error), /// Could not create an event fd. EventFdCreate(sys_util::Error), /// Could not add a device to the mmio bus. @@ -149,6 +153,7 @@ impl Display for DeviceRegistrationError { CreatePipe(e) => write!(f, "failed to create pipe: {}", e), CreateSerialDevice(e) => write!(f, "failed to create serial device: {}", e), Cmdline(e) => write!(f, "unable to add device to kernel command line: {}", e), + EventFdClone(e) => write!(f, "failed to clone eventfd: {}", e), EventFdCreate(e) => write!(f, "failed to create eventfd: {}", e), MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e), RegisterIoevent(e) => write!(f, "failed to register ioevent to VM: {}", e), @@ -166,6 +171,7 @@ impl Display for DeviceRegistrationError { /// Creates a root PCI device for use by this Vm. pub fn generate_pci_root( devices: Vec<(Box, Option)>, + gsi_relay: &mut Option, mmio_bus: &mut Bus, resources: &mut SystemAllocator, vm: &mut Vm, @@ -191,10 +197,22 @@ pub fn generate_pci_root( 1 => PciInterruptPin::IntB, 2 => PciInterruptPin::IntC, 3 => PciInterruptPin::IntD, - _ => panic!(""), // Obviously not possible, but the compiler is not smart enough. + _ => unreachable!(), // Obviously not possible, but the compiler is not smart enough. }; - vm.register_irqfd_resample(&irqfd, &irq_resample_fd, irq_num) - .map_err(DeviceRegistrationError::RegisterIrqfd)?; + if let Some(relay) = gsi_relay { + relay.register_irqfd_resample( + irqfd + .try_clone() + .map_err(DeviceRegistrationError::EventFdClone)?, + irq_resample_fd + .try_clone() + .map_err(DeviceRegistrationError::EventFdClone)?, + irq_num as usize, + ); + } else { + vm.register_irqfd_resample(&irqfd, &irq_resample_fd, irq_num) + .map_err(DeviceRegistrationError::RegisterIrqfd)?; + } keep_fds.push(irqfd.as_raw_fd()); keep_fds.push(irq_resample_fd.as_raw_fd()); device.assign_irq(irqfd, irq_resample_fd, irq_num, pci_irq_pin); diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs index e1fbfb8..09ccb89 100644 --- a/devices/src/ioapic.rs +++ b/devices/src/ioapic.rs @@ -10,6 +10,7 @@ use crate::BusDevice; use bit_field::*; use kvm::Vm; use msg_socket::{MsgReceiver, MsgSender}; +use std::sync::Arc; use sys_util::{error, warn, EventFd, Result}; use vm_control::{VmIrqRequest, VmIrqRequestSocket, VmIrqResponse}; @@ -89,6 +90,7 @@ pub struct Ioapic { redirect_table: [RedirectionTableEntry; kvm::NUM_IOAPIC_PINS], // IOREGSEL is technically 32 bits, but only bottom 8 are writable: all others are fixed to 0. ioregsel: u8, + relay: Arc, irqfd: Vec, socket: VmIrqRequestSocket, } @@ -166,11 +168,16 @@ impl Ioapic { current_interrupt_level_bitmap: 0, redirect_table: entries, ioregsel: 0, + relay: Default::default(), irqfd, socket, }) } + pub fn register_relay(&mut self, relay: Arc) { + self.relay = relay; + } + // The ioapic must be informed about EOIs in order to avoid sending multiple interrupts of the // same type at the same time. pub fn end_of_interrupt(&mut self, vector: u8) { @@ -183,6 +190,12 @@ impl Ioapic { if self.redirect_table[i].get_vector() == vector && self.redirect_table[i].get_trigger_mode() == TriggerMode::Level { + if self.relay.irqfd_resample[i].is_some() { + self.service_irq(i, false); + } + if let Some(resample_evt) = &self.relay.irqfd_resample[i] { + resample_evt.write(1).unwrap(); + } self.redirect_table[i].set_remote_irr(false); } // There is an inherent race condition in hardware if the OS is finished processing an diff --git a/devices/src/pic.rs b/devices/src/pic.rs index c18abef..f562be6 100644 --- a/devices/src/pic.rs +++ b/devices/src/pic.rs @@ -12,7 +12,9 @@ // For the purposes of both using more descriptive terms and avoiding terms with lots of charged // emotional context, this file refers to them instead as "primary" and "secondary" PICs. +use crate::split_irqchip_common::GsiRelay; use crate::BusDevice; +use std::sync::Arc; use sys_util::{debug, warn}; #[repr(usize)] @@ -30,7 +32,7 @@ enum PicInitState { Icw4 = 3, } -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Default)] struct PicState { last_irr: u8, // Edge detection. irr: u8, // Interrupt Request Register. @@ -53,6 +55,8 @@ struct PicState { elcr: u8, elcr_mask: u8, init_state: Option, + is_primary: bool, + relay: Arc, } pub struct Pic { @@ -176,12 +180,18 @@ impl Pic { // that should be masked here. In this case, bits 8 - 8 = 0 and 13 - 8 = 5. secondary_pic.elcr_mask = !((1 << 0) | (1 << 5)); + primary_pic.is_primary = true; Pic { interrupt_request: false, pics: [primary_pic, secondary_pic], } } + pub fn register_relay(&mut self, relay: Arc) { + self.pics[0].relay = relay.clone(); + self.pics[1].relay = relay; + } + pub fn service_irq(&mut self, irq: u8, level: bool) -> bool { assert!(irq <= 15, "Unexpectedly high value irq: {} vs 15", irq); @@ -391,6 +401,11 @@ impl Pic { fn clear_isr(pic: &mut PicState, irq: u8) { assert!(irq <= 7, "Unexpectedly high value for irq: {} vs 7", irq); pic.isr &= !(1 << irq); + Pic::set_irq_internal(pic, irq, false); + let irq = if pic.is_primary { irq } else { irq + 8 }; + if let Some(resample_evt) = &pic.relay.irqfd_resample[irq as usize] { + resample_evt.write(1).unwrap(); + } } fn update_irq(&mut self) -> bool { @@ -1088,26 +1103,6 @@ mod tests { assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6); } - /// Verify that no-op doesn't change state. - #[test] - fn no_op_ocw2() { - let mut data = set_up(); - icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); - - // TODO(mutexlox): Verify APIC interaction when it is implemented. - data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); - assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); - data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); - - let orig = data.pic.pics[PicSelect::Primary as usize].clone(); - - // Run a no-op. - data.pic.write(PIC_PRIMARY_COMMAND, &[0x40]); - - // Nothing should have changed. - assert_eq!(orig, data.pic.pics[PicSelect::Primary as usize]); - } - /// Tests cascade IRQ that happens on secondary PIC. #[test] fn cascade_irq() { diff --git a/devices/src/split_irqchip_common.rs b/devices/src/split_irqchip_common.rs index b54c35a..1e513f2 100644 --- a/devices/src/split_irqchip_common.rs +++ b/devices/src/split_irqchip_common.rs @@ -5,6 +5,7 @@ // Common constants and types used for Split IRQ chip devices (e.g. PIC, PIT, IOAPIC). use bit_field::*; +use sys_util::EventFd; #[bitfield] #[derive(Clone, Copy, Debug, PartialEq)] @@ -58,3 +59,36 @@ pub struct MsiDataMessage { trigger: TriggerMode, reserved2: BitField16, } + +/// Acts as a relay of interrupt signals between devices and IRQ chips. +#[derive(Default)] +pub struct GsiRelay { + pub irqfd: [Option; kvm::NUM_IOAPIC_PINS], + pub irqfd_resample: [Option; kvm::NUM_IOAPIC_PINS], +} + +impl GsiRelay { + pub fn new() -> GsiRelay { + GsiRelay { + irqfd: Default::default(), + irqfd_resample: Default::default(), + } + } + + pub fn register_irqfd(&mut self, evt: EventFd, gsi: usize) { + if gsi >= kvm::NUM_IOAPIC_PINS { + // Invalid gsi; ignore. + return; + } + self.irqfd[gsi] = Some(evt); + } + + pub fn register_irqfd_resample(&mut self, evt: EventFd, resample_evt: EventFd, gsi: usize) { + if gsi >= kvm::NUM_IOAPIC_PINS { + // Invalid gsi; ignore. + return; + } + self.irqfd[gsi] = Some(evt); + self.irqfd_resample[gsi] = Some(resample_evt); + } +} diff --git a/src/linux.rs b/src/linux.rs index a181e52..97f691f 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1699,6 +1699,7 @@ fn run_control( Suspend, ChildSignal, CheckAvailableMemory, + IrqFd { gsi: usize }, LowMemory, LowmemTimer, VmControlServer, @@ -1749,6 +1750,16 @@ fn run_control( .add(&freemem_timer, Token::CheckAvailableMemory) .map_err(Error::PollContextAdd)?; + if let Some(gsi_relay) = &linux.gsi_relay { + for (gsi, evt) in gsi_relay.irqfd.into_iter().enumerate() { + if let Some(evt) = evt { + poll_ctx + .add(evt, Token::IrqFd { gsi }) + .map_err(Error::PollContextAdd)?; + } + } + } + // Used to add jitter to timer values so that we don't have a thundering herd problem when // multiple VMs are running. let mut simple_rng = SimpleRng::new( @@ -1787,6 +1798,7 @@ fn run_control( } vcpu_thread_barrier.wait(); + let mut ioapic_delayed = Vec::::default(); 'poll: loop { let events = { match poll_ctx.wait() { @@ -1798,6 +1810,26 @@ fn run_control( } }; + ioapic_delayed.retain(|&gsi| { + if let Some((_, ioapic)) = &linux.split_irqchip { + if let Ok(mut ioapic) = ioapic.try_lock() { + // The unwrap will never fail because gsi_relay is Some iff split_irqchip is + // Some. + if linux.gsi_relay.as_ref().unwrap().irqfd_resample[gsi].is_some() { + ioapic.service_irq(gsi, true); + } else { + ioapic.service_irq(gsi, true); + ioapic.service_irq(gsi, false); + } + false + } else { + true + } + } else { + true + } + }); + let mut vm_control_indices_to_remove = Vec::new(); for event in events.iter_readable() { match event.token() { @@ -1861,6 +1893,47 @@ fn run_control( } } } + Token::IrqFd { gsi } => { + if let Some((pic, ioapic)) = &linux.split_irqchip { + // This will never fail because gsi_relay is Some iff split_irqchip is + // Some. + let gsi_relay = linux.gsi_relay.as_ref().unwrap(); + if let Some(eventfd) = &gsi_relay.irqfd[gsi] { + eventfd.read().unwrap(); + } else { + warn!( + "irqfd {} not found in GSI relay, should be impossible.", + gsi + ); + } + + let mut pic = pic.lock(); + if gsi_relay.irqfd_resample[gsi].is_some() { + pic.service_irq(gsi as u8, true); + } else { + pic.service_irq(gsi as u8, true); + pic.service_irq(gsi as u8, false); + } + if let Err(e) = vcpu_handles[0].kill(SIGRTMIN() + 0) { + warn!("PIC: failed to kick vCPU0: {}", e); + } + + // When IOAPIC is configuring its redirection table, we should first + // process its AddMsiRoute request, otherwise we would deadlock. + if let Ok(mut ioapic) = ioapic.try_lock() { + if gsi_relay.irqfd_resample[gsi].is_some() { + ioapic.service_irq(gsi, true); + } else { + ioapic.service_irq(gsi, true); + ioapic.service_irq(gsi, false); + } + } else { + ioapic_delayed.push(gsi); + } + } else { + panic!("split irqchip not found, should be impossible."); + } + } Token::LowMemory => { if let Some(low_mem) = &low_mem { let old_balloon_memory = current_balloon_memory; @@ -2020,6 +2093,7 @@ fn run_control( Token::Suspend => {} Token::ChildSignal => {} Token::CheckAvailableMemory => {} + Token::IrqFd { gsi: _ } => {} Token::LowMemory => {} Token::LowmemTimer => {} Token::VmControlServer => {} diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index d9fe8a9..a912edd 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -55,6 +55,7 @@ use std::sync::Arc; use crate::bootparam::boot_params; use arch::{RunnableLinuxVm, VmComponents, VmImage}; +use devices::split_irqchip_common::GsiRelay; use devices::{ get_serial_tty_string, Ioapic, PciConfigIo, PciDevice, PciInterruptPin, Pic, SerialParameters, IOAPIC_BASE_ADDRESS, IOAPIC_MEM_LENGTH_BYTES, @@ -88,6 +89,7 @@ pub enum Error { CreateVcpu(sys_util::Error), CreateVm(sys_util::Error), E820Configuration, + EnableSplitIrqchip(sys_util::Error), KernelOffsetPastEnd, LoadBios(io::Error), LoadBzImage(bzimage::Error), @@ -136,6 +138,7 @@ impl Display for Error { CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e), CreateVm(e) => write!(f, "failed to create VM: {}", e), E820Configuration => write!(f, "invalid e820 setup params"), + EnableSplitIrqchip(e) => write!(f, "failed to enable split irqchip: {}", e), KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"), LoadBios(e) => write!(f, "error loading bios: {}", e), LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e), @@ -369,7 +372,8 @@ impl arch::LinuxArch for X8664arch { let exit_evt = EventFd::new().map_err(Error::CreateEventFd)?; - let split_irqchip = if split_irqchip { + let (split_irqchip, mut gsi_relay) = if split_irqchip { + let gsi_relay = GsiRelay::new(); let pic = Arc::new(Mutex::new(Pic::new())); let ioapic = Arc::new(Mutex::new( Ioapic::new(&mut vm, ioapic_device_socket).map_err(Error::CreateIoapicDevice)?, @@ -382,15 +386,20 @@ impl arch::LinuxArch for X8664arch { false, ) .unwrap(); - Some((pic, ioapic)) + (Some((pic, ioapic)), Some(gsi_relay)) } else { - None + (None, None) }; let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt) .map_err(|e| Error::CreateDevices(Box::new(e)))?; - let (pci, pci_irqs, pid_debug_label_map) = - arch::generate_pci_root(pci_devices, &mut mmio_bus, &mut resources, &mut vm) - .map_err(Error::CreatePciRoot)?; + let (pci, pci_irqs, pid_debug_label_map) = arch::generate_pci_root( + pci_devices, + &mut gsi_relay, + &mut mmio_bus, + &mut resources, + &mut vm, + ) + .map_err(Error::CreatePciRoot)?; let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci))); // Event used to notify crosvm that guest OS is trying to suspend. @@ -400,15 +409,20 @@ impl arch::LinuxArch for X8664arch { let mut io_bus = Self::setup_io_bus( &mut vm, - split_irqchip.is_some(), + &mut gsi_relay, exit_evt.try_clone().map_err(Error::CloneEventFd)?, Some(pci_bus.clone()), components.memory_size, suspend_evt.try_clone().map_err(Error::CloneEventFd)?, )?; - let stdio_serial_num = - Self::setup_serial_devices(&mut vm, &mut io_bus, serial_parameters, serial_jail)?; + let stdio_serial_num = Self::setup_serial_devices( + &mut vm, + &mut io_bus, + &mut gsi_relay, + serial_parameters, + serial_jail, + )?; let ramoops_region = match components.pstore { Some(pstore) => Some( @@ -418,7 +432,7 @@ impl arch::LinuxArch for X8664arch { None => None, }; - if let Some((pic, _)) = &split_irqchip { + let gsi_relay = if let Some((pic, ioapic)) = &split_irqchip { io_bus.insert(pic.clone(), 0x20, 0x2, true).unwrap(); io_bus.insert(pic.clone(), 0xa0, 0x2, true).unwrap(); io_bus.insert(pic.clone(), 0x4d0, 0x2, true).unwrap(); @@ -427,7 +441,15 @@ impl arch::LinuxArch for X8664arch { while irq_num < kvm::NUM_IOAPIC_PINS as u32 { irq_num = resources.allocate_irq().unwrap(); } - } + + // This will never fail because gsi_relay is Some iff split_irqchip is Some. + let gsi_relay = Arc::new(gsi_relay.unwrap()); + pic.lock().register_relay(gsi_relay.clone()); + ioapic.lock().register_relay(gsi_relay.clone()); + Some(gsi_relay) + } else { + None + }; match components.vm_image { VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?, @@ -483,6 +505,7 @@ impl arch::LinuxArch for X8664arch { vcpu_affinity, irq_chip, split_irqchip, + gsi_relay, io_bus, mmio_bus, pid_debug_label_map, @@ -638,6 +661,8 @@ impl X8664arch { vm.create_pit().map_err(Error::CreatePit)?; vm.create_irq_chip().map_err(Error::CreateIrqChip)?; } else { + vm.enable_split_irqchip() + .map_err(Error::EnableSplitIrqchip)?; for i in 0..kvm::NUM_IOAPIC_PINS { // Add dummy MSI routes to replace the default IRQChip routes. let route = IrqRoute { @@ -719,13 +744,13 @@ impl X8664arch { /// # Arguments /// /// * - `vm` the vm object - /// * - `split_irqchip`: whether to use a split IRQ chip (i.e. userspace PIT/PIC/IOAPIC) + /// * - `gsi_relay`: only valid for split IRQ chip (i.e. userspace PIT/PIC/IOAPIC) /// * - `exit_evt` - the event fd object which should receive exit events /// * - `mem_size` - the size in bytes of physical ram for the guest /// * - `suspend_evt` - the event fd object which used to suspend the vm fn setup_io_bus( - vm: &mut Vm, - split_irqchip: bool, + _vm: &mut Vm, + gsi_relay: &mut Option, exit_evt: EventFd, pci: Option>>, mem_size: u64, @@ -758,7 +783,7 @@ impl X8664arch { exit_evt.try_clone().map_err(Error::CloneEventFd)?, ))); - if split_irqchip { + if let Some(gsi_relay) = gsi_relay { let pit_evt = EventFd::new().map_err(Error::CreateEventFd)?; let pit = Arc::new(Mutex::new( devices::Pit::new( @@ -770,8 +795,7 @@ impl X8664arch { io_bus.insert(pit.clone(), 0x040, 0x8, true).unwrap(); io_bus.insert(pit.clone(), 0x061, 0x1, true).unwrap(); io_bus.insert(i8042, 0x062, 0x3, true).unwrap(); - vm.register_irqfd(&pit_evt, 0) - .map_err(Error::RegisterIrqfd)?; + gsi_relay.register_irqfd(pit_evt, 0); } else { io_bus .insert(nul_device.clone(), 0x040, 0x8, false) @@ -816,10 +840,12 @@ impl X8664arch { /// /// * - `vm` the vm object /// * - `io_bus` the I/O bus to add the devices to + /// * - `gsi_relay`: only valid for split IRQ chip (i.e. userspace PIT/PIC/IOAPIC) /// * - `serial_parmaters` - definitions for how the serial devices should be configured fn setup_serial_devices( vm: &mut Vm, io_bus: &mut devices::Bus, + gsi_relay: &mut Option, serial_parameters: &BTreeMap, serial_jail: Option, ) -> Result> { @@ -835,10 +861,15 @@ impl X8664arch { ) .map_err(Error::CreateSerialDevices)?; - vm.register_irqfd(&com_evt_1_3, X86_64_SERIAL_1_3_IRQ) - .map_err(Error::RegisterIrqfd)?; - vm.register_irqfd(&com_evt_2_4, X86_64_SERIAL_2_4_IRQ) - .map_err(Error::RegisterIrqfd)?; + if let Some(gsi_relay) = gsi_relay { + gsi_relay.register_irqfd(com_evt_1_3, X86_64_SERIAL_1_3_IRQ as usize); + gsi_relay.register_irqfd(com_evt_2_4, X86_64_SERIAL_2_4_IRQ as usize); + } else { + vm.register_irqfd(&com_evt_1_3, X86_64_SERIAL_1_3_IRQ) + .map_err(Error::RegisterIrqfd)?; + vm.register_irqfd(&com_evt_2_4, X86_64_SERIAL_2_4_IRQ) + .map_err(Error::RegisterIrqfd)?; + } Ok(stdio_serial_num) } -- cgit 1.4.1 From d2a862b41f07d387926f0b984c56dc838003102c Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Tue, 25 Feb 2020 18:01:05 -0800 Subject: crosvm: add handling for hyperv exits When features for Hyper-V are enabled there's a another type of exit that can be triggered. This change attempts to add support for those types of exits. BUG=b:150151095 TEST=ran build_test Change-Id: I3131a2c8d9c610576ac177dbfe82f78e8d5dbfb1 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2073254 Reviewed-by: Matt Delco Tested-by: Matt Delco Tested-by: kokoro Commit-Queue: Matt Delco Auto-Submit: Matt Delco --- crosvm_plugin/crosvm.h | 37 +++++- crosvm_plugin/src/lib.rs | 49 ++++++++ kvm/src/lib.rs | 54 ++++++++- kvm_sys/src/aarch64/bindings.rs | 172 +++++++++++++++++++++++++++ kvm_sys/src/lib.rs | 3 - kvm_sys/src/x86/bindings.rs | 173 ++++++++++++++++++++++++++- protos/src/plugin.proto | 17 ++- src/plugin/mod.rs | 15 +++ src/plugin/vcpu.rs | 48 ++++++++ tests/plugin_enable_cap.c | 258 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 817 insertions(+), 9 deletions(-) diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h index db824b9..d1bea2a 100644 --- a/crosvm_plugin/crosvm.h +++ b/crosvm_plugin/crosvm.h @@ -47,7 +47,7 @@ extern "C" { * do not indicate anything about what version of crosvm is running. */ #define CROSVM_API_MAJOR 0 -#define CROSVM_API_MINOR 21 +#define CROSVM_API_MINOR 22 #define CROSVM_API_PATCH 0 enum crosvm_address_space { @@ -495,6 +495,16 @@ enum crosvm_vcpu_event_kind { * a `crosvm_pause_vcpus` call. */ CROSVM_VCPU_EVENT_KIND_PAUSED, + + /* + * Hyper-V hypercall. + */ + CROSVM_VCPU_EVENT_KIND_HYPERV_HCALL, + + /* + * Hyper-V synic change. + */ + CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC, }; struct crosvm_vcpu_event { @@ -553,6 +563,31 @@ struct crosvm_vcpu_event { /* CROSVM_VCPU_EVENT_KIND_PAUSED */ void *user; + /* CROSVM_VCPU_EVENT_KIND_HYPERV_HCALL */ + struct { + /* + * The |input| and |params| members are populated for the plugin to use. + * The |result| member is populated by the API to point to a uint64_t + * that the plugin should update before resuming. + */ + uint64_t input; + uint64_t *result; + uint64_t params[2]; + } hyperv_call; + + /* CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC */ + struct { + /* + * The |msr|, |control|, |evt_page|, and |msg_page| fields are populated + * for the plugin to use. + */ + uint32_t msr; + uint32_t _reserved; + uint64_t control; + uint64_t evt_page; + uint64_t msg_page; + } hyperv_synic; + uint8_t _reserved[64]; }; }; diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index 8695d41..05c19bf 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -59,6 +59,8 @@ const CROSVM_IRQ_ROUTE_MSI: u32 = 1; const CROSVM_VCPU_EVENT_KIND_INIT: u32 = 0; const CROSVM_VCPU_EVENT_KIND_IO_ACCESS: u32 = 1; const CROSVM_VCPU_EVENT_KIND_PAUSED: u32 = 2; +const CROSVM_VCPU_EVENT_KIND_HYPERV_HCALL: u32 = 3; +const CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC: u32 = 4; #[repr(C)] #[derive(Copy, Clone)] @@ -925,10 +927,30 @@ struct anon_io_access { __reserved1: [u8; 2], } +#[derive(Copy, Clone)] +#[repr(C)] +struct anon_hyperv_call { + input: u64, + result: *mut u8, + params: [u64; 2], +} + +#[derive(Copy, Clone)] +#[repr(C)] +struct anon_hyperv_synic { + msr: u32, + reserved: u32, + control: u64, + evt_page: u64, + msg_page: u64, +} + #[repr(C)] union anon_vcpu_event { io_access: anon_io_access, user: *mut c_void, + hyperv_call: anon_hyperv_call, + hyperv_synic: anon_hyperv_synic, #[allow(dead_code)] __reserved: [u8; 64], } @@ -1118,6 +1140,33 @@ impl crosvm_vcpu { self.sregs.get = false; self.debugregs.get = false; Ok(()) + } else if wait.has_hyperv_call() { + let hv: &VcpuResponse_Wait_HypervCall = wait.get_hyperv_call(); + event.kind = CROSVM_VCPU_EVENT_KIND_HYPERV_HCALL; + self.resume_data = vec![0; 8]; + event.event.hyperv_call = anon_hyperv_call { + input: hv.input, + result: self.resume_data.as_mut_ptr(), + params: [hv.params0, hv.params1], + }; + self.regs.get = false; + self.sregs.get = false; + self.debugregs.get = false; + Ok(()) + } else if wait.has_hyperv_synic() { + let hv: &VcpuResponse_Wait_HypervSynic = wait.get_hyperv_synic(); + event.kind = CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC; + event.event.hyperv_synic = anon_hyperv_synic { + msr: hv.msr, + reserved: 0, + control: hv.control, + evt_page: hv.evt_page, + msg_page: hv.msg_page, + }; + self.regs.get = false; + self.sregs.get = false; + self.debugregs.get = false; + Ok(()) } else { Err(EPROTO) } diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index 2d951f2..b8cd12b 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1124,6 +1124,16 @@ pub enum VcpuExit { IoapicEoi { vector: u8, }, + HypervSynic { + msr: u32, + control: u64, + evt_page: u64, + msg_page: u64, + }, + HypervHcall { + input: u64, + params: [u64; 2], + }, Unknown, Exception, Hypercall, @@ -1243,10 +1253,10 @@ impl Vcpu { &self.guest_mem } - /// Sets the data received by an mmio or ioport read/in instruction. + /// Sets the data received by a mmio read, ioport in, or hypercall instruction. /// - /// This function should be called after `Vcpu::run` returns an `VcpuExit::IoIn` or - /// `Vcpu::MmioRead`. + /// This function should be called after `Vcpu::run` returns an `VcpuExit::IoIn`, + /// `VcpuExit::MmioRead`, or 'VcpuExit::HypervHcall`. #[allow(clippy::cast_ptr_alignment)] pub fn set_data(&self, data: &[u8]) -> Result<()> { // Safe because we know we mapped enough memory to hold the kvm_run struct because the @@ -1288,6 +1298,20 @@ impl Vcpu { mmio.data[..len].copy_from_slice(data); Ok(()) } + KVM_EXIT_HYPERV => { + // Safe because the exit_reason (which comes from the kernel) told us which + // union field to use. + let hyperv = unsafe { &mut run.__bindgen_anon_1.hyperv }; + if hyperv.type_ != KVM_EXIT_HYPERV_HCALL { + return Err(Error::new(EINVAL)); + } + let hcall = unsafe { &mut hyperv.u.hcall }; + if data.len() != std::mem::size_of::() { + return Err(Error::new(EINVAL)); + } + hcall.result.to_ne_bytes().copy_from_slice(data); + Ok(()) + } _ => Err(Error::new(EINVAL)), } } @@ -1840,6 +1864,30 @@ impl RunnableVcpu { let vector = unsafe { run.__bindgen_anon_1.eoi.vector }; Ok(VcpuExit::IoapicEoi { vector }) } + KVM_EXIT_HYPERV => { + // Safe because the exit_reason (which comes from the kernel) told us which + // union field to use. + let hyperv = unsafe { &run.__bindgen_anon_1.hyperv }; + match hyperv.type_ as u32 { + KVM_EXIT_HYPERV_SYNIC => { + let synic = unsafe { &hyperv.u.synic }; + Ok(VcpuExit::HypervSynic { + msr: synic.msr, + control: synic.control, + evt_page: synic.evt_page, + msg_page: synic.msg_page, + }) + } + KVM_EXIT_HYPERV_HCALL => { + let hcall = unsafe { &hyperv.u.hcall }; + Ok(VcpuExit::HypervHcall { + input: hcall.input, + params: hcall.params, + }) + } + _ => Err(Error::new(EINVAL)), + } + } KVM_EXIT_UNKNOWN => Ok(VcpuExit::Unknown), KVM_EXIT_EXCEPTION => Ok(VcpuExit::Exception), KVM_EXIT_HYPERCALL => Ok(VcpuExit::Hypercall), diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs index 7be9e0d..084fc13 100644 --- a/kvm_sys/src/aarch64/bindings.rs +++ b/kvm_sys/src/aarch64/bindings.rs @@ -274,11 +274,14 @@ pub const KVM_EXIT_EPR: ::std::os::raw::c_uint = 23; pub const KVM_EXIT_SYSTEM_EVENT: ::std::os::raw::c_uint = 24; pub const KVM_EXIT_S390_STSI: ::std::os::raw::c_uint = 25; pub const KVM_EXIT_IOAPIC_EOI: ::std::os::raw::c_uint = 26; +pub const KVM_EXIT_HYPERV: ::std::os::raw::c_uint = 27; pub const KVM_INTERNAL_ERROR_EMULATION: ::std::os::raw::c_uint = 1; pub const KVM_INTERNAL_ERROR_SIMUL_EX: ::std::os::raw::c_uint = 2; pub const KVM_INTERNAL_ERROR_DELIVERY_EV: ::std::os::raw::c_uint = 3; pub const KVM_EXIT_IO_IN: ::std::os::raw::c_uint = 0; pub const KVM_EXIT_IO_OUT: ::std::os::raw::c_uint = 1; +pub const KVM_EXIT_HYPERV_SYNIC: ::std::os::raw::c_uint = 1; +pub const KVM_EXIT_HYPERV_HCALL: ::std::os::raw::c_uint = 2; pub const KVM_S390_RESET_POR: ::std::os::raw::c_uint = 1; pub const KVM_S390_RESET_CLEAR: ::std::os::raw::c_uint = 2; pub const KVM_S390_RESET_SUBSYSTEM: ::std::os::raw::c_uint = 4; @@ -1694,6 +1697,7 @@ pub union kvm_run__bindgen_ty_1 { pub system_event: kvm_run__bindgen_ty_1__bindgen_ty_17, pub s390_stsi: kvm_run__bindgen_ty_1__bindgen_ty_18, pub eoi: kvm_run__bindgen_ty_1__bindgen_ty_19, + pub hyperv: kvm_hyperv_exit, pub padding: [::std::os::raw::c_char; 256usize], _bindgen_union_align: [u64; 32usize], } @@ -2833,6 +2837,174 @@ fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_19() { ) ); } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct kvm_hyperv_exit { + pub type_: __u32, + pub pad: __u32, + pub u: kvm_hyperv_exit__bindgen_ty_1, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit() { + assert_eq!( + ::std::mem::size_of::(), + 40usize, + concat!("Size of: ", stringify!(kvm_hyperv_exit)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(kvm_hyperv_exit)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).u as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit), + "::", + stringify!(u) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union kvm_hyperv_exit__bindgen_ty_1 { + pub synic: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1, + pub hcall: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(kvm_hyperv_exit__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(kvm_hyperv_exit__bindgen_ty_1)) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1 { + pub msr: __u32, + pub pad: __u32, + pub control: __u64, + pub evt_page: __u64, + pub msg_page: __u64, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!( + "Size of: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!( + "Alignment of ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).control + as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(control) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).evt_page + as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(evt_page) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).msg_page + as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(msg_page) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2 { + pub input: __u64, + pub result: __u64, + pub params: [__u64; 2], +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!( + "Size of: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2) + ) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!( + "Alignment of ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).result + as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2), + "::", + stringify!(result) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).params + as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2), + "::", + stringify!(params) + ) + ); +} #[test] fn bindgen_test_layout_kvm_run__bindgen_ty_1() { assert_eq!( diff --git a/kvm_sys/src/lib.rs b/kvm_sys/src/lib.rs index 4c0324d..8f27690 100644 --- a/kvm_sys/src/lib.rs +++ b/kvm_sys/src/lib.rs @@ -8,9 +8,6 @@ use sys_util::{ioctl_io_nr, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr}; -// Somehow this one gets missed by bindgen -pub const KVM_EXIT_IO_OUT: ::std::os::raw::c_uint = 1; - // Each of the below modules defines ioctls specific to their platform. #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs index 5991d17..7236611 100644 --- a/kvm_sys/src/x86/bindings.rs +++ b/kvm_sys/src/x86/bindings.rs @@ -251,11 +251,14 @@ pub const KVM_EXIT_EPR: ::std::os::raw::c_uint = 23; pub const KVM_EXIT_SYSTEM_EVENT: ::std::os::raw::c_uint = 24; pub const KVM_EXIT_S390_STSI: ::std::os::raw::c_uint = 25; pub const KVM_EXIT_IOAPIC_EOI: ::std::os::raw::c_uint = 26; +pub const KVM_EXIT_HYPERV: ::std::os::raw::c_uint = 27; pub const KVM_INTERNAL_ERROR_EMULATION: ::std::os::raw::c_uint = 1; pub const KVM_INTERNAL_ERROR_SIMUL_EX: ::std::os::raw::c_uint = 2; pub const KVM_INTERNAL_ERROR_DELIVERY_EV: ::std::os::raw::c_uint = 3; pub const KVM_EXIT_IO_IN: ::std::os::raw::c_uint = 0; pub const KVM_EXIT_IO_OUT: ::std::os::raw::c_uint = 1; +pub const KVM_EXIT_HYPERV_SYNIC: ::std::os::raw::c_uint = 1; +pub const KVM_EXIT_HYPERV_HCALL: ::std::os::raw::c_uint = 2; pub const KVM_S390_RESET_POR: ::std::os::raw::c_uint = 1; pub const KVM_S390_RESET_CLEAR: ::std::os::raw::c_uint = 2; pub const KVM_S390_RESET_SUBSYSTEM: ::std::os::raw::c_uint = 4; @@ -4084,6 +4087,7 @@ pub union kvm_run__bindgen_ty_1 { pub system_event: kvm_run__bindgen_ty_1__bindgen_ty_17, pub s390_stsi: kvm_run__bindgen_ty_1__bindgen_ty_18, pub eoi: kvm_run__bindgen_ty_1__bindgen_ty_19, + pub hyperv: kvm_hyperv_exit, pub padding: [::std::os::raw::c_char; 256usize], _bindgen_union_align: [u64; 32usize], } @@ -5223,7 +5227,174 @@ fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_19() { ) ); } - +#[repr(C)] +#[derive(Copy, Clone)] +pub struct kvm_hyperv_exit { + pub type_: __u32, + pub pad: __u32, + pub u: kvm_hyperv_exit__bindgen_ty_1, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit() { + assert_eq!( + ::std::mem::size_of::(), + 40usize, + concat!("Size of: ", stringify!(kvm_hyperv_exit)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(kvm_hyperv_exit)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).u as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit), + "::", + stringify!(u) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union kvm_hyperv_exit__bindgen_ty_1 { + pub synic: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1, + pub hcall: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(kvm_hyperv_exit__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(kvm_hyperv_exit__bindgen_ty_1)) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1 { + pub msr: __u32, + pub pad: __u32, + pub control: __u64, + pub evt_page: __u64, + pub msg_page: __u64, +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!( + "Size of: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!( + "Alignment of ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).control + as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(control) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).evt_page + as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(evt_page) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).msg_page + as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(msg_page) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2 { + pub input: __u64, + pub result: __u64, + pub params: [__u64; 2], +} +#[test] +fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!( + "Size of: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2) + ) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!( + "Alignment of ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).result + as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2), + "::", + stringify!(result) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).params + as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2), + "::", + stringify!(params) + ) + ); +} #[test] fn bindgen_test_layout_kvm_run__bindgen_ty_1() { assert_eq!( diff --git a/protos/src/plugin.proto b/protos/src/plugin.proto index 442eef0..8e5f3a9 100644 --- a/protos/src/plugin.proto +++ b/protos/src/plugin.proto @@ -403,15 +403,30 @@ message VcpuResponse { bytes debugregs = 8; } - // This type of wait reason is only generated after a PuaseVcpus request on this VCPU. + // This type of wait reason is only generated after a PauseVcpus request on this VCPU. message User { uint64 user = 1; } + message HypervCall { + uint64 input = 1; + uint64 params0 = 2; + uint64 params1 = 3; + } + + message HypervSynic { + uint32 msr = 1; + uint64 control = 2; + uint64 evt_page = 3; + uint64 msg_page = 4; + } + oneof exit { Init init = 1; Io io = 2; User user = 3; + HypervCall hyperv_call = 4; + HypervSynic hyperv_synic = 5; } } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index adda9a3..1c7027f 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -517,6 +517,21 @@ pub fn run_vcpus( &vcpu, ); } + VcpuExit::HypervHcall { input, params } => { + let mut data = [0; 8]; + vcpu_plugin.hyperv_call(input, params, &mut data, &vcpu); + // Setting data for hyperv call can not fail. + let _ = vcpu.set_data(&data); + } + VcpuExit::HypervSynic { + msr, + control, + evt_page, + msg_page, + } => { + vcpu_plugin + .hyperv_synic(msr, control, evt_page, msg_page, &vcpu); + } VcpuExit::Hlt => break, VcpuExit::Shutdown => break, VcpuExit::InternalError => { diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index ae96789..3bb6bed 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -513,6 +513,54 @@ impl PluginVcpu { self.process(IoSpace::Mmio, addr, VcpuRunData::Write(data), vcpu) } + /// Has the plugin process handle a hyper-v call. + pub fn hyperv_call(&self, input: u64, params: [u64; 2], data: &mut [u8], vcpu: &Vcpu) -> bool { + let mut wait_reason = VcpuResponse_Wait::new(); + let hv = wait_reason.mut_hyperv_call(); + hv.input = input; + hv.params0 = params[0]; + hv.params1 = params[1]; + + self.wait_reason.set(Some(wait_reason)); + match self.handle_until_resume(vcpu) { + Ok(resume_data) => { + data.copy_from_slice(&resume_data); + true + } + Err(e) if e.errno() == EPIPE => false, + Err(e) => { + error!("failed to process hyperv call request: {}", e); + false + } + } + } + + /// Has the plugin process handle a synic config change. + pub fn hyperv_synic( + &self, + msr: u32, + control: u64, + evt_page: u64, + msg_page: u64, + vcpu: &Vcpu, + ) -> bool { + let mut wait_reason = VcpuResponse_Wait::new(); + let hv = wait_reason.mut_hyperv_synic(); + hv.msr = msr; + hv.control = control; + hv.evt_page = evt_page; + hv.msg_page = msg_page; + self.wait_reason.set(Some(wait_reason)); + match self.handle_until_resume(vcpu) { + Ok(_resume_data) => true, + Err(e) if e.errno() == EPIPE => false, + Err(e) => { + error!("failed to process hyperv synic request: {}", e); + false + } + } + } + fn handle_request(&self, vcpu: &Vcpu) -> SysResult>> { let mut wait_reason = self.wait_reason.take(); let mut do_recv = true; diff --git a/tests/plugin_enable_cap.c b/tests/plugin_enable_cap.c index 60977ef..7ae416e 100644 --- a/tests/plugin_enable_cap.c +++ b/tests/plugin_enable_cap.c @@ -5,14 +5,159 @@ */ #include +#include #include +#include #include #include #include #include +#include +#include +#include #include "crosvm.h" +#define KILL_ADDRESS 0x3f9 + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#endif + +#ifndef F_SEAL_SHRINK +#define F_SEAL_SHRINK 0x0002 +#endif + +const uint8_t code[] = { + // Set a non-zero value for HV_X64_MSR_GUEST_OS_ID + // to enable hypercalls. + + // mov edx, 0xffffffff + 0x66, 0xba, 0xff, 0xff, 0xff, 0xff, + + // mov eax, 0xffffffff + 0x66, 0xb8, 0xff, 0xff, 0xff, 0xff, + + // mov ecx, 0x40000000 # HV_X64_MSR_GUEST_OS_ID + 0x66, 0xb9, 0x00, 0x00, 0x00, 0x40, + + // wrmsr + 0x0f, 0x30, + + // Establish page at 0x2000 as the hypercall page. + + // mov edx, 0x00000000 + 0x66, 0xba, 0x00, 0x00, 0x00, 0x00, + + // mov eax, 0x00002001 # lowest bit is enable bit + 0x66, 0xb8, 0x01, 0x20, 0x00, 0x00, + + // mov ecx, 0x40000001 # HV_X64_MSR_HYPERCALL + 0x66, 0xb9, 0x01, 0x00, 0x00, 0x40, + + // wrmsr + 0x0f, 0x30, + + // We can't test generic hypercalls since they're + // defined to UD for processors running in real mode. + + // for HV_X64_MSR_CONTROL: + // edx:eax gets transferred as 'control' + + // mov edx, 0x05060708 + 0x66, 0xba, 0x08, 0x07, 0x06, 0x05, + + // mov eax, 0x01020304 + 0x66, 0xb8, 0x04, 0x03, 0x02, 0x01, + + // mov ecx, 0x40000080 # HV_X64_MSR_SCONTROL + 0x66, 0xb9, 0x80, 0x00, 0x00, 0x40, + + // wrmsr + 0x0f, 0x30, + + // Establish page at 0x3000 as the evt_page. + + // mov edx, 0x00000000 + 0x66, 0xba, 0x00, 0x00, 0x00, 0x00, + + // mov eax, 0x00003000 + 0x66, 0xb8, 0x00, 0x30, 0x00, 0x00, + + // mov ecx, 0x40000082 # HV_X64_MSR_SIEFP + 0x66, 0xb9, 0x82, 0x00, 0x00, 0x40, + + // wrmsr + 0x0f, 0x30, + + // Establish page at 0x4000 as the 'msg_page'. + + // mov edx, 0x00000000 + 0x66, 0xba, 0x00, 0x00, 0x00, 0x00, + + // mov eax, 0x00004000 + 0x66, 0xb8, 0x00, 0x40, 0x00, 0x00, + + // mov ecx, 0x40000083 # HV_X64_MSR_SIMP + 0x66, 0xb9, 0x83, 0x00, 0x00, 0x40, + + // wrmsr + 0x0f, 0x30, + + // Request a kill. + + // mov dx, 0x3f9 + 0xba, 0xf9, 0x03, + + // mov al, 0x1 + 0xb0, 0x01, + + // out dx, al + 0xee, + + // hlt + 0xf4 +}; + +int check_synic_access(struct crosvm_vcpu* vcpu, struct crosvm_vcpu_event *evt, + uint32_t msr, uint64_t control, uint64_t evt_page, + uint64_t msg_page, const char *phase) { + if (evt->kind != CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC) { + fprintf(stderr, "Got incorrect exit type before %s: %d\n", phase, + evt->kind); + return 1; + } + if (evt->hyperv_synic.msr != msr || + evt->hyperv_synic._reserved != 0 || + evt->hyperv_synic.control != control || + evt->hyperv_synic.evt_page != evt_page || + evt->hyperv_synic.msg_page != msg_page) { + fprintf(stderr, "Got unexpected synic message after %s: " + "0x%x vs 0x%x, 0x%lx vs 0x%lx, 0x%lx vs 0x%lx, " + "0x%lx vs 0x%lx\n", + phase, msr, evt->hyperv_synic.msr, + control, evt->hyperv_synic.control, + evt_page, evt->hyperv_synic.evt_page, + msg_page, evt->hyperv_synic.msg_page); + return 1; + } + + if (crosvm_vcpu_resume(vcpu) != 0) { + fprintf(stderr, "Failed to resume after %s\n", phase); + return 1; + } + + if (crosvm_vcpu_wait(vcpu, evt) != 0) { + fprintf(stderr, "Failed to wait after %s\n", phase); + return 1; + } + return 0; +} + int main(int argc, char** argv) { struct crosvm* crosvm = NULL; uint64_t cap_args[4] = {0}; @@ -23,6 +168,53 @@ int main(int argc, char** argv) { return 1; } + ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT, + KILL_ADDRESS, 1); + if (ret) { + fprintf(stderr, "failed to reserve kill port: %d\n", ret); + return 1; + } + + // VM mem layout: + // null page, code page, hypercall page, synic evt_page, synic msg_page + int mem_size = 0x4000; + int mem_fd = syscall(SYS_memfd_create, "guest_mem", + MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (mem_fd < 0) { + fprintf(stderr, "failed to create guest memfd: %d\n", errno); + return 1; + } + ret = ftruncate(mem_fd, mem_size); + if (ret) { + fprintf(stderr, "failed to set size of guest memory: %d\n", errno); + return 1; + } + uint8_t *mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, 0x0); + if (mem == MAP_FAILED) { + fprintf(stderr, "failed to mmap guest memory: %d\n", errno); + return 1; + } + fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK); + memcpy(mem, code, sizeof(code)); + + // Before MSR verify hypercall page is zero + int i; + for (i = 0; i < 5; ++i) { + if (mem[0x1000 + i]) { + fprintf(stderr, "Hypercall page isn't zero\n"); + return 1; + } + } + + struct crosvm_memory *mem_obj; + ret = crosvm_create_memory(crosvm, mem_fd, 0x0, mem_size, 0x1000, + false, false, &mem_obj); + if (ret) { + fprintf(stderr, "failed to create memory in crosvm: %d\n", ret); + return 1; + } + struct crosvm_vcpu* vcpu = NULL; ret = crosvm_get_vcpu(crosvm, 0, &vcpu); if (ret) { @@ -61,5 +253,71 @@ int main(int argc, char** argv) { return 1; } + { + struct kvm_sregs sregs = {0}; + crosvm_vcpu_get_sregs(vcpu, &sregs); + sregs.cs.base = 0; + sregs.cs.selector = 0; + sregs.es.base = 0; + sregs.es.selector = 0; + crosvm_vcpu_set_sregs(vcpu, &sregs); + + struct kvm_regs regs = {0}; + crosvm_vcpu_get_regs(vcpu, ®s); + regs.rip = 0x1000; + regs.rflags = 2; + crosvm_vcpu_set_regs(vcpu, ®s); + } + + if (crosvm_vcpu_resume(vcpu) != 0) { + fprintf(stderr, "Failed to resume after init\n"); + return 1; + } + + if (crosvm_vcpu_wait(vcpu, &evt) != 0) { + fprintf(stderr, "Failed to wait after init\n"); + return 1; + } + if (check_synic_access(vcpu, &evt, 0x40000080, 0x506070801020304, 0, 0, + "synic msg #1")) { + return 1; + } + + // After first MSR verify hypercall page is non-zero + uint8_t value = 0; + for (i = 0; i < 5; ++i) { + value |= mem[0x1000+i]; + } + if (value == 0) { + fprintf(stderr, "Hypercall page is still zero\n"); + return 1; + } + + if (check_synic_access(vcpu, &evt, 0x40000082, 0x506070801020304, 0x3000, + 0, "synic msg #2")) { + return 1; + } + + if (check_synic_access(vcpu, &evt, 0x40000083, 0x506070801020304, 0x3000, + 0x4000, "synic msg #3")) { + return 1; + } + + if (evt.kind != CROSVM_VCPU_EVENT_KIND_IO_ACCESS) { + fprintf(stderr, "Got incorrect exit type after synic #3: %d\n", + evt.kind); + return 1; + } + if (evt.io_access.address_space != CROSVM_ADDRESS_SPACE_IOPORT || + evt.io_access.address != KILL_ADDRESS || + !evt.io_access.is_write || + evt.io_access.length != 1 || + evt.io_access.data[0] != 1) { + fprintf(stderr, "Didn't see kill request from VM\n"); + return 1; + } + + fprintf(stderr, "Saw kill request from VM, exiting\n"); + return 0; } -- cgit 1.4.1 From dc7f52bdb76a8f3b3cf6260bc0d861758956991e Mon Sep 17 00:00:00 2001 From: Noah Gold Date: Sat, 1 Feb 2020 13:01:58 -0800 Subject: Use simple virtio_input_events where possible. Previously, all input events in CrosVM were required to be linux input_events, which have a timestamp field that is actually unused by when we send/receive from the guest which are of type virtio_input_event. This CL allows CrosVM to understand both types of input events in a first class manner. It is a follow up on https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1930405. This CL also addresses some bugs with window driven input: 1. attach_event_device was being called before the surface was created, so the devices were never attached. 2. The default touchpad size was not being set to the display window size. Additionally, it removes the unused event "filter" feature on event sources. Breaking change: from this point forward, CrosVM will treat input events sent via a socket (e.g. SocketEventSource) to be virtio_input_events. BUG=None TEST=builds + manual Change-Id: I7fec07c582e5a071a6f116975ba70d6e621bb483 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2034046 Reviewed-by: Zach Reizner Tested-by: kokoro Commit-Queue: Noah Gold --- devices/src/virtio/gpu/virtio_backend.rs | 4 + devices/src/virtio/input/event_source.rs | 134 ++++++++++++------------------- devices/src/virtio/input/mod.rs | 30 +------ gpu_display/src/event_device.rs | 16 ++-- gpu_display/src/gpu_display_x.rs | 22 ++--- linux_input_sys/src/lib.rs | 99 +++++++++++++++++------ src/crosvm.rs | 4 +- src/linux.rs | 11 +-- 8 files changed, 157 insertions(+), 163 deletions(-) diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs index 605cf5a..bb1db4a 100644 --- a/devices/src/virtio/gpu/virtio_backend.rs +++ b/devices/src/virtio/gpu/virtio_backend.rs @@ -59,6 +59,7 @@ impl VirtioBackend { pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { // TODO(zachr): support more than one scanout. if scanout != 0 { + error!("got nonzero scanout: {:}, but only support zero.", scanout); return; } @@ -105,6 +106,9 @@ impl VirtioBackend { match display.create_surface(None, self.display_width, self.display_height) { Ok(surface_id) => { self.scanout_surface_id = Some(surface_id); + for (event_device_id, _) in &self.event_devices { + display.attach_event_device(surface_id, *event_device_id); + } } Err(e) => error!("failed to create display surface: {}", e), } diff --git a/devices/src/virtio/input/event_source.rs b/devices/src/virtio/input/event_source.rs index d190e18..392c121 100644 --- a/devices/src/virtio/input/event_source.rs +++ b/devices/src/virtio/input/event_source.rs @@ -4,32 +4,15 @@ use super::constants::*; use super::evdev::{grab_evdev, ungrab_evdev}; -use super::virtio_input_event; use super::InputError; use super::Result; use data_model::DataInit; -use linux_input_sys::input_event; +use linux_input_sys::{input_event, virtio_input_event, InputEventDecoder}; use std::collections::VecDeque; use std::io::Read; use std::io::Write; -use std::mem::size_of; use std::os::unix::io::{AsRawFd, RawFd}; -use sys_util::{error, warn}; - -trait ConvertFromVirtioInputEvent { - fn from_virtio_input_event(other: &virtio_input_event) -> input_event; -} - -impl ConvertFromVirtioInputEvent for input_event { - fn from_virtio_input_event(other: &virtio_input_event) -> input_event { - input_event { - timestamp_fields: [0, 0], - type_: other.type_.into(), - code: other.code.into(), - value: other.value.into(), - } - } -} +use sys_util::warn; /// Encapsulates a socket or device node into an abstract event source, providing a common /// interface. @@ -58,21 +41,11 @@ pub trait EventSource: AsRawFd { fn send_event(&mut self, vio_evt: &virtio_input_event) -> Result<()>; } -// Try to read 16 events at a time to match what the linux guest driver does. -const READ_BUFFER_SIZE: usize = 16 * size_of::(); - -// The read buffer needs to be aligned to the alignment of input_event, which is aligned as u64 -#[repr(align(8))] -pub struct ReadBuffer { - buffer: [u8; READ_BUFFER_SIZE], -} - /// Encapsulates implementation details common to all kinds of event sources. pub struct EventSourceImpl { source: T, queue: VecDeque, - read_buffer: ReadBuffer, - // The read index accounts for incomplete events read previously. + read_buffer: Vec, read_idx: usize, } @@ -86,38 +59,19 @@ impl EventSourceImpl where T: Read + Write, { - // Receive events from the source and store them in a queue, unless they should be filtered out. - fn receive_events bool>(&mut self, event_filter: F) -> Result { + // Receive events from the source and store them in a queue. + fn receive_events(&mut self) -> Result { let read = self .source - .read(&mut self.read_buffer.buffer[self.read_idx..]) + .read(&mut self.read_buffer[self.read_idx..]) .map_err(InputError::EventsReadError)?; let buff_size = read + self.read_idx; - for evt_slice in self.read_buffer.buffer[..buff_size].chunks_exact(input_event::EVENT_SIZE) - { - let input_evt = match input_event::from_slice(evt_slice) { - Some(x) => x, - None => { - // This shouldn't happen because all slices (even the last one) are guaranteed - // to have the correct size and be properly aligned. - error!( - "Failed converting a slice of sice {} to input_event", - evt_slice.len() - ); - // Skipping the event here effectively means no events will be received, because - // if from_slice fails once it will fail always. - continue; - } - }; - if !event_filter(&input_evt) { - continue; - } - let vio_evt = virtio_input_event::from_input_event(input_evt); - self.queue.push_back(vio_evt); + for evt_slice in self.read_buffer[..buff_size].chunks_exact(E::SIZE) { + self.queue.push_back(E::decode(evt_slice)); } - let remainder = buff_size % input_event::EVENT_SIZE; + let remainder = buff_size % E::SIZE; // If there is an incomplete event at the end of the buffer, it needs to be moved to the // beginning and the next read operation must write right after it. if remainder != 0 { @@ -125,13 +79,13 @@ where // The copy should only happen if there is at least one complete event in the buffer, // otherwise source and destination would be the same. if buff_size != remainder { - let (des, src) = self.read_buffer.buffer.split_at_mut(buff_size - remainder); + let (des, src) = self.read_buffer.split_at_mut(buff_size - remainder); des[..remainder].copy_from_slice(&src[..remainder]); } } self.read_idx = remainder; - let received_events = buff_size / input_event::EVENT_SIZE; + let received_events = buff_size / E::SIZE; Ok(received_events) } @@ -144,32 +98,42 @@ where self.queue.pop_front() } - fn send_event(&mut self, vio_evt: &virtio_input_event) -> Result<()> { - let evt = input_event::from_virtio_input_event(vio_evt); + fn send_event(&mut self, vio_evt: &virtio_input_event, encoding: EventType) -> Result<()> { // Miscellaneous events produced by the device are sent back to it by the kernel input // subsystem, but because these events are handled by the host kernel as well as the // guest the device would get them twice. Which would prompt the device to send the // event to the guest again entering an infinite loop. - if evt.type_ != EV_MSC { + if vio_evt.type_ != EV_MSC { + let evt; + let event_bytes = match encoding { + EventType::InputEvent => { + evt = input_event::from_virtio_input_event(vio_evt); + evt.as_slice() + } + EventType::VirtioInputEvent => vio_evt.as_slice(), + }; self.source - .write_all(evt.as_slice()) + .write_all(event_bytes) .map_err(InputError::EventsWriteError)?; } Ok(()) } - fn new(source: T) -> EventSourceImpl { + fn new(source: T, capacity: usize) -> EventSourceImpl { EventSourceImpl { source, queue: VecDeque::new(), - read_buffer: ReadBuffer { - buffer: [0u8; READ_BUFFER_SIZE], - }, + read_buffer: vec![0; capacity], read_idx: 0, } } } +enum EventType { + VirtioInputEvent, + InputEvent, +} + /// Encapsulates a (unix) socket as an event source. pub struct SocketEventSource { evt_source_impl: EventSourceImpl, @@ -181,7 +145,7 @@ where { pub fn new(source: T) -> SocketEventSource { SocketEventSource { - evt_source_impl: EventSourceImpl::new(source), + evt_source_impl: EventSourceImpl::new(source, 16 * virtio_input_event::SIZE), } } } @@ -205,7 +169,7 @@ where } fn receive_events(&mut self) -> Result { - self.evt_source_impl.receive_events(|_evt| true) + self.evt_source_impl.receive_events::() } fn available_events_count(&self) -> usize { @@ -217,7 +181,8 @@ where } fn send_event(&mut self, vio_evt: &virtio_input_event) -> Result<()> { - self.evt_source_impl.send_event(vio_evt) + self.evt_source_impl + .send_event(vio_evt, EventType::VirtioInputEvent) } } @@ -232,7 +197,7 @@ where { pub fn new(source: T) -> EvdevEventSource { EvdevEventSource { - evt_source_impl: EventSourceImpl::new(source), + evt_source_impl: EventSourceImpl::new(source, 16 * input_event::SIZE), } } } @@ -256,7 +221,7 @@ where } fn receive_events(&mut self) -> Result { - self.evt_source_impl.receive_events(|_evt| true) + self.evt_source_impl.receive_events::() } fn available_events_count(&self) -> usize { @@ -268,19 +233,20 @@ where } fn send_event(&mut self, vio_evt: &virtio_input_event) -> Result<()> { - self.evt_source_impl.send_event(vio_evt) + self.evt_source_impl + .send_event(vio_evt, EventType::InputEvent) } } #[cfg(test)] mod tests { - use crate::virtio::input::event_source::input_event; - use crate::virtio::input::event_source::EventSourceImpl; - use crate::virtio::input::virtio_input_event; - use data_model::{DataInit, Le16, Le32}; use std::cmp::min; - use std::io::Read; - use std::io::Write; + use std::io::{Read, Write}; + + use data_model::{DataInit, Le16, Le32}; + use linux_input_sys::InputEventDecoder; + + use crate::virtio::input::event_source::{input_event, virtio_input_event, EventSourceImpl}; struct SourceMock { events: Vec, @@ -317,7 +283,7 @@ mod tests { #[test] fn empty_new() { - let mut source = EventSourceImpl::new(SourceMock::new(&vec![])); + let mut source = EventSourceImpl::new(SourceMock::new(&vec![]), 128); assert_eq!( source.available_events(), 0, @@ -332,9 +298,9 @@ mod tests { #[test] fn empty_receive() { - let mut source = EventSourceImpl::new(SourceMock::new(&vec![])); + let mut source = EventSourceImpl::new(SourceMock::new(&vec![]), 128); assert_eq!( - source.receive_events(|_| true).unwrap(), + source.receive_events::().unwrap(), 0, "zero events should be received" ); @@ -367,9 +333,9 @@ mod tests { #[test] fn partial_pop() { let evts = instantiate_input_events(4usize); - let mut source = EventSourceImpl::new(SourceMock::new(&evts)); + let mut source = EventSourceImpl::new(SourceMock::new(&evts), input_event::SIZE * 4); assert_eq!( - source.receive_events(|_| true).unwrap(), + source.receive_events::().unwrap(), evts.len(), "should receive all events" ); @@ -383,9 +349,9 @@ mod tests { fn total_pop() { const EVENT_COUNT: usize = 4; let evts = instantiate_input_events(EVENT_COUNT); - let mut source = EventSourceImpl::new(SourceMock::new(&evts)); + let mut source = EventSourceImpl::new(SourceMock::new(&evts), input_event::SIZE * 4); assert_eq!( - source.receive_events(|_| true).unwrap(), + source.receive_events::().unwrap(), evts.len(), "should receive all events" ); diff --git a/devices/src/virtio/input/mod.rs b/devices/src/virtio/input/mod.rs index 88a2637..c789dd8 100644 --- a/devices/src/virtio/input/mod.rs +++ b/devices/src/virtio/input/mod.rs @@ -20,12 +20,11 @@ use super::{ copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_INPUT, }; -use linux_input_sys::input_event; +use linux_input_sys::{virtio_input_event, InputEventDecoder}; use std::collections::BTreeMap; use std::fmt::{self, Display}; use std::io::Read; use std::io::Write; -use std::mem::size_of; use std::thread; const EVENT_QUEUE_SIZE: u16 = 64; @@ -341,29 +340,6 @@ impl VirtioInputConfig { } } -#[derive(Copy, Clone, Debug, Default)] -#[repr(C)] -pub struct virtio_input_event { - type_: Le16, - code: Le16, - value: Le32, -} - -// Safe because it only has data and has no implicit padding. -unsafe impl DataInit for virtio_input_event {} - -impl virtio_input_event { - const EVENT_SIZE: usize = size_of::(); - - fn from_input_event(other: &input_event) -> virtio_input_event { - virtio_input_event { - type_: Le16::from(other.type_), - code: Le16::from(other.code), - value: Le32::from(other.value), - } - } -} - struct Worker { interrupt: Interrupt, event_source: T, @@ -381,7 +357,7 @@ impl Worker { ) -> Result { let mut writer = Writer::new(mem, avail_desc).map_err(InputError::Descriptor)?; - while writer.available_bytes() >= virtio_input_event::EVENT_SIZE { + while writer.available_bytes() >= virtio_input_event::SIZE { if let Some(evt) = event_source.pop_available_event() { writer.write_obj(evt).map_err(InputError::WriteQueue)?; } else { @@ -437,7 +413,7 @@ impl Worker { mem: &GuestMemory, ) -> Result { let mut reader = Reader::new(mem, avail_desc).map_err(InputError::Descriptor)?; - while reader.available_bytes() >= virtio_input_event::EVENT_SIZE { + while reader.available_bytes() >= virtio_input_event::SIZE { let evt: virtio_input_event = reader.read_obj().map_err(InputError::ReadQueue)?; event_source.send_event(&evt)?; } diff --git a/gpu_display/src/event_device.rs b/gpu_display/src/event_device.rs index 5f1bbc7..5aae55c 100644 --- a/gpu_display/src/event_device.rs +++ b/gpu_display/src/event_device.rs @@ -3,14 +3,14 @@ // found in the LICENSE file. use data_model::DataInit; -use linux_input_sys::input_event; +use linux_input_sys::{virtio_input_event, InputEventDecoder}; use std::collections::VecDeque; use std::io::{self, Error, ErrorKind, Read, Write}; use std::iter::ExactSizeIterator; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; -const EVENT_SIZE: usize = input_event::EVENT_SIZE; +const EVENT_SIZE: usize = virtio_input_event::SIZE; const EVENT_BUFFER_LEN_MAX: usize = 16 * EVENT_SIZE; // /// Half-way build `EventDevice` with only the `event_socket` defined. Finish building the @@ -93,7 +93,7 @@ impl EventDevice { self.event_buffer.is_empty() } - pub fn send_report>( + pub fn send_report>( &mut self, events: E, ) -> io::Result @@ -111,14 +111,14 @@ impl EventDevice { } self.event_buffer - .extend(input_event::syn().as_slice().iter()); + .extend(virtio_input_event::syn().as_slice().iter()); self.flush_buffered_events() } /// Sends the given `event`, returning `Ok(true)` if, after this function returns, there are no /// buffered events remaining. - pub fn send_event_encoded(&mut self, event: input_event) -> io::Result { + pub fn send_event_encoded(&mut self, event: virtio_input_event) -> io::Result { if !self.flush_buffered_events()? { return Ok(false); } @@ -137,14 +137,14 @@ impl EventDevice { Ok(false) } - pub fn recv_event_encoded(&self) -> io::Result { + pub fn recv_event_encoded(&self) -> io::Result { let mut event_bytes = [0u8; 24]; (&self.event_socket).read_exact(&mut event_bytes)?; - match input_event::from_slice(&event_bytes) { + match virtio_input_event::from_slice(&event_bytes) { Some(event) => Ok(*event), None => Err(Error::new( ErrorKind::InvalidInput, - "failed to read input_event", + "failed to read virtio_input_event", )), } } diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs index c0074ca..2940f40 100644 --- a/gpu_display/src/gpu_display_x.rs +++ b/gpu_display/src/gpu_display_x.rs @@ -11,7 +11,7 @@ )] mod xlib; -use linux_input_sys::input_event; +use linux_input_sys::virtio_input_event; use std::cmp::max; use std::collections::BTreeMap; use std::ffi::{c_void, CStr, CString}; @@ -333,7 +333,11 @@ impl Surface { } } - fn dispatch_to_event_devices(&mut self, events: &[input_event], device_type: EventDeviceKind) { + fn dispatch_to_event_devices( + &mut self, + events: &[virtio_input_event], + device_type: EventDeviceKind, + ) { for event_device in self.event_devices.values_mut() { if event_device.kind() != device_type { continue; @@ -348,7 +352,7 @@ impl Surface { match ev.as_enum(self.buffer_completion_type) { XEventEnum::KeyEvent(key) => { if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) { - let events = &[input_event::key( + let events = &[virtio_input_event::key( linux_keycode, key.type_ == xlib::KeyPress as i32, )]; @@ -363,9 +367,9 @@ impl Surface { if button_event.button & xlib::Button1 != 0 { // The touch event *must* be first per the Linux input subsystem's guidance. let events = &[ - input_event::touch(pressed), - input_event::absolute_x(max(0, button_event.x) as u32), - input_event::absolute_y(max(0, button_event.y) as u32), + virtio_input_event::touch(pressed), + virtio_input_event::absolute_x(max(0, button_event.x) as u32), + virtio_input_event::absolute_y(max(0, button_event.y) as u32), ]; self.dispatch_to_event_devices(events, EventDeviceKind::Touchscreen); } @@ -373,9 +377,9 @@ impl Surface { XEventEnum::Motion(motion) => { if motion.state & xlib::Button1Mask != 0 { let events = &[ - input_event::touch(true), - input_event::absolute_x(max(0, motion.x) as u32), - input_event::absolute_y(max(0, motion.y) as u32), + virtio_input_event::touch(true), + virtio_input_event::absolute_x(max(0, motion.x) as u32), + virtio_input_event::absolute_y(max(0, motion.y) as u32), ]; self.dispatch_to_event_devices(events, EventDeviceKind::Touchscreen); } diff --git a/linux_input_sys/src/lib.rs b/linux_input_sys/src/lib.rs index 3880be2..f70dcc9 100644 --- a/linux_input_sys/src/lib.rs +++ b/linux_input_sys/src/lib.rs @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use data_model::DataInit; +use data_model::{DataInit, Le16, Le32}; use std::mem::size_of; const EV_SYN: u16 = 0x00; @@ -20,6 +20,13 @@ const ABS_Y: u16 = 0x01; const BTN_TOUCH: u16 = 0x14a; const BTN_TOOL_FINGER: u16 = 0x145; +/// Allows a raw input event of the implementor's type to be decoded into +/// a virtio_input_event. +pub trait InputEventDecoder { + const SIZE: usize; + fn decode(data: &[u8]) -> virtio_input_event; +} + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] #[repr(C)] pub struct input_event { @@ -32,55 +39,99 @@ pub struct input_event { unsafe impl DataInit for input_event {} impl input_event { - pub const EVENT_SIZE: usize = size_of::(); - - #[inline] - pub fn syn() -> input_event { + pub fn from_virtio_input_event(other: &virtio_input_event) -> input_event { input_event { timestamp_fields: [0, 0], - type_: EV_SYN, - code: SYN_REPORT, - value: 0, + type_: other.type_.into(), + code: other.code.into(), + value: other.value.into(), + } + } +} + +impl InputEventDecoder for input_event { + const SIZE: usize = size_of::(); + + fn decode(data: &[u8]) -> virtio_input_event { + #[repr(align(8))] + struct Aligner([u8; input_event::SIZE]); + let data_aligned = Aligner(*<[u8; input_event::SIZE]>::from_slice(data).unwrap()); + let e = Self::from_slice(&data_aligned.0).unwrap(); + virtio_input_event { + type_: Le16::from(e.type_), + code: Le16::from(e.code), + value: Le32::from(e.value), + } + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[repr(C)] +pub struct virtio_input_event { + pub type_: Le16, + pub code: Le16, + pub value: Le32, +} + +// Safe because it only has data and has no implicit padding. +unsafe impl DataInit for virtio_input_event {} + +impl InputEventDecoder for virtio_input_event { + const SIZE: usize = size_of::(); + + fn decode(data: &[u8]) -> virtio_input_event { + #[repr(align(4))] + struct Aligner([u8; virtio_input_event::SIZE]); + let data_aligned = Aligner(*<[u8; virtio_input_event::SIZE]>::from_slice(data).unwrap()); + *Self::from_slice(&data_aligned.0).unwrap() + } +} + +impl virtio_input_event { + #[inline] + pub fn syn() -> virtio_input_event { + virtio_input_event { + type_: Le16::from(EV_SYN), + code: Le16::from(SYN_REPORT), + value: Le32::from(0), } } #[inline] - pub fn absolute(code: u16, value: u32) -> input_event { - input_event { - timestamp_fields: [0, 0], - type_: EV_ABS, - code, - value, + pub fn absolute(code: u16, value: u32) -> virtio_input_event { + virtio_input_event { + type_: Le16::from(EV_ABS), + code: Le16::from(code), + value: Le32::from(value), } } #[inline] - pub fn absolute_x(x: u32) -> input_event { + pub fn absolute_x(x: u32) -> virtio_input_event { Self::absolute(ABS_X, x) } #[inline] - pub fn absolute_y(y: u32) -> input_event { + pub fn absolute_y(y: u32) -> virtio_input_event { Self::absolute(ABS_Y, y) } #[inline] - pub fn touch(has_contact: bool) -> input_event { + pub fn touch(has_contact: bool) -> virtio_input_event { Self::key(BTN_TOUCH, has_contact) } #[inline] - pub fn finger_tool(active: bool) -> input_event { + pub fn finger_tool(active: bool) -> virtio_input_event { Self::key(BTN_TOOL_FINGER, active) } #[inline] - pub fn key(code: u16, pressed: bool) -> input_event { - input_event { - timestamp_fields: [0, 0], - type_: EV_KEY, - code, - value: if pressed { 1 } else { 0 }, + pub fn key(code: u16, pressed: bool) -> virtio_input_event { + virtio_input_event { + type_: Le16::from(EV_KEY), + code: Le16::from(code), + value: Le32::from(if pressed { 1 } else { 0 }), } } } diff --git a/src/crosvm.rs b/src/crosvm.rs index e0ddf06..2eead30 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -57,8 +57,8 @@ pub struct GidMap { pub count: u32, } -pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 800; -pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1280; +pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1024; +pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 1280; pub struct TouchDeviceOption { path: PathBuf, diff --git a/src/linux.rs b/src/linux.rs index 97f691f..10f96b8 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -63,10 +63,6 @@ use vm_control::{ }; use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption}; - -#[cfg(feature = "gpu")] -use crate::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH}; - use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] @@ -1048,19 +1044,16 @@ fn create_virtio_devices( #[cfg(feature = "gpu")] { - if cfg.gpu_parameters.is_some() { + if let Some(gpu_parameters) = &cfg.gpu_parameters { let mut event_devices = Vec::new(); if cfg.display_window_mouse { let (event_device_socket, virtio_dev_socket) = UnixStream::pair().map_err(Error::CreateSocket)?; - // TODO(nkgold): the width/height here should match the display's height/width. When - // those settings are available as CLI options, we should use the CLI options here - // as well. let (single_touch_width, single_touch_height) = cfg .virtio_single_touch .as_ref() .map(|single_touch_spec| single_touch_spec.get_size()) - .unwrap_or((DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)); + .unwrap_or((gpu_parameters.display_width, gpu_parameters.display_height)); let dev = virtio::new_single_touch( virtio_dev_socket, single_touch_width, -- cgit 1.4.1 From ea6cf66ab537ea554c53fa8723f5bd20b8ec98bf Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Mon, 11 Nov 2019 18:32:02 +0800 Subject: Vfio: multi vfio group support current one container contains one group only, but one container could contain multi groups actually. The main gap that current code to support multi groups is that container will be initialized multi times when multi groups exist, as each group will initialize container one time. This patch extracts the code which should run one time only on a container, so when the first group is added into container, this container initialize code will run once. The container once initialize code contains: a. Set iommu driver type as VfioType1V2 b. Setup Iommu table on each guest memory region c. create vfio_kvm device, so kernel kvm and vfio is associated. BUG=chromium:992270 TEST=passthrough two/three vfio devices into guest, these devices belong to different vfio groups, then check these devices function in guest. Change-Id: I94c9c86f70f49957a5e5c1dfd2c7d823ad042320 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2078970 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Xiong Zhang --- devices/src/lib.rs | 2 +- devices/src/pci/vfio_pci.rs | 7 -- devices/src/vfio.rs | 168 +++++++++++++++++++++++++++----------------- src/linux.rs | 16 +++-- 4 files changed, 115 insertions(+), 78 deletions(-) diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 2319d86..1ecfc9c 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -44,5 +44,5 @@ pub use self::serial::{ }; pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; pub use self::usb::xhci::xhci_controller::XhciController; -pub use self::vfio::VfioDevice; +pub use self::vfio::{VfioContainer, VfioDevice}; pub use self::virtio::VirtioPciDevice; diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index c031793..4d5ad9e 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -890,13 +890,6 @@ impl PciDevice for VfioPciDevice { } } - if let Err(e) = self.device.setup_dma_map() { - error!( - "failed to add all guest memory regions into iommu table: {}", - e - ); - } - // Quirk, enable igd memory for guest vga arbitrate, otherwise kernel vga arbitrate // driver doesn't claim this vga device, then xorg couldn't boot up. if self.is_intel_gfx() { diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs index 88d7066..a574430 100644 --- a/devices/src/vfio.rs +++ b/devices/src/vfio.rs @@ -3,6 +3,7 @@ // found in the LICENSE file. use data_model::vec_with_array_field; +use std::collections::HashMap; use std::ffi::CString; use std::fmt; use std::fs::{File, OpenOptions}; @@ -11,7 +12,9 @@ use std::mem; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::prelude::FileExt; use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::u32; +use sync::Mutex; use kvm::Vm; use sys_util::{ @@ -76,25 +79,34 @@ fn get_error() -> Error { Error::last() } -struct VfioContainer { +/// VfioContainer contain multi VfioGroup, and delegate an IOMMU domain table +pub struct VfioContainer { container: File, + kvm_vfio_dev: Option, + groups: HashMap>, } const VFIO_API_VERSION: u8 = 0; impl VfioContainer { - fn new() -> Result { + /// Open VfioContainer + pub fn new() -> Result { let container = OpenOptions::new() .read(true) .write(true) .open("/dev/vfio/vfio") .map_err(VfioError::OpenContainer)?; - Ok(VfioContainer { container }) - } - - fn get_api_version(&self) -> i32 { // Safe as file is vfio container fd and ioctl is defined by kernel. - unsafe { ioctl(self, VFIO_GET_API_VERSION()) } + let version = unsafe { ioctl(&container, VFIO_GET_API_VERSION()) }; + if version as u8 != VFIO_API_VERSION { + return Err(VfioError::VfioApiVersion); + } + + Ok(VfioContainer { + container, + kvm_vfio_dev: None, + groups: HashMap::new(), + }) } fn check_extension(&self, val: u32) -> bool { @@ -150,6 +162,66 @@ impl VfioContainer { Ok(()) } + + fn init(&mut self, vm: &Vm, guest_mem: &GuestMemory) -> Result<(), VfioError> { + if !self.check_extension(VFIO_TYPE1v2_IOMMU) { + return Err(VfioError::VfioType1V2); + } + + if self.set_iommu(VFIO_TYPE1v2_IOMMU) < 0 { + return Err(VfioError::ContainerSetIOMMU(get_error())); + } + + // Add all guest memory regions into vfio container's iommu table, + // then vfio kernel driver could access guest memory from gfn + guest_mem.with_regions(|_index, guest_addr, size, host_addr, _fd_offset| { + // Safe because the guest regions are guaranteed not to overlap + unsafe { self.vfio_dma_map(guest_addr.0, size as u64, host_addr as u64) } + })?; + + let mut vfio_dev = kvm_sys::kvm_create_device { + type_: kvm_sys::kvm_device_type_KVM_DEV_TYPE_VFIO, + fd: 0, + flags: 0, + }; + vm.create_device(&mut vfio_dev) + .map_err(VfioError::CreateVfioKvmDevice)?; + // Safe as we are the owner of vfio_dev.fd which is valid value. + let kvm_vfio_file = unsafe { File::from_raw_fd(vfio_dev.fd as i32) }; + self.kvm_vfio_dev = Some(kvm_vfio_file); + + Ok(()) + } + + fn get_group( + &mut self, + id: u32, + vm: &Vm, + guest_mem: &GuestMemory, + ) -> Result, VfioError> { + match self.groups.get(&id) { + Some(group) => Ok(group.clone()), + None => { + let group = Arc::new(VfioGroup::new(self, id)?); + + if self.groups.is_empty() { + // Before the first group is added into container, do once cotainer + // initialize for a vm + self.init(vm, guest_mem)?; + } + + let kvm_vfio_file = self + .kvm_vfio_dev + .as_ref() + .expect("kvm vfio device should exist"); + group.kvm_device_add_group(kvm_vfio_file)?; + + self.groups.insert(id, group.clone()); + + Ok(group) + } + } + } } impl AsRawFd for VfioContainer { @@ -160,11 +232,10 @@ impl AsRawFd for VfioContainer { struct VfioGroup { group: File, - container: VfioContainer, } impl VfioGroup { - fn new(id: u32, vm: &Vm) -> Result { + fn new(container: &VfioContainer, id: u32) -> Result { let mut group_path = String::from("/dev/vfio/"); let s_id = &id; group_path.push_str(s_id.to_string().as_str()); @@ -190,14 +261,6 @@ impl VfioGroup { return Err(VfioError::GroupViable); } - let container = VfioContainer::new()?; - if container.get_api_version() as u8 != VFIO_API_VERSION { - return Err(VfioError::VfioApiVersion); - } - if !container.check_extension(VFIO_TYPE1v2_IOMMU) { - return Err(VfioError::VfioType1V2); - } - // Safe as we are the owner of group_file and container_raw_fd which are valid value, // and we verify the ret value let container_raw_fd = container.as_raw_fd(); @@ -206,32 +269,11 @@ impl VfioGroup { return Err(VfioError::GroupSetContainer(get_error())); } - ret = container.set_iommu(VFIO_TYPE1v2_IOMMU); - if ret < 0 { - return Err(VfioError::ContainerSetIOMMU(get_error())); - } - - Self::kvm_device_add_group(vm, &group_file)?; - - Ok(VfioGroup { - group: group_file, - container, - }) + Ok(VfioGroup { group: group_file }) } - fn kvm_device_add_group(vm: &Vm, group: &File) -> Result { - let mut vfio_dev = kvm_sys::kvm_create_device { - type_: kvm_sys::kvm_device_type_KVM_DEV_TYPE_VFIO, - fd: 0, - flags: 0, - }; - vm.create_device(&mut vfio_dev) - .map_err(VfioError::CreateVfioKvmDevice)?; - - // Safe as we are the owner of vfio_dev.fd which is valid value. - let vfio_dev_fd = unsafe { File::from_raw_fd(vfio_dev.fd as i32) }; - - let group_fd = group.as_raw_fd(); + fn kvm_device_add_group(&self, kvm_vfio_file: &File) -> Result<(), VfioError> { + let group_fd = self.as_raw_fd(); let group_fd_ptr = &group_fd as *const i32; let vfio_dev_attr = kvm_sys::kvm_device_attr { flags: 0, @@ -243,12 +285,16 @@ impl VfioGroup { // Safe as we are the owner of vfio_dev_fd and vfio_dev_attr which are valid value, // and we verify the return value. if 0 != unsafe { - ioctl_with_ref(&vfio_dev_fd, kvm_sys::KVM_SET_DEVICE_ATTR(), &vfio_dev_attr) + ioctl_with_ref( + kvm_vfio_file, + kvm_sys::KVM_SET_DEVICE_ATTR(), + &vfio_dev_attr, + ) } { return Err(VfioError::KvmSetDeviceAttr(get_error())); } - Ok(vfio_dev_fd) + Ok(()) } fn get_device(&self, name: &Path) -> Result { @@ -296,17 +342,22 @@ struct VfioRegion { /// Vfio device for exposing regions which could be read/write to kernel vfio device. pub struct VfioDevice { dev: File, - group: VfioGroup, + container: Arc>, + group_fd: RawFd, // vec for vfio device's regions regions: Vec, - guest_mem: GuestMemory, } impl VfioDevice { /// Create a new vfio device, then guest read/write on this device could be /// transfered into kernel vfio. /// sysfspath specify the vfio device path in sys file system. - pub fn new(sysfspath: &Path, vm: &Vm, guest_mem: GuestMemory) -> Result { + pub fn new( + sysfspath: &Path, + vm: &Vm, + guest_mem: &GuestMemory, + container: Arc>, + ) -> Result { let mut uuid_path = PathBuf::new(); uuid_path.push(sysfspath); uuid_path.push("iommu_group"); @@ -317,15 +368,15 @@ impl VfioDevice { .parse::() .map_err(|_| VfioError::InvalidPath)?; - let group = VfioGroup::new(group_id, vm)?; + let group = container.lock().get_group(group_id, vm, guest_mem)?; let new_dev = group.get_device(sysfspath)?; let dev_regions = Self::get_regions(&new_dev)?; Ok(VfioDevice { dev: new_dev, - group, + container, + group_fd: group.as_raw_fd(), regions: dev_regions, - guest_mem, }) } @@ -740,8 +791,8 @@ impl VfioDevice { pub fn keep_fds(&self) -> Vec { let mut fds = Vec::new(); fds.push(self.as_raw_fd()); - fds.push(self.group.as_raw_fd()); - fds.push(self.group.container.as_raw_fd()); + fds.push(self.group_fd); + fds.push(self.container.lock().as_raw_fd()); fds } @@ -752,23 +803,12 @@ impl VfioDevice { size: u64, user_addr: u64, ) -> Result<(), VfioError> { - self.group.container.vfio_dma_map(iova, size, user_addr) + self.container.lock().vfio_dma_map(iova, size, user_addr) } /// Remove (iova, user_addr) map from vfio container iommu table pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> { - self.group.container.vfio_dma_unmap(iova, size) - } - - /// Add all guest memory regions into vfio container's iommu table, - /// then vfio kernel driver could access guest memory from gfn - pub fn setup_dma_map(&self) -> Result<(), VfioError> { - self.guest_mem - .with_regions(|_index, guest_addr, size, host_addr, _fd_offset| { - // Safe because the guest regions are guaranteed not to overlap - unsafe { self.vfio_dma_map(guest_addr.0, size as u64, host_addr as u64) } - })?; - Ok(()) + self.container.lock().vfio_dma_unmap(iova, size) } } diff --git a/src/linux.rs b/src/linux.rs index 10f96b8..4a87f7d 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -32,8 +32,8 @@ use audio_streams::shm_streams::NullShmStreamSource; use devices::virtio::EventDevice; use devices::virtio::{self, VirtioDevice}; use devices::{ - self, HostBackendDeviceProvider, PciDevice, VfioDevice, VfioPciDevice, VirtioPciDevice, - XhciController, + self, HostBackendDeviceProvider, PciDevice, VfioContainer, VfioDevice, VfioPciDevice, + VirtioPciDevice, XhciController, }; use io_jail::{self, Minijail}; use kvm::*; @@ -1176,7 +1176,11 @@ fn create_devices( let usb_controller = Box::new(XhciController::new(mem.clone(), usb_provider)); pci_devices.push((usb_controller, simple_jail(&cfg, "xhci")?)); - if cfg.vfio.is_some() { + if let Some(vfio_path) = &cfg.vfio { + let vfio_container = Arc::new(Mutex::new( + VfioContainer::new().map_err(Error::CreateVfioDevice)?, + )); + let (vfio_host_socket_irq, vfio_device_socket_irq) = msg_socket::pair::().map_err(Error::CreateSocket)?; control_sockets.push(TaggedControlSocket::VmIrq(vfio_host_socket_irq)); @@ -1185,9 +1189,9 @@ fn create_devices( msg_socket::pair::().map_err(Error::CreateSocket)?; control_sockets.push(TaggedControlSocket::VmMemory(vfio_host_socket_mem)); - let vfio_path = cfg.vfio.as_ref().unwrap().as_path(); - let vfiodevice = - VfioDevice::new(vfio_path, vm, mem.clone()).map_err(Error::CreateVfioDevice)?; + let vfio_path = vfio_path.as_path(); + let vfiodevice = VfioDevice::new(vfio_path, vm, mem, vfio_container.clone()) + .map_err(Error::CreateVfioDevice)?; let vfiopcidevice = Box::new(VfioPciDevice::new( vfiodevice, vfio_device_socket_irq, -- cgit 1.4.1 From 29775733653225023cce80915b4f4eefa41c695a Mon Sep 17 00:00:00 2001 From: Chirantan Ekbote Date: Thu, 5 Mar 2020 15:29:41 +0900 Subject: seccomp: Add missing syscalls to fs device policy BUG=none TEST=`tast run vm.Fio.virtiofs` Change-Id: I937df0be738e0aa302a4ad3e87ed33ff97afb4fc Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2089174 Tested-by: Chirantan Ekbote Tested-by: kokoro Auto-Submit: Chirantan Ekbote Reviewed-by: Daniel Verkamp Commit-Queue: Chirantan Ekbote --- seccomp/aarch64/fs_device.policy | 3 +++ seccomp/arm/fs_device.policy | 3 +++ seccomp/x86_64/fs_device.policy | 3 +++ 3 files changed, 9 insertions(+) diff --git a/seccomp/aarch64/fs_device.policy b/seccomp/aarch64/fs_device.policy index 5199092..9fd4c8b 100644 --- a/seccomp/aarch64/fs_device.policy +++ b/seccomp/aarch64/fs_device.policy @@ -4,10 +4,13 @@ @include /usr/share/policy/crosvm/common_device.policy +fallocate: 1 fchmodat: 1 fchownat: 1 +fdatasync: 1 fgetxattr: 1 fsetxattr: 1 +fsync: 1 newfstatat: 1 fstatfs: 1 ftruncate: 1 diff --git a/seccomp/arm/fs_device.policy b/seccomp/arm/fs_device.policy index 5822261..eb9df16 100644 --- a/seccomp/arm/fs_device.policy +++ b/seccomp/arm/fs_device.policy @@ -4,12 +4,15 @@ @include /usr/share/policy/crosvm/common_device.policy +fallocate: 1 fchmodat: 1 fchownat: 1 +fdatasync: 1 fgetxattr: 1 fsetxattr: 1 fstatat64: 1 fstatfs64: 1 +fsync: 1 ftruncate64: 1 getdents64: 1 getegid32: 1 diff --git a/seccomp/x86_64/fs_device.policy b/seccomp/x86_64/fs_device.policy index 32e7477..ddb2a51 100644 --- a/seccomp/x86_64/fs_device.policy +++ b/seccomp/x86_64/fs_device.policy @@ -4,11 +4,14 @@ @include /usr/share/policy/crosvm/common_device.policy +fallocate: 1 fchmodat: 1 fchownat: 1 +fdatasync: 1 fgetxattr: 1 fsetxattr: 1 fstatfs: 1 +fsync: 1 ftruncate: 1 getdents64: 1 getegid: 1 -- cgit 1.4.1 From 0d2f3d276d5865707a3b31e89a953d4061117ef1 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Tue, 3 Mar 2020 16:13:31 -0800 Subject: fuzz: ensure sys_util path dependency is used When compiling a new-enough crosvm for fuzzing (after https://crrev.com/c/1749950), the build would fail with an error about importing two different version of sys_util: expected struct `sys_util::shm::SharedMemory`, found a different struct `sys_util::shm::SharedMemory` expected reference `sys_util::shm::SharedMemory (struct `sys_util::shm::SharedMemory`) found reference sys_util::shm::SharedMemory (struct `sys_util::shm::SharedMemory`) perhaps two different versions of crate `sys_util` are being used? Fix this by patching the version of sys_util used by audio_streams so that it also uses the path dependency instead of the sys_util from the dev-rust/sys_util ebuild in the fuzz-specific Cargo.toml. BUG=chromium:1057532 TEST=`USE='asan fuzzer' emerge-nami crosvm` Change-Id: I38252465a1111a9a8f643a59e36733016c5db99d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2086401 Reviewed-by: Zach Reizner Reviewed-by: Chirantan Ekbote Tested-by: kokoro Commit-Queue: Manoj Gupta --- fuzz/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 1616921..93c2e03 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -42,3 +42,6 @@ path = "virtqueue_fuzzer.rs" [[bin]] name = "crosvm_zimage_fuzzer" path = "zimage_fuzzer.rs" + +[patch.crates-io] +sys_util = { path = "../sys_util" } -- cgit 1.4.1 From aee33b5862f329066a82ef58cd6005f11bd3f5ab Mon Sep 17 00:00:00 2001 From: Xiong Zhang Date: Tue, 25 Feb 2020 14:41:28 +0800 Subject: virtio: Add VIRTIO_RING_F_EVENT_IDX test case VIRTIO_RING_F_EVENT_IDX use Wrapping(u16) for irq suppressing, this test case to avoid some corner case for Wrapping. BUG=None TEST=Run build_test.py Change-Id: I47d377056fefcc36739bb197e30319deafb0faf4 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2073902 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Xiong Zhang --- devices/src/virtio/queue.rs | 291 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 290 insertions(+), 1 deletion(-) diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index 18ddd6d..2613d2f 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -450,10 +450,15 @@ impl Queue { } /// inject interrupt into guest on this queue - pub fn trigger_interrupt(&mut self, mem: &GuestMemory, interrupt: &Interrupt) { + /// return true: interrupt is injected into guest for this queue + /// false: interrupt isn't injected + pub fn trigger_interrupt(&mut self, mem: &GuestMemory, interrupt: &Interrupt) -> bool { if self.available_interrupt_enabled(mem) { self.last_used = self.next_used; interrupt.signal_used_queue(self.vector); + true + } else { + false } } @@ -462,3 +467,287 @@ impl Queue { self.features |= features; } } + +#[cfg(test)] +mod tests { + use super::*; + use data_model::{DataInit, Le16, Le32, Le64}; + use std::convert::TryInto; + use std::sync::atomic::AtomicUsize; + use std::sync::Arc; + use sys_util::EventFd; + + const GUEST_MEMORY_SIZE: u64 = 0x10000; + const DESC_OFFSET: u64 = 0; + const AVAIL_OFFSET: u64 = 0x200; + const USED_OFFSET: u64 = 0x400; + const QUEUE_SIZE: usize = 0x10; + const BUFFER_OFFSET: u64 = 0x8000; + const BUFFER_LEN: u32 = 0x400; + + #[derive(Copy, Clone, Debug)] + #[repr(C)] + struct Desc { + addr: Le64, + len: Le32, + flags: Le16, + next: Le16, + } + // Safe as this only runs in test + unsafe impl DataInit for Desc {} + + #[derive(Copy, Clone, Debug)] + #[repr(C)] + struct Avail { + flags: Le16, + idx: Le16, + ring: [Le16; QUEUE_SIZE], + used_event: Le16, + } + // Safe as this only runs in test + unsafe impl DataInit for Avail {} + impl Default for Avail { + fn default() -> Self { + Avail { + flags: Le16::from(0u16), + idx: Le16::from(0u16), + ring: [Le16::from(0u16); QUEUE_SIZE], + used_event: Le16::from(0u16), + } + } + } + + #[derive(Copy, Clone, Debug)] + #[repr(C)] + struct UsedElem { + id: Le32, + len: Le32, + } + // Safe as this only runs in test + unsafe impl DataInit for UsedElem {} + impl Default for UsedElem { + fn default() -> Self { + UsedElem { + id: Le32::from(0u32), + len: Le32::from(0u32), + } + } + } + + #[derive(Copy, Clone, Debug)] + #[repr(C)] + struct Used { + flags: Le16, + idx: Le16, + used_elem_ring: [UsedElem; QUEUE_SIZE], + avail_event: Le16, + } + // Safe as this only runs in test + unsafe impl DataInit for Used {} + impl Default for Used { + fn default() -> Self { + Used { + flags: Le16::from(0u16), + idx: Le16::from(0u16), + used_elem_ring: [UsedElem::default(); QUEUE_SIZE], + avail_event: Le16::from(0u16), + } + } + } + + fn setup_vq(queue: &mut Queue, mem: &GuestMemory) { + let desc = Desc { + addr: Le64::from(BUFFER_OFFSET), + len: Le32::from(BUFFER_LEN), + flags: Le16::from(0u16), + next: Le16::from(1u16), + }; + let _ = mem.write_obj_at_addr(desc, GuestAddress(DESC_OFFSET)); + + let avail = Avail::default(); + let _ = mem.write_obj_at_addr(avail, GuestAddress(AVAIL_OFFSET)); + + let used = Used::default(); + let _ = mem.write_obj_at_addr(used, GuestAddress(USED_OFFSET)); + + queue.desc_table = GuestAddress(DESC_OFFSET); + queue.avail_ring = GuestAddress(AVAIL_OFFSET); + queue.used_ring = GuestAddress(USED_OFFSET); + queue.ack_features((1u64) << VIRTIO_RING_F_EVENT_IDX); + } + + #[test] + fn queue_event_id_guest_fast() { + let mut queue = Queue::new(QUEUE_SIZE.try_into().unwrap()); + let memory_start_addr = GuestAddress(0x0); + let mem = GuestMemory::new(&vec![(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap(); + setup_vq(&mut queue, &mem); + + let interrupt = Interrupt::new( + Arc::new(AtomicUsize::new(0)), + EventFd::new().unwrap(), + EventFd::new().unwrap(), + None, + 10, + ); + + // Calculating the offset of used_event within Avail structure + let used_event_offset: u64 = + unsafe { &(*(::std::ptr::null::())).used_event as *const _ as u64 }; + let used_event_address = GuestAddress(AVAIL_OFFSET + used_event_offset); + + // Assume driver submit 0x100 req to device, + // device has handled them, so increase self.next_used to 0x100 + let mut device_generate: Wrapping = Wrapping(0x100); + for _ in 0..device_generate.0 { + queue.add_used(&mem, 0x0, BUFFER_LEN); + } + + // At this moment driver hasn't handled any interrupts yet, so it + // should inject interrupt. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + + // Driver handle all the interrupts and update avail.used_event to 0x100 + let mut driver_handled = device_generate; + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // At this moment driver have handled all the interrupts, and + // device doesn't generate more data, so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // Assume driver submit another u16::MAX - 0x100 req to device, + // Device has handled all of them, so increase self.next_used to u16::MAX + for _ in device_generate.0..u16::max_value() { + queue.add_used(&mem, 0x0, BUFFER_LEN); + } + device_generate = Wrapping(u16::max_value()); + + // At this moment driver just handled 0x100 interrupts, so it + // should inject interrupt. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + + // driver handle all the interrupts and update avail.used_event to u16::MAX + driver_handled = device_generate; + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // At this moment driver have handled all the interrupts, and + // device doesn't generate more data, so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // Assume driver submit another 1 request, + // device has handled it, so wrap self.next_used to 0 + queue.add_used(&mem, 0x0, BUFFER_LEN); + device_generate += Wrapping(1); + + // At this moment driver has handled all the previous interrupts, so it + // should inject interrupt again. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + + // driver handle that interrupts and update avail.used_event to 0 + driver_handled = device_generate; + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // At this moment driver have handled all the interrupts, and + // device doesn't generate more data, so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + } + + #[test] + fn queue_event_id_guest_slow() { + let mut queue = Queue::new(QUEUE_SIZE.try_into().unwrap()); + let memory_start_addr = GuestAddress(0x0); + let mem = GuestMemory::new(&vec![(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap(); + setup_vq(&mut queue, &mem); + + let interrupt = Interrupt::new( + Arc::new(AtomicUsize::new(0)), + EventFd::new().unwrap(), + EventFd::new().unwrap(), + None, + 10, + ); + + // Calculating the offset of used_event within Avail structure + let used_event_offset: u64 = + unsafe { &(*(::std::ptr::null::())).used_event as *const _ as u64 }; + let used_event_address = GuestAddress(AVAIL_OFFSET + used_event_offset); + + // Assume driver submit 0x100 req to device, + // device have handled 0x100 req, so increase self.next_used to 0x100 + let mut device_generate: Wrapping = Wrapping(0x100); + for _ in 0..device_generate.0 { + queue.add_used(&mem, 0x0, BUFFER_LEN); + } + + // At this moment driver hasn't handled any interrupts yet, so it + // should inject interrupt. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + + // Driver handle part of the interrupts and update avail.used_event to 0x80 + let mut driver_handled = Wrapping(0x80); + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // At this moment driver hasn't finished last interrupt yet, + // so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // Assume driver submit another 1 request, + // device has handled it, so increment self.next_used. + queue.add_used(&mem, 0x0, BUFFER_LEN); + device_generate += Wrapping(1); + + // At this moment driver hasn't finished last interrupt yet, + // so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // Assume driver submit another u16::MAX - 0x101 req to device, + // Device has handled all of them, so increase self.next_used to u16::MAX + for _ in device_generate.0..u16::max_value() { + queue.add_used(&mem, 0x0, BUFFER_LEN); + } + device_generate = Wrapping(u16::max_value()); + + // At this moment driver hasn't finished last interrupt yet, + // so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // driver handle most of the interrupts and update avail.used_event to u16::MAX - 1, + driver_handled = device_generate - Wrapping(1); + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // Assume driver submit another 1 request, + // device has handled it, so wrap self.next_used to 0 + queue.add_used(&mem, 0x0, BUFFER_LEN); + device_generate += Wrapping(1); + + // At this moment driver has already finished the last interrupt(0x100), + // and device service other request, so new interrupt is needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + + // Assume driver submit another 1 request, + // device has handled it, so increment self.next_used to 1 + queue.add_used(&mem, 0x0, BUFFER_LEN); + device_generate += Wrapping(1); + + // At this moment driver hasn't finished last interrupt((Wrapping(0)) yet, + // so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // driver handle all the remain interrupts and wrap avail.used_event to 0x1. + driver_handled = device_generate; + let _ = mem.write_obj_at_addr(Le16::from(driver_handled.0), used_event_address); + + // At this moment driver has handled all the interrupts, and + // device doesn't generate more data, so interrupt isn't needed. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), false); + + // Assume driver submit another 1 request, + // device has handled it, so increase self.next_used. + queue.add_used(&mem, 0x0, BUFFER_LEN); + device_generate += Wrapping(1); + + // At this moment driver has finished all the previous interrupts, so it + // should inject interrupt again. + assert_eq!(queue.trigger_interrupt(&mem, &interrupt), true); + } +} -- cgit 1.4.1 From 3f3c0a3f6ab82779b269446232e9134aa57ad7bc Mon Sep 17 00:00:00 2001 From: Mattias Nissler Date: Fri, 6 Mar 2020 11:34:45 +0100 Subject: ac97: Mark pci::ac97_bus_master::test::start_playback ignored The test is flaky and causes continuous build failures. BUG=chromium:1058881 TEST=Tests run, offending test is listed as ignored. Exempt-From-Owner-Approval: Sheriff action to silence flakes on builders Change-Id: I204e6ef548e6f203b0c15b0d01fde3b88660bd44 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2090414 Reviewed-by: Mattias Nissler Commit-Queue: Mattias Nissler Tested-by: Mattias Nissler Tested-by: kokoro --- devices/src/pci/ac97_bus_master.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index c13913a..cb28c5a 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -946,6 +946,7 @@ mod test { } #[test] + #[ignore] // flaky - see crbug.com/1058881 fn start_playback() { const TIMEOUT: Duration = Duration::from_millis(500); const LVI_MASK: u8 = 0x1f; // Five bits for 32 total entries. -- cgit 1.4.1 From 9515b05c086c55b9e3fbddbc56fb6eb3e9a510a8 Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Mon, 10 Feb 2020 17:06:34 +0900 Subject: devices: virtio: resource_bridge: Transfer plane metadata Transfer plane offsets and strides for exported GPU resource over resource bridge as well as a resource itself. These metadata will be required by virtio-video decoder and encoder. BUG=b:120456557 TEST=Start ARCVM on atlas Change-Id: Iaf539857c0f8525bd5be294521e75ad32cae05e7 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1787032 Reviewed-by: Keiichi Watanabe Reviewed-by: David Stevens Reviewed-by: Chirantan Ekbote Commit-Queue: Keiichi Watanabe Tested-by: Keiichi Watanabe --- devices/src/virtio/gpu/mod.rs | 13 +++---------- devices/src/virtio/gpu/virtio_2d_backend.rs | 6 +++--- devices/src/virtio/gpu/virtio_3d_backend.rs | 18 ++++++++++++------ devices/src/virtio/resource_bridge.rs | 21 +++++++++++++++++---- devices/src/virtio/wl.rs | 8 ++++---- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 0fcc453..8cc211f 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -10,7 +10,6 @@ mod virtio_gfxstream_backend; use std::cell::RefCell; use std::collections::VecDeque; -use std::fs::File; use std::i64; use std::io::Read; use std::mem::{self, size_of}; @@ -141,7 +140,7 @@ trait Backend { fn import_event_device(&mut self, event_device: EventDevice, scanout: u32); /// If supported, export the resource with the given id to a file. - fn export_resource(&mut self, id: u32) -> Option; + fn export_resource(&mut self, id: u32) -> ResourceResponse; /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. fn display_info(&self) -> [(u32, u32); 1]; @@ -416,7 +415,7 @@ impl Frontend { } fn process_resource_bridge(&mut self, resource_bridge: &ResourceResponseSocket) { - let request = match resource_bridge.recv() { + let ResourceRequest::GetResource { id } = match resource_bridge.recv() { Ok(msg) => msg, Err(e) => { error!("error receiving resource bridge request: {}", e); @@ -424,13 +423,7 @@ impl Frontend { } }; - let response = match request { - ResourceRequest::GetResource { id } => self - .backend - .export_resource(id) - .map(ResourceResponse::Resource) - .unwrap_or(ResourceResponse::Invalid), - }; + let response = self.backend.export_resource(id); if let Err(e) = resource_bridge.send(&response) { error!("error sending resource bridge request: {}", e); diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs index d92b015..a51a664 100644 --- a/devices/src/virtio/gpu/virtio_2d_backend.rs +++ b/devices/src/virtio/gpu/virtio_2d_backend.rs @@ -9,7 +9,6 @@ use std::cmp::{max, min}; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; use std::fmt::{self, Display}; -use std::fs::File; use std::marker::PhantomData; use std::rc::Rc; use std::usize; @@ -24,6 +23,7 @@ use vm_control::VmMemoryControlRequestSocket; use super::protocol::GpuResponse; pub use super::virtio_backend::{VirtioBackend, VirtioResource}; use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1}; +use crate::virtio::resource_bridge::ResourceResponse; #[derive(Debug)] pub enum Error { @@ -481,8 +481,8 @@ impl Backend for Virtio2DBackend { } /// If supported, export the resource with the given id to a file. - fn export_resource(&mut self, _id: u32) -> Option { - None + fn export_resource(&mut self, _id: u32) -> ResourceResponse { + ResourceResponse::Invalid } /// Creates a fence with the given id that can be used to determine when the previous command diff --git a/devices/src/virtio/gpu/virtio_3d_backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs index 21a5ac1..7ae044c 100644 --- a/devices/src/virtio/gpu/virtio_3d_backend.rs +++ b/devices/src/virtio/gpu/virtio_3d_backend.rs @@ -8,7 +8,6 @@ use std::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; -use std::fs::File; use std::os::unix::io::AsRawFd; use std::rc::Rc; use std::usize; @@ -35,6 +34,7 @@ use crate::virtio::gpu::{ Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY, VIRTIO_GPU_F_VIRGL, }; +use crate::virtio::resource_bridge::{PlaneInfo, ResourceInfo, ResourceResponse}; use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse}; @@ -319,13 +319,19 @@ impl Backend for Virtio3DBackend { } /// If supported, export the resource with the given id to a file. - fn export_resource(&mut self, id: u32) -> Option { - let test: Option = self - .resources + fn export_resource(&mut self, id: u32) -> ResourceResponse { + self + .resources .get(&id) // Option .and_then(|resource| resource.gpu_resource.export().ok()) // Option<(Query, File)> - .and_then(|t| Some(t.1)); // Option - return test; + .map(|(q, file)| { + ResourceResponse::Resource(ResourceInfo{file, planes: [ + PlaneInfo{offset: q.out_offsets[0], stride: q.out_strides[0]}, + PlaneInfo{offset: q.out_offsets[1], stride: q.out_strides[1]}, + PlaneInfo{offset: q.out_offsets[2], stride: q.out_strides[2]}, + PlaneInfo{offset: q.out_offsets[3], stride: q.out_strides[3]}, + ]}) + }).unwrap_or(ResourceResponse::Invalid) } /// Creates a fence with the given id that can be used to determine when the previous command diff --git a/devices/src/virtio/resource_bridge.rs b/devices/src/virtio/resource_bridge.rs index aaf776c..2a2343f 100644 --- a/devices/src/virtio/resource_bridge.rs +++ b/devices/src/virtio/resource_bridge.rs @@ -16,9 +16,22 @@ pub enum ResourceRequest { GetResource { id: u32 }, } +#[derive(MsgOnSocket, Clone)] +pub struct PlaneInfo { + pub offset: u32, + pub stride: u32, +} + +const RESOURE_PLANE_NUM: usize = 4; +#[derive(MsgOnSocket)] +pub struct ResourceInfo { + pub file: File, + pub planes: [PlaneInfo; RESOURE_PLANE_NUM], +} + #[derive(MsgOnSocket)] pub enum ResourceResponse { - Resource(File), + Resource(ResourceInfo), Invalid, } @@ -58,16 +71,16 @@ impl fmt::Display for ResourceBridgeError { impl std::error::Error for ResourceBridgeError {} -pub fn get_resource_fd( +pub fn get_resource_info( sock: &ResourceRequestSocket, id: u32, -) -> std::result::Result { +) -> std::result::Result { if let Err(e) = sock.send(&ResourceRequest::GetResource { id }) { return Err(ResourceBridgeError::SendFailure(id, e)); } match sock.recv() { - Ok(ResourceResponse::Resource(bridged_file)) => Ok(bridged_file), + Ok(ResourceResponse::Resource(info)) => Ok(info), Ok(ResourceResponse::Invalid) => Err(ResourceBridgeError::InvalidResource(id)), Err(e) => Err(ResourceBridgeError::RecieveFailure(id, e)), } diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs index 5ace29c..65ad1cf 100644 --- a/devices/src/virtio/wl.rs +++ b/devices/src/virtio/wl.rs @@ -1093,10 +1093,10 @@ impl WlState { #[cfg(feature = "gpu")] VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU if self.resource_bridge.is_some() => { let sock = self.resource_bridge.as_ref().unwrap(); - match get_resource_fd(sock, id) { - Ok(bridged_file) => { - *fd = bridged_file.as_raw_fd(); - bridged_files.push(bridged_file); + match get_resource_info(sock, id) { + Ok(info) => { + *fd = info.file.as_raw_fd(); + bridged_files.push(info.file); } Err(ResourceBridgeError::InvalidResource(id)) => { warn!("attempt to send non-existent gpu resource {}", id); -- cgit 1.4.1