diff options
112 files changed, 4518 insertions, 984 deletions
diff --git a/Cargo.lock b/Cargo.lock index e9da7f9..41843ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,6 +447,7 @@ 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)", "msg_on_socket_derive 0.1.0", + "sync 0.1.0", "sys_util 0.1.0", ] diff --git a/Cargo.toml b/Cargo.toml index 66a616a..3fdad6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ exclude = [ "sys_util", "syscall_defines", "tempfile", + "hypervisor" ] [features] diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs index 9dcafa5..19662c6 100644 --- a/aarch64/src/fdt.rs +++ b/aarch64/src/fdt.rs @@ -10,7 +10,8 @@ use arch::fdt::{ begin_node, end_node, finish_fdt, generate_prop32, generate_prop64, property, property_cstring, property_null, property_string, property_u32, property_u64, start_fdt, Error, Result, }; -use devices::{PciInterruptPin, SERIAL_ADDR}; +use arch::SERIAL_ADDR; +use devices::{PciAddress, PciInterruptPin}; use sys_util::{GuestAddress, GuestMemory}; // This is the start of DRAM in the physical address space. @@ -36,12 +37,13 @@ use crate::AARCH64_SERIAL_SIZE; use crate::AARCH64_SERIAL_SPEED; // These are related to guest virtio devices. -use crate::AARCH64_IRQ_BASE; use crate::AARCH64_MMIO_BASE; use crate::AARCH64_MMIO_SIZE; use crate::AARCH64_PCI_CFG_BASE; use crate::AARCH64_PCI_CFG_SIZE; +use crate::AARCH64_PMU_IRQ; + // This is an arbitrary number to specify the node for the GIC. // If we had a more complex interrupt architecture, then we'd need an enum for // these. @@ -137,6 +139,23 @@ fn create_timer_node(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> { Ok(()) } +fn create_pmu_node(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> { + let compatible = "arm,armv8-pmuv3"; + let cpu_mask: u32 = + (((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK; + let irq = generate_prop32(&[ + GIC_FDT_IRQ_TYPE_PPI, + AARCH64_PMU_IRQ, + cpu_mask | IRQ_TYPE_LEVEL_HIGH, + ]); + + begin_node(fdt, "pmu")?; + property_string(fdt, "compatible", compatible)?; + property(fdt, "interrupts", &irq)?; + end_node(fdt)?; + Ok(()) +} + fn create_serial_node(fdt: &mut Vec<u8>, addr: u64, irq: u32) -> Result<()> { let serial_reg_prop = generate_prop64(&[addr, AARCH64_SERIAL_SIZE]); let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, irq, IRQ_TYPE_EDGE_RISING]); @@ -216,7 +235,7 @@ fn create_chosen_node( fn create_pci_nodes( fdt: &mut Vec<u8>, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, pci_device_base: u64, pci_device_size: u64, ) -> Result<()> { @@ -247,14 +266,14 @@ fn create_pci_nodes( let mut interrupts: Vec<u32> = Vec::new(); let mut masks: Vec<u32> = Vec::new(); - for (i, pci_irq) in pci_irqs.iter().enumerate() { + for (address, irq_num, irq_pin) in pci_irqs.iter() { // PCI_DEVICE(3) - interrupts.push((pci_irq.0 + 1) << 11); + interrupts.push(address.to_config_address(0)); interrupts.push(0); interrupts.push(0); // INT#(1) - interrupts.push(pci_irq.1.to_mask() + 1); + interrupts.push(irq_pin.to_mask() + 1); // CONTROLLER(PHANDLE) interrupts.push(PHANDLE_GIC); @@ -263,7 +282,7 @@ fn create_pci_nodes( // CONTROLLER_DATA(3) interrupts.push(GIC_FDT_IRQ_TYPE_SPI); - interrupts.push(AARCH64_IRQ_BASE + i as u32); + interrupts.push(*irq_num); interrupts.push(IRQ_TYPE_LEVEL_HIGH); // PCI_DEVICE(3) @@ -330,7 +349,7 @@ fn create_rtc_node(fdt: &mut Vec<u8>) -> Result<()> { /// /// * `fdt_max_size` - The amount of space reserved for the device tree /// * `guest_mem` - The guest memory object -/// * `pci_irqs` - List of PCI device number to PCI interrupt pin mappings +/// * `pci_irqs` - List of PCI device address to PCI interrupt number and pin mappings /// * `num_cpus` - Number of virtual CPUs the guest will have /// * `fdt_load_offset` - The offset into physical memory for the device tree /// * `pci_device_base` - The offset into physical memory for PCI device memory @@ -342,7 +361,7 @@ fn create_rtc_node(fdt: &mut Vec<u8>) -> Result<()> { pub fn create_fdt( fdt_max_size: usize, guest_mem: &GuestMemory, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, num_cpus: u32, fdt_load_offset: u64, pci_device_base: u64, @@ -351,6 +370,7 @@ pub fn create_fdt( initrd: Option<(GuestAddress, usize)>, android_fstab: Option<File>, is_gicv3: bool, + use_pmu: bool, ) -> Result<()> { let mut fdt = vec![0; fdt_max_size]; start_fdt(&mut fdt, fdt_max_size)?; @@ -369,6 +389,9 @@ pub fn create_fdt( create_cpu_nodes(&mut fdt, num_cpus)?; create_gic_node(&mut fdt, is_gicv3, num_cpus as u64)?; create_timer_node(&mut fdt, num_cpus)?; + if use_pmu { + create_pmu_node(&mut fdt, num_cpus)?; + } create_serial_nodes(&mut fdt)?; create_psci_node(&mut fdt)?; create_pci_nodes(&mut fdt, pci_irqs, pci_device_base, pci_device_size)?; diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index f8a36b9..0a90fd4 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -11,11 +11,11 @@ use std::io; use std::os::unix::io::FromRawFd; use std::sync::Arc; -use arch::{RunnableLinuxVm, VmComponents, VmImage}; -use devices::{ - get_serial_tty_string, Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin, - SerialParameters, +use arch::{ + get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters, + VmComponents, VmImage, }; +use devices::{Bus, BusError, PciAddress, PciConfigMmio, PciDevice, PciInterruptPin}; use io_jail::Minijail; use remain::sorted; use resources::SystemAllocator; @@ -109,6 +109,9 @@ const AARCH64_MMIO_SIZE: u64 = 0x100000; // Virtio devices start at SPI interrupt number 3 const AARCH64_IRQ_BASE: u32 = 3; +// PMU PPI interrupt, same as qemu +const AARCH64_PMU_IRQ: u32 = 7; + #[sorted] #[derive(Debug)] pub enum Error { @@ -124,6 +127,7 @@ pub enum Error { CreateSocket(io::Error), CreateVcpu(sys_util::Error), CreateVm(sys_util::Error), + GetSerialCmdline(GetSerialCmdlineError), InitrdLoadFailure(arch::LoadImageError), KernelLoadFailure(arch::LoadImageError), KernelMissing, @@ -156,6 +160,7 @@ impl Display for Error { CreateSocket(e) => write!(f, "failed to create socket: {}", e), CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e), CreateVm(e) => write!(f, "failed to create vm: {}", e), + GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e), InitrdLoadFailure(e) => write!(f, "initrd cound not be loaded: {}", e), KernelLoadFailure(e) => write!(f, "kernel cound not be loaded: {}", e), KernelMissing => write!(f, "aarch64 requires a kernel"), @@ -197,7 +202,7 @@ impl arch::LinuxArch for AArch64 { mut components: VmComponents, _split_irqchip: bool, _ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap<u8, SerialParameters>, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option<Minijail>, create_devices: F, ) -> Result<RunnableLinuxVm> @@ -235,6 +240,11 @@ impl arch::LinuxArch for AArch64 { let (irq_chip, is_gicv3) = Self::create_irq_chip(&vm, vcpu_count as u64)?; + let mut use_pmu = true; + for vcpu in &vcpus { + use_pmu &= vcpu.arm_pmu_init(AARCH64_PMU_IRQ as u64 + 16).is_ok(); + } + let mut mmio_bus = devices::Bus::new(); let exit_evt = EventFd::new().map_err(Error::CreateEventFd)?; @@ -262,11 +272,11 @@ impl arch::LinuxArch for AArch64 { let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?; let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?; - let stdio_serial_num = arch::add_serial_devices( + arch::add_serial_devices( &mut mmio_bus, &com_evt_1_3, &com_evt_2_4, - &serial_parameters, + serial_parameters, serial_jail, ) .map_err(Error::CreateSerialDevices)?; @@ -285,7 +295,9 @@ impl arch::LinuxArch for AArch64 { ) .map_err(Error::RegisterPci)?; - let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); + let mut cmdline = Self::get_base_linux_cmdline(); + get_serial_cmdline(&mut cmdline, serial_parameters, "mmio") + .map_err(Error::GetSerialCmdline)?; for param in components.extra_kernel_params { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } @@ -311,6 +323,7 @@ impl arch::LinuxArch for AArch64 { components.android_fstab, kernel_end, is_gicv3, + use_pmu, )?; Ok(RunnableLinuxVm { @@ -338,10 +351,11 @@ impl AArch64 { vcpu_count: u32, cmdline: &CStr, initrd_file: Option<File>, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, android_fstab: Option<File>, kernel_end: u64, is_gicv3: bool, + use_pmu: bool, ) -> Result<()> { let initrd = match initrd_file { Some(initrd_file) => { @@ -370,6 +384,7 @@ impl AArch64 { initrd, android_fstab, is_gicv3, + use_pmu, ) .map_err(Error::CreateFdt)?; Ok(()) @@ -388,12 +403,8 @@ impl AArch64 { } /// This returns a base part of the kernel command for this architecture - fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline { + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { let mut cmdline = kernel_cmdline::Cmdline::new(sys_util::pagesize()); - if let Some(stdio_serial_num) = stdio_serial_num { - let tty_string = get_serial_tty_string(stdio_serial_num); - cmdline.insert("console", &tty_string).unwrap(); - } cmdline.insert_str("panic=-1").unwrap(); cmdline } @@ -540,7 +551,7 @@ impl AArch64 { fn configure_vcpu( guest_mem: &GuestMemory, - _kvm: &Kvm, + kvm: &Kvm, vm: &Vm, vcpu: &Vcpu, cpu_id: u64, @@ -555,8 +566,10 @@ impl AArch64 { vm.arm_preferred_target(&mut kvi) .map_err(Error::ReadPreferredTarget)?; - // TODO(sonnyrao): need to verify this feature is supported by host kernel kvi.features[0] |= 1 << kvm_sys::KVM_ARM_VCPU_PSCI_0_2; + if kvm.check_extension(Cap::ArmPmuV3) { + kvi.features[0] |= 1 << kvm_sys::KVM_ARM_VCPU_PMU_V3; + } // Non-boot cpus are powered off initially if cpu_id > 0 { diff --git a/acpi_tables/src/aml.rs b/acpi_tables/src/aml.rs new file mode 100644 index 0000000..7d16153 --- /dev/null +++ b/acpi_tables/src/aml.rs @@ -0,0 +1,1966 @@ +// 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. + +/// The trait Aml can be implemented by the ACPI objects to translate itself +/// into the AML raw data. So that these AML raw data can be added into the +/// ACPI DSDT for guest. +pub trait Aml { + /// Translate an ACPI object into AML code and append to the vector + /// buffer. + /// * `bytes` - The vector used to append the AML code. + fn to_aml_bytes(&self, bytes: &mut Vec<u8>); +} + +// AML byte stream defines +const ZEROOP: u8 = 0x00; +const ONEOP: u8 = 0x01; +const NAMEOP: u8 = 0x08; +const BYTEPREFIX: u8 = 0x0a; +const WORDPREFIX: u8 = 0x0b; +const DWORDPREFIX: u8 = 0x0c; +const STRINGOP: u8 = 0x0d; +const QWORDPREFIX: u8 = 0x0e; +const SCOPEOP: u8 = 0x10; +const BUFFEROP: u8 = 0x11; +const PACKAGEOP: u8 = 0x12; +const METHODOP: u8 = 0x14; +const DUALNAMEPREFIX: u8 = 0x2e; +const MULTINAMEPREFIX: u8 = 0x2f; +const NAMECHARBASE: u8 = 0x40; + +const EXTOPPREFIX: u8 = 0x5b; +const MUTEXOP: u8 = 0x01; +const ACQUIREOP: u8 = 0x23; +const RELEASEOP: u8 = 0x27; +const OPREGIONOP: u8 = 0x80; +const FIELDOP: u8 = 0x81; +const DEVICEOP: u8 = 0x82; + +const LOCAL0OP: u8 = 0x60; +const ARG0OP: u8 = 0x68; +const STOREOP: u8 = 0x70; +const ADDOP: u8 = 0x72; +const CONCATOP: u8 = 0x73; +const SUBTRACTOP: u8 = 0x74; +const MULTIPLYOP: u8 = 0x77; +const SHIFTLEFTOP: u8 = 0x79; +const SHIFTRIGHTOP: u8 = 0x7a; +const ANDOP: u8 = 0x7b; +const NANDOP: u8 = 0x7c; +const OROP: u8 = 0x7d; +const NOROP: u8 = 0x7e; +const XOROP: u8 = 0x7f; +const CONCATRESOP: u8 = 0x84; +const MODOP: u8 = 0x85; +const NOTIFYOP: u8 = 0x86; +const INDEXOP: u8 = 0x88; +const LEQUALOP: u8 = 0x93; +const LLESSOP: u8 = 0x95; +const TOSTRINGOP: u8 = 0x9c; +const IFOP: u8 = 0xa0; +const WHILEOP: u8 = 0xa2; +const RETURNOP: u8 = 0xa4; +const ONESOP: u8 = 0xff; + +// AML resouce data fields +const IOPORTDESC: u8 = 0x47; +const ENDTAG: u8 = 0x79; +const MEMORY32FIXEDDESC: u8 = 0x86; +const DWORDADDRSPACEDESC: u8 = 0x87; +const WORDADDRSPACEDESC: u8 = 0x88; +const EXTIRQDESC: u8 = 0x89; +const QWORDADDRSPACEDESC: u8 = 0x8A; + +/// Zero object in ASL. +pub const ZERO: Zero = Zero {}; +pub struct Zero {} + +impl Aml for Zero { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut vec![ZEROOP]); + } +} + +/// One object in ASL. +pub const ONE: One = One {}; +pub struct One {} + +impl Aml for One { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut vec![ONEOP]); + } +} + +/// Ones object represents all bits 1. +pub const ONES: Ones = Ones {}; +pub struct Ones {} + +impl Aml for Ones { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut vec![ONESOP]); + } +} + +/// Represents Namestring to construct ACPI objects like +/// Name/Device/Method/Scope and so on... +pub struct Path { + root: bool, + name_parts: Vec<[u8; 4]>, +} + +impl Aml for Path { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + if self.root { + bytes.push(b'\\'); + } + + match self.name_parts.len() { + 0 => panic!("Name cannot be empty"), + 1 => {} + 2 => { + bytes.push(DUALNAMEPREFIX); + } + n => { + bytes.push(MULTINAMEPREFIX); + bytes.push(n as u8); + } + }; + + for part in self.name_parts.clone().iter_mut() { + bytes.append(&mut part.to_vec()); + } + } +} + +impl Path { + /// Per ACPI Spec, the Namestring split by "." has 4 bytes long. So any name + /// not has 4 bytes will not be accepted. + pub fn new(name: &str) -> Self { + let root = name.starts_with('\\'); + let offset = root as usize; + let mut name_parts = Vec::new(); + for part in name[offset..].split('.') { + assert_eq!(part.len(), 4); + let mut name_part = [0u8; 4]; + name_part.copy_from_slice(part.as_bytes()); + name_parts.push(name_part); + } + + Path { root, name_parts } + } +} + +impl From<&str> for Path { + fn from(s: &str) -> Self { + Path::new(s) + } +} + +pub type Byte = u8; + +impl Aml for Byte { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(BYTEPREFIX); + bytes.push(*self); + } +} + +pub type Word = u16; + +impl Aml for Word { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(WORDPREFIX); + bytes.append(&mut self.to_le_bytes().to_vec()); + } +} + +pub type DWord = u32; + +impl Aml for DWord { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(DWORDPREFIX); + bytes.append(&mut self.to_le_bytes().to_vec()); + } +} + +pub type QWord = u64; + +impl Aml for QWord { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(QWORDPREFIX); + bytes.append(&mut self.to_le_bytes().to_vec()); + } +} + +/// Name object. bytes represents the raw AML data for it. +pub struct Name { + bytes: Vec<u8>, +} + +impl Aml for Name { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut self.bytes.clone()); + } +} + +impl Name { + /// Create Name object: + /// + /// * `path` - The namestring. + /// * `inner` - AML objects contained in this namespace. + pub fn new(path: Path, inner: &dyn Aml) -> Self { + let mut bytes = Vec::new(); + bytes.push(NAMEOP); + path.to_aml_bytes(&mut bytes); + inner.to_aml_bytes(&mut bytes); + Name { bytes } + } +} + +/// Package object. 'children' represents the ACPI objects contained in this package. +pub struct Package<'a> { + children: Vec<&'a dyn Aml>, +} + +impl<'a> Aml for Package<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + bytes.push(self.children.len() as u8); + for child in &self.children { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, PACKAGEOP); + + aml.append(&mut bytes); + } +} + +impl<'a> Package<'a> { + /// Create Package object: + pub fn new(children: Vec<&'a dyn Aml>) -> Self { + Package { children } + } +} + +/* + +From the ACPI spec for PkgLength: + +"The high 2 bits of the first byte reveal how many follow bytes are in the PkgLength. If the +PkgLength has only one byte, bit 0 through 5 are used to encode the package length (in other +words, values 0-63). If the package length value is more than 63, more than one byte must be +used for the encoding in which case bit 4 and 5 of the PkgLeadByte are reserved and must be zero. +If the multiple bytes encoding is used, bits 0-3 of the PkgLeadByte become the least significant 4 +bits of the resulting package length value. The next ByteData will become the next least +significant 8 bits of the resulting value and so on, up to 3 ByteData bytes. Thus, the maximum +package length is 2**28." + +*/ + +/* Also used for NamedField but in that case the length is not included in itself */ +fn create_pkg_length(data: &[u8], include_self: bool) -> Vec<u8> { + let mut result = Vec::new(); + + /* PkgLength is inclusive and includes the length bytes */ + let length_length = if data.len() < (2usize.pow(6) - 1) { + 1 + } else if data.len() < (2usize.pow(12) - 2) { + 2 + } else if data.len() < (2usize.pow(20) - 3) { + 3 + } else { + 4 + }; + + let length = data.len() + if include_self { length_length } else { 0 }; + + match length_length { + 1 => result.push(length as u8), + 2 => { + result.push((1u8 << 6) | (length & 0xf) as u8); + result.push((length >> 4) as u8) + } + 3 => { + result.push((2u8 << 6) | (length & 0xf) as u8); + result.push((length >> 4) as u8); + result.push((length >> 12) as u8); + } + _ => { + result.push((3u8 << 6) | (length & 0xf) as u8); + result.push((length >> 4) as u8); + result.push((length >> 12) as u8); + result.push((length >> 20) as u8); + } + } + + result +} + +/// EISAName object. 'value' means the encoded u32 EisaIdString. +pub struct EISAName { + value: DWord, +} + +impl EISAName { + /// Per ACPI Spec, the EisaIdString must be a String + /// object of the form UUUNNNN, where U is an uppercase letter + /// and N is a hexadecimal digit. No asterisks or other characters + /// are allowed in the string. + pub fn new(name: &str) -> Self { + assert_eq!(name.len(), 7); + + let data = name.as_bytes(); + + let value: u32 = (u32::from(data[0].checked_sub(NAMECHARBASE).unwrap()) << 26 + | u32::from(data[1].checked_sub(NAMECHARBASE).unwrap()) << 21 + | u32::from(data[2].checked_sub(NAMECHARBASE).unwrap()) << 16 + | name.chars().nth(3).unwrap().to_digit(16).unwrap() << 12 + | name.chars().nth(4).unwrap().to_digit(16).unwrap() << 8 + | name.chars().nth(5).unwrap().to_digit(16).unwrap() << 4 + | name.chars().nth(6).unwrap().to_digit(16).unwrap()) + .swap_bytes(); + + EISAName { value } + } +} + +impl Aml for EISAName { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + self.value.to_aml_bytes(bytes); + } +} + +fn create_integer(v: usize, bytes: &mut Vec<u8>) { + if v <= u8::max_value().into() { + (v as u8).to_aml_bytes(bytes); + } else if v <= u16::max_value().into() { + (v as u16).to_aml_bytes(bytes); + } else if v <= u32::max_value() as usize { + (v as u32).to_aml_bytes(bytes); + } else { + (v as u64).to_aml_bytes(bytes); + } +} + +pub type Usize = usize; + +impl Aml for Usize { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + create_integer(*self, bytes); + } +} + +fn create_aml_string(v: &str) -> Vec<u8> { + let mut data = Vec::new(); + data.push(STRINGOP); + data.extend_from_slice(v.as_bytes()); + data.push(0x0); /* NullChar */ + data +} + +/// implement Aml trait for 'str' so that 'str' can be directly append to the aml vector +pub type AmlStr = &'static str; + +impl Aml for AmlStr { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut create_aml_string(self)); + } +} + +/// implement Aml trait for 'String'. So purpose with str. +pub type AmlString = String; + +impl Aml for AmlString { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.append(&mut create_aml_string(self)); + } +} + +/// ResouceTemplate object. 'children' represents the ACPI objects in it. +pub struct ResourceTemplate<'a> { + children: Vec<&'a dyn Aml>, +} + +impl<'a> Aml for ResourceTemplate<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + + // Add buffer data + for child in &self.children { + child.to_aml_bytes(&mut bytes); + } + + // Mark with end and mark checksum as as always valid + bytes.push(ENDTAG); + bytes.push(0); /* zero checksum byte */ + + // Buffer length is an encoded integer including buffer data + // and EndTag and checksum byte + let mut buffer_length = Vec::new(); + bytes.len().to_aml_bytes(&mut buffer_length); + buffer_length.reverse(); + for byte in buffer_length { + bytes.insert(0, byte); + } + + // PkgLength is everything else + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, BUFFEROP); + + aml.append(&mut bytes); + } +} + +impl<'a> ResourceTemplate<'a> { + /// Create ResouceTemplate object + pub fn new(children: Vec<&'a dyn Aml>) -> Self { + ResourceTemplate { children } + } +} + +/// Memory32Fixed object with read_write accessing type, and the base address/length. +pub struct Memory32Fixed { + read_write: bool, /* true for read & write, false for read only */ + base: u32, + length: u32, +} + +impl Memory32Fixed { + /// Create Memory32Fixed object. + pub fn new(read_write: bool, base: u32, length: u32) -> Self { + Memory32Fixed { + read_write, + base, + length, + } + } +} + +impl Aml for Memory32Fixed { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(MEMORY32FIXEDDESC); /* 32bit Fixed Memory Range Descriptor */ + bytes.append(&mut 9u16.to_le_bytes().to_vec()); + + // 9 bytes of payload + bytes.push(self.read_write as u8); + bytes.append(&mut self.base.to_le_bytes().to_vec()); + bytes.append(&mut self.length.to_le_bytes().to_vec()); + } +} + +#[derive(Copy, Clone)] +enum AddressSpaceType { + Memory, + IO, + BusNumber, +} + +/// AddressSpaceCachable represent cache types for AddressSpace object +#[derive(Copy, Clone)] +pub enum AddressSpaceCachable { + NotCacheable, + Cacheable, + WriteCombining, + PreFetchable, +} + +/// AddressSpace structure with type, resouce range and flags to +/// construct Memory/IO/BusNumber objects +pub struct AddressSpace<T> { + type_: AddressSpaceType, + min: T, + max: T, + type_flags: u8, +} + +impl<T> AddressSpace<T> { + /// Create DWordMemory/QWordMemory object + pub fn new_memory(cacheable: AddressSpaceCachable, read_write: bool, min: T, max: T) -> Self { + AddressSpace { + type_: AddressSpaceType::Memory, + min, + max, + type_flags: (cacheable as u8) << 1 | read_write as u8, + } + } + + /// Create WordIO/DWordIO/QWordIO object + pub fn new_io(min: T, max: T) -> Self { + AddressSpace { + type_: AddressSpaceType::IO, + min, + max, + type_flags: 3, /* EntireRange */ + } + } + + /// Create WordBusNumber object + pub fn new_bus_number(min: T, max: T) -> Self { + AddressSpace { + type_: AddressSpaceType::BusNumber, + min, + max, + type_flags: 0, + } + } + + fn push_header(&self, bytes: &mut Vec<u8>, descriptor: u8, length: usize) { + bytes.push(descriptor); /* Word Address Space Descriptor */ + bytes.append(&mut (length as u16).to_le_bytes().to_vec()); + bytes.push(self.type_ as u8); /* type */ + let generic_flags = 1 << 2 /* Min Fixed */ | 1 << 3; /* Max Fixed */ + bytes.push(generic_flags); + bytes.push(self.type_flags); + } +} + +impl Aml for AddressSpace<u16> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + self.push_header( + bytes, + WORDADDRSPACEDESC, /* Word Address Space Descriptor */ + 3 + 5 * std::mem::size_of::<u16>(), /* 3 bytes of header + 5 u16 fields */ + ); + + bytes.append(&mut 0u16.to_le_bytes().to_vec()); /* Granularity */ + bytes.append(&mut self.min.to_le_bytes().to_vec()); /* Min */ + bytes.append(&mut self.max.to_le_bytes().to_vec()); /* Max */ + bytes.append(&mut 0u16.to_le_bytes().to_vec()); /* Translation */ + let len = self.max - self.min + 1; + bytes.append(&mut len.to_le_bytes().to_vec()); /* Length */ + } +} + +impl Aml for AddressSpace<u32> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + self.push_header( + bytes, + DWORDADDRSPACEDESC, /* DWord Address Space Descriptor */ + 3 + 5 * std::mem::size_of::<u32>(), /* 3 bytes of header + 5 u32 fields */ + ); + + bytes.append(&mut 0u32.to_le_bytes().to_vec()); /* Granularity */ + bytes.append(&mut self.min.to_le_bytes().to_vec()); /* Min */ + bytes.append(&mut self.max.to_le_bytes().to_vec()); /* Max */ + bytes.append(&mut 0u32.to_le_bytes().to_vec()); /* Translation */ + let len = self.max - self.min + 1; + bytes.append(&mut len.to_le_bytes().to_vec()); /* Length */ + } +} + +impl Aml for AddressSpace<u64> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + self.push_header( + bytes, + QWORDADDRSPACEDESC, /* QWord Address Space Descriptor */ + 3 + 5 * std::mem::size_of::<u64>(), /* 3 bytes of header + 5 u64 fields */ + ); + + bytes.append(&mut 0u64.to_le_bytes().to_vec()); /* Granularity */ + bytes.append(&mut self.min.to_le_bytes().to_vec()); /* Min */ + bytes.append(&mut self.max.to_le_bytes().to_vec()); /* Max */ + bytes.append(&mut 0u64.to_le_bytes().to_vec()); /* Translation */ + let len = self.max - self.min + 1; + bytes.append(&mut len.to_le_bytes().to_vec()); /* Length */ + } +} + +/// IO resouce object with the IO range, alignment and length +pub struct IO { + min: u16, + max: u16, + alignment: u8, + length: u8, +} + +impl IO { + /// Create IO object + pub fn new(min: u16, max: u16, alignment: u8, length: u8) -> Self { + IO { + min, + max, + alignment, + length, + } + } +} + +impl Aml for IO { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(IOPORTDESC); /* IO Port Descriptor */ + bytes.push(1); /* IODecode16 */ + bytes.append(&mut self.min.to_le_bytes().to_vec()); + bytes.append(&mut self.max.to_le_bytes().to_vec()); + bytes.push(self.alignment); + bytes.push(self.length); + } +} + +/// Interrupt resouce object with the interrupt characters. +pub struct Interrupt { + consumer: bool, + edge_triggered: bool, + active_low: bool, + shared: bool, + number: u32, +} + +impl Interrupt { + /// Create Interrupt object + pub fn new( + consumer: bool, + edge_triggered: bool, + active_low: bool, + shared: bool, + number: u32, + ) -> Self { + Interrupt { + consumer, + edge_triggered, + active_low, + shared, + number, + } + } +} + +impl Aml for Interrupt { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(EXTIRQDESC); /* Extended IRQ Descriptor */ + bytes.append(&mut 6u16.to_le_bytes().to_vec()); + let flags = (self.shared as u8) << 3 + | (self.active_low as u8) << 2 + | (self.edge_triggered as u8) << 1 + | self.consumer as u8; + bytes.push(flags); + bytes.push(1u8); /* count */ + bytes.append(&mut self.number.to_le_bytes().to_vec()); + } +} + +/// Device object with its device name and children objects in it. +pub struct Device<'a> { + path: Path, + children: Vec<&'a dyn Aml>, +} + +impl<'a> Aml for Device<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.path.to_aml_bytes(&mut bytes); + for child in &self.children { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, DEVICEOP); /* DeviceOp */ + bytes.insert(0, EXTOPPREFIX); /* ExtOpPrefix */ + aml.append(&mut bytes) + } +} + +impl<'a> Device<'a> { + /// Create Device object + pub fn new(path: Path, children: Vec<&'a dyn Aml>) -> Self { + Device { path, children } + } +} + +/// Scope object with its name and children objects in it. +pub struct Scope<'a> { + path: Path, + children: Vec<&'a dyn Aml>, +} + +impl<'a> Aml for Scope<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.path.to_aml_bytes(&mut bytes); + for child in &self.children { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, SCOPEOP); + aml.append(&mut bytes) + } +} + +impl<'a> Scope<'a> { + /// Create Scope object + pub fn new(path: Path, children: Vec<&'a dyn Aml>) -> Self { + Scope { path, children } + } +} + +/// Method object with its name, children objects, arguments and serialized character. +pub struct Method<'a> { + path: Path, + children: Vec<&'a dyn Aml>, + args: u8, + serialized: bool, +} + +impl<'a> Method<'a> { + /// Create Method object. + pub fn new(path: Path, args: u8, serialized: bool, children: Vec<&'a dyn Aml>) -> Self { + Method { + path, + children, + args, + serialized, + } + } +} + +impl<'a> Aml for Method<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.path.to_aml_bytes(&mut bytes); + let flags: u8 = (self.args & 0x7) | (self.serialized as u8) << 3; + bytes.push(flags); + for child in &self.children { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, METHODOP); + aml.append(&mut bytes) + } +} + +/// Return object with its return value. +pub struct Return<'a> { + value: &'a dyn Aml, +} + +impl<'a> Return<'a> { + /// Create Return object + pub fn new(value: &'a dyn Aml) -> Self { + Return { value } + } +} + +impl<'a> Aml for Return<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(RETURNOP); + self.value.to_aml_bytes(bytes); + } +} + +/// FiledAccessType defines the filed accessing types. +#[derive(Clone, Copy)] +pub enum FieldAccessType { + Any, + Byte, + Word, + DWord, + QWord, + Buffer, +} + +/// FiledUpdateRule defines the rules to update the filed. +#[derive(Clone, Copy)] +pub enum FieldUpdateRule { + Preserve = 0, + WriteAsOnes = 1, + WriteAsZeroes = 2, +} + +/// FiledEntry defines the filed entry. +pub enum FieldEntry { + Named([u8; 4], usize), + Reserved(usize), +} + +/// Field object with the region name, filed entries, access type and update rules. +pub struct Field { + path: Path, + + fields: Vec<FieldEntry>, + access_type: FieldAccessType, + update_rule: FieldUpdateRule, +} + +impl Field { + /// Create Field object + pub fn new( + path: Path, + access_type: FieldAccessType, + update_rule: FieldUpdateRule, + fields: Vec<FieldEntry>, + ) -> Self { + Field { + path, + access_type, + update_rule, + fields, + } + } +} + +impl Aml for Field { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.path.to_aml_bytes(&mut bytes); + + let flags: u8 = self.access_type as u8 | (self.update_rule as u8) << 5; + bytes.push(flags); + + for field in self.fields.iter() { + match field { + FieldEntry::Named(name, length) => { + bytes.extend_from_slice(name); + bytes.append(&mut create_pkg_length(&vec![0; *length], false)); + } + FieldEntry::Reserved(length) => { + bytes.push(0x0); + bytes.append(&mut create_pkg_length(&vec![0; *length], false)); + } + } + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, FIELDOP); + bytes.insert(0, EXTOPPREFIX); + aml.append(&mut bytes) + } +} + +/// The space type for OperationRegion object +#[derive(Clone, Copy)] +pub enum OpRegionSpace { + SystemMemory, + SystemIO, + PCIConfig, + EmbeddedControl, + SMBus, + SystemCMOS, + PciBarTarget, + IPMI, + GeneralPurposeIO, + GenericSerialBus, +} + +/// OperationRegion object with region name, region space type, its offset and length. +pub struct OpRegion { + path: Path, + space: OpRegionSpace, + offset: usize, + length: usize, +} + +impl OpRegion { + /// Create OperationRegion object. + pub fn new(path: Path, space: OpRegionSpace, offset: usize, length: usize) -> Self { + OpRegion { + path, + space, + offset, + length, + } + } +} + +impl Aml for OpRegion { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.path.to_aml_bytes(&mut bytes); + bytes.push(self.space as u8); + self.offset.to_aml_bytes(&mut bytes); /* RegionOffset */ + self.length.to_aml_bytes(&mut bytes); /* RegionLen */ + bytes.insert(0, OPREGIONOP); + bytes.insert(0, EXTOPPREFIX); + aml.append(&mut bytes) + } +} + +/// If object with the if condition(predicate) and the body presented by the if_children objects. +pub struct If<'a> { + predicate: &'a dyn Aml, + if_children: Vec<&'a dyn Aml>, +} + +impl<'a> If<'a> { + /// Create If object. + pub fn new(predicate: &'a dyn Aml, if_children: Vec<&'a dyn Aml>) -> Self { + If { + predicate, + if_children, + } + } +} + +impl<'a> Aml for If<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.predicate.to_aml_bytes(&mut bytes); + for child in self.if_children.iter() { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, IFOP); + aml.append(&mut bytes) + } +} + +/// Equal object with its right part and left part, which are both ACPI objects. +pub struct Equal<'a> { + right: &'a dyn Aml, + left: &'a dyn Aml, +} + +impl<'a> Equal<'a> { + /// Create Equal object. + pub fn new(left: &'a dyn Aml, right: &'a dyn Aml) -> Self { + Equal { left, right } + } +} + +impl<'a> Aml for Equal<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(LEQUALOP); + self.left.to_aml_bytes(bytes); + self.right.to_aml_bytes(bytes); + } +} + +/// LessThan object with its right part and left part, which are both ACPI objects. +pub struct LessThan<'a> { + right: &'a dyn Aml, + left: &'a dyn Aml, +} + +impl<'a> LessThan<'a> { + /// Create LessThan object. + pub fn new(left: &'a dyn Aml, right: &'a dyn Aml) -> Self { + LessThan { left, right } + } +} + +impl<'a> Aml for LessThan<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(LLESSOP); + self.left.to_aml_bytes(bytes); + self.right.to_aml_bytes(bytes); + } +} + +/// Argx object. +pub struct Arg(pub u8); + +impl Aml for Arg { + /// Per ACPI spec, there is maximum 7 Argx objects from + /// Arg0 ~ Arg6. Any other Arg object will not be accepted. + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + assert!(self.0 <= 6); + bytes.push(ARG0OP + self.0); + } +} + +/// Localx object. +pub struct Local(pub u8); + +impl Aml for Local { + /// Per ACPI spec, there is maximum 8 Localx objects from + /// Local0 ~ Local7. Any other Local object will not be accepted. + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + assert!(self.0 <= 7); + bytes.push(LOCAL0OP + self.0); + } +} + +/// Store object with the ACPI object name which can be stored to and +/// the ACPI object value which is to store. +pub struct Store<'a> { + name: &'a dyn Aml, + value: &'a dyn Aml, +} + +impl<'a> Store<'a> { + /// Create Store object. + pub fn new(name: &'a dyn Aml, value: &'a dyn Aml) -> Self { + Store { name, value } + } +} + +impl<'a> Aml for Store<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(STOREOP); + self.value.to_aml_bytes(bytes); + self.name.to_aml_bytes(bytes); + } +} + +/// Mutex object with a mutex name and a synchronization level. +pub struct Mutex { + path: Path, + sync_level: u8, +} + +impl Mutex { + /// Create Mutex object. + pub fn new(path: Path, sync_level: u8) -> Self { + Self { path, sync_level } + } +} + +impl Aml for Mutex { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(EXTOPPREFIX); + bytes.push(MUTEXOP); + self.path.to_aml_bytes(bytes); + bytes.push(self.sync_level); + } +} + +/// Acquire object with a Mutex object and timeout value. +pub struct Acquire { + mutex: Path, + timeout: u16, +} + +impl Acquire { + /// Create Acquire object. + pub fn new(mutex: Path, timeout: u16) -> Self { + Acquire { mutex, timeout } + } +} + +impl Aml for Acquire { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(EXTOPPREFIX); + bytes.push(ACQUIREOP); + self.mutex.to_aml_bytes(bytes); + bytes.extend_from_slice(&self.timeout.to_le_bytes()); + } +} + +/// Release object with a Mutex object to release. +pub struct Release { + mutex: Path, +} + +impl Release { + /// Create Release object. + pub fn new(mutex: Path) -> Self { + Release { mutex } + } +} + +impl Aml for Release { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(EXTOPPREFIX); + bytes.push(RELEASEOP); + self.mutex.to_aml_bytes(bytes); + } +} + +/// Notify object with an object which is to be notified with the value. +pub struct Notify<'a> { + object: &'a dyn Aml, + value: &'a dyn Aml, +} + +impl<'a> Notify<'a> { + /// Create Notify object. + pub fn new(object: &'a dyn Aml, value: &'a dyn Aml) -> Self { + Notify { object, value } + } +} + +impl<'a> Aml for Notify<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push(NOTIFYOP); + self.object.to_aml_bytes(bytes); + self.value.to_aml_bytes(bytes); + } +} + +/// While object with the while condition objects(predicate) and +/// the while body objects(while_children). +pub struct While<'a> { + predicate: &'a dyn Aml, + while_children: Vec<&'a dyn Aml>, +} + +impl<'a> While<'a> { + /// Create While object. + pub fn new(predicate: &'a dyn Aml, while_children: Vec<&'a dyn Aml>) -> Self { + While { + predicate, + while_children, + } + } +} + +impl<'a> Aml for While<'a> { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.predicate.to_aml_bytes(&mut bytes); + for child in self.while_children.iter() { + child.to_aml_bytes(&mut bytes); + } + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, WHILEOP); + aml.append(&mut bytes) + } +} + +macro_rules! binary_op { + ($name:ident, $opcode:expr) => { + /// General operation object with the operator a/b and a target. + pub struct $name<'a> { + a: &'a dyn Aml, + b: &'a dyn Aml, + target: &'a dyn Aml, + } + + impl<'a> $name<'a> { + /// Create the object. + pub fn new(target: &'a dyn Aml, a: &'a dyn Aml, b: &'a dyn Aml) -> Self { + $name { target, a, b } + } + } + + impl<'a> Aml for $name<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + bytes.push($opcode); /* Op for the binary operator */ + self.a.to_aml_bytes(bytes); + self.b.to_aml_bytes(bytes); + self.target.to_aml_bytes(bytes); + } + } + }; +} + +binary_op!(Add, ADDOP); +binary_op!(Concat, CONCATOP); +binary_op!(Subtract, SUBTRACTOP); +binary_op!(Multiply, MULTIPLYOP); +binary_op!(ShiftLeft, SHIFTLEFTOP); +binary_op!(ShiftRight, SHIFTRIGHTOP); +binary_op!(And, ANDOP); +binary_op!(Nand, NANDOP); +binary_op!(Or, OROP); +binary_op!(Nor, NOROP); +binary_op!(Xor, XOROP); +binary_op!(ConcatRes, CONCATRESOP); +binary_op!(Mod, MODOP); +binary_op!(Index, INDEXOP); +binary_op!(ToString, TOSTRINGOP); + +/// MethodCall object with the method name and parameter objects. +pub struct MethodCall<'a> { + name: Path, + args: Vec<&'a dyn Aml>, +} + +impl<'a> MethodCall<'a> { + /// Create MethodCall object. + pub fn new(name: Path, args: Vec<&'a dyn Aml>) -> Self { + MethodCall { name, args } + } +} + +impl<'a> Aml for MethodCall<'a> { + fn to_aml_bytes(&self, bytes: &mut Vec<u8>) { + self.name.to_aml_bytes(bytes); + for arg in self.args.iter() { + arg.to_aml_bytes(bytes); + } + } +} + +/// Buffer object with the data in it. +pub struct Buffer { + data: Vec<u8>, +} + +impl Buffer { + /// Create Buffer object. + pub fn new(data: Vec<u8>) -> Self { + Buffer { data } + } +} + +impl Aml for Buffer { + fn to_aml_bytes(&self, aml: &mut Vec<u8>) { + let mut bytes = Vec::new(); + self.data.len().to_aml_bytes(&mut bytes); + bytes.extend_from_slice(&self.data); + + let mut pkg_length = create_pkg_length(&bytes, true); + pkg_length.reverse(); + for byte in pkg_length { + bytes.insert(0, byte); + } + + bytes.insert(0, BUFFEROP); + + aml.append(&mut bytes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_device() { + /* + Device (_SB.COM1) + { + Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID + Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings + { + Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, ) + { + 0x00000004, + } + IO (Decode16, + 0x03F8, // Range Minimum + 0x03F8, // Range Maximum + 0x00, // Alignment + 0x08, // Length + ) + } + } + */ + let com1_device = [ + 0x5B, 0x82, 0x30, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, + 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, + 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00, 0x00, 0x47, 0x01, + 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00, + ]; + let mut aml = Vec::new(); + + Device::new( + "_SB_.COM1".into(), + vec![ + &Name::new("_HID".into(), &EISAName::new("PNP0501")), + &Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![ + &Interrupt::new(true, true, false, false, 4), + &IO::new(0x3f8, 0x3f8, 0, 0x8), + ]), + ), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &com1_device[..]); + } + + #[test] + fn test_scope() { + /* + Scope (_SB.MBRD) + { + Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings + { + Memory32Fixed (ReadWrite, + 0xE8000000, // Address Base + 0x10000000, // Address Length + ) + }) + } + */ + + let mbrd_scope = [ + 0x10, 0x21, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x43, + 0x52, 0x53, 0x11, 0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, + 0x00, 0x00, 0x00, 0x10, 0x79, 0x00, + ]; + let mut aml = Vec::new(); + + Scope::new( + "_SB_.MBRD".into(), + vec![&Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![&Memory32Fixed::new(true, 0xE800_0000, 0x1000_0000)]), + )], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &mbrd_scope[..]); + } + + #[test] + fn test_resource_template() { + /* + Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings + { + Memory32Fixed (ReadWrite, + 0xE8000000, // Address Base + 0x10000000, // Address Length + ) + }) + */ + let crs_memory_32_fixed = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x10, 0x79, 0x00, + ]; + let mut aml = Vec::new(); + + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![&Memory32Fixed::new(true, 0xE800_0000, 0x1000_0000)]), + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, crs_memory_32_fixed); + + /* + Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings + { + WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode, + 0x0000, // Granularity + 0x0000, // Range Minimum + 0x00FF, // Range Maximum + 0x0000, // Translation Offset + 0x0100, // Length + ,, ) + WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, // Granularity + 0x0000, // Range Minimum + 0x0CF7, // Range Maximum + 0x0000, // Translation Offset + 0x0CF8, // Length + ,, , TypeStatic, DenseTranslation) + WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, // Granularity + 0x0D00, // Range Minimum + 0xFFFF, // Range Maximum + 0x0000, // Translation Offset + 0xF300, // Length + ,, , TypeStatic, DenseTranslation) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite, + 0x00000000, // Granularity + 0x000A0000, // Range Minimum + 0x000BFFFF, // Range Maximum + 0x00000000, // Translation Offset + 0x00020000, // Length + ,, , AddressRangeMemory, TypeStatic) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite, + 0x00000000, // Granularity + 0xC0000000, // Range Minimum + 0xFEBFFFFF, // Range Maximum + 0x00000000, // Translation Offset + 0x3EC00000, // Length + ,, , AddressRangeMemory, TypeStatic) + QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite, + 0x0000000000000000, // Granularity + 0x0000000800000000, // Range Minimum + 0x0000000FFFFFFFFF, // Range Maximum + 0x0000000000000000, // Translation Offset + 0x0000000800000000, // Length + ,, , AddressRangeMemory, TypeStatic) + }) + */ + + // WordBusNumber from above + let crs_word_bus_number = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x15, 0x0A, 0x12, 0x88, 0x0D, 0x00, 0x02, 0x0C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x79, 0x00, + ]; + aml.clear(); + + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![&AddressSpace::new_bus_number(0x0u16, 0xffu16)]), + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &crs_word_bus_number); + + // WordIO blocks (x 2) from above + let crs_word_io = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x25, 0x0A, 0x22, 0x88, 0x0D, 0x00, 0x01, 0x0C, + 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D, 0x00, + 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x79, + 0x00, + ]; + aml.clear(); + + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![ + &AddressSpace::new_io(0x0u16, 0xcf7u16), + &AddressSpace::new_io(0xd00u16, 0xffffu16), + ]), + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &crs_word_io[..]); + + // DWordMemory blocks (x 2) from above + let crs_dword_memory = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x39, 0x0A, 0x36, 0x87, 0x17, 0x00, 0x00, 0x0C, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF, 0x0B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xBF, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xC0, 0x3E, 0x79, 0x00, + ]; + aml.clear(); + + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![ + &AddressSpace::new_memory( + AddressSpaceCachable::Cacheable, + true, + 0xa_0000u32, + 0xb_ffffu32, + ), + &AddressSpace::new_memory( + AddressSpaceCachable::NotCacheable, + true, + 0xc000_0000u32, + 0xfebf_ffffu32, + ), + ]), + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &crs_dword_memory[..]); + + // QWordMemory from above + let crs_qword_memory = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x33, 0x0A, 0x30, 0x8A, 0x2B, 0x00, 0x00, 0x0C, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, + 0x00, + ]; + aml.clear(); + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![&AddressSpace::new_memory( + AddressSpaceCachable::Cacheable, + true, + 0x8_0000_0000u64, + 0xf_ffff_ffffu64, + )]), + ) + .to_aml_bytes(&mut aml); + + assert_eq!(aml, &crs_qword_memory[..]); + + /* + Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings + { + Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, ) + { + 0x00000004, + } + IO (Decode16, + 0x03F8, // Range Minimum + 0x03F8, // Range Maximum + 0x00, // Alignment + 0x08, // Length + ) + }) + + */ + let interrupt_io_data = [ + 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, + 0x04, 0x00, 0x00, 0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00, + ]; + aml.clear(); + Name::new( + "_CRS".into(), + &ResourceTemplate::new(vec![ + &Interrupt::new(true, true, false, false, 4), + &IO::new(0x3f8, 0x3f8, 0, 0x8), + ]), + ) + .to_aml_bytes(&mut aml); + + assert_eq!(aml, &interrupt_io_data[..]); + } + + #[test] + fn test_pkg_length() { + assert_eq!(create_pkg_length(&[0u8; 62].to_vec(), true), vec![63]); + assert_eq!( + create_pkg_length(&[0u8; 64].to_vec(), true), + vec![1 << 6 | (66 & 0xf), 66 >> 4] + ); + assert_eq!( + create_pkg_length(&[0u8; 4096].to_vec(), true), + vec![ + 2 << 6 | (4099 & 0xf) as u8, + (4099 >> 4) as u8, + (4099 >> 12) as u8 + ] + ); + } + + #[test] + fn test_package() { + /* + Name (_S5, Package (0x01) // _S5_: S5 System State + { + 0x05 + }) + */ + let s5_sleep_data = [0x08, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05]; + let mut aml = Vec::new(); + + Name::new("_S5_".into(), &Package::new(vec![&5u8])).to_aml_bytes(&mut aml); + + assert_eq!(s5_sleep_data.to_vec(), aml); + } + + #[test] + fn test_eisa_name() { + let mut aml = Vec::new(); + Name::new("_HID".into(), &EISAName::new("PNP0501")).to_aml_bytes(&mut aml); + assert_eq!( + aml, + [0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01], + ) + } + #[test] + fn test_name_path() { + let mut aml = Vec::new(); + (&"_SB_".into() as &Path).to_aml_bytes(&mut aml); + assert_eq!(aml, [0x5Fu8, 0x53, 0x42, 0x5F]); + aml.clear(); + (&"\\_SB_".into() as &Path).to_aml_bytes(&mut aml); + assert_eq!(aml, [0x5C, 0x5F, 0x53, 0x42, 0x5F]); + aml.clear(); + (&"_SB_.COM1".into() as &Path).to_aml_bytes(&mut aml); + assert_eq!(aml, [0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31]); + aml.clear(); + (&"_SB_.PCI0._HID".into() as &Path).to_aml_bytes(&mut aml); + assert_eq!( + aml, + [0x2F, 0x03, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x5F, 0x48, 0x49, 0x44] + ); + } + + #[test] + fn test_numbers() { + let mut aml = Vec::new(); + 128u8.to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0a, 0x80]); + aml.clear(); + 1024u16.to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0b, 0x0, 0x04]); + aml.clear(); + (16u32 << 20).to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0c, 0x00, 0x00, 0x0, 0x01]); + aml.clear(); + 0xdeca_fbad_deca_fbadu64.to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0e, 0xad, 0xfb, 0xca, 0xde, 0xad, 0xfb, 0xca, 0xde]); + } + + #[test] + fn test_name() { + let mut aml = Vec::new(); + Name::new("_SB_.PCI0._UID".into(), &0x1234u16).to_aml_bytes(&mut aml); + assert_eq!( + aml, + [ + 0x08, /* NameOp */ + 0x2F, /* MultiNamePrefix */ + 0x03, /* 3 name parts */ + 0x5F, 0x53, 0x42, 0x5F, /* _SB_ */ + 0x50, 0x43, 0x49, 0x30, /* PCI0 */ + 0x5F, 0x55, 0x49, 0x44, /* _UID */ + 0x0b, /* WordPrefix */ + 0x34, 0x12 + ] + ); + } + + #[test] + fn test_string() { + let mut aml = Vec::new(); + (&"ACPI" as &dyn Aml).to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0d, b'A', b'C', b'P', b'I', 0]); + aml.clear(); + "ACPI".to_owned().to_aml_bytes(&mut aml); + assert_eq!(aml, [0x0d, b'A', b'C', b'P', b'I', 0]); + } + + #[test] + fn test_method() { + let mut aml = Vec::new(); + Method::new("_STA".into(), 0, false, vec![&Return::new(&0xfu8)]).to_aml_bytes(&mut aml); + assert_eq!( + aml, + [0x14, 0x09, 0x5F, 0x53, 0x54, 0x41, 0x00, 0xA4, 0x0A, 0x0F] + ); + } + + #[test] + fn test_field() { + /* + Field (PRST, ByteAcc, NoLock, WriteAsZeros) + { + Offset (0x04), + CPEN, 1, + CINS, 1, + CRMV, 1, + CEJ0, 1, + Offset (0x05), + CCMD, 8 + } + + */ + + let field_data = [ + 0x5Bu8, 0x81, 0x23, 0x50, 0x52, 0x53, 0x54, 0x41, 0x00, 0x20, 0x43, 0x50, 0x45, 0x4E, + 0x01, 0x43, 0x49, 0x4E, 0x53, 0x01, 0x43, 0x52, 0x4D, 0x56, 0x01, 0x43, 0x45, 0x4A, + 0x30, 0x01, 0x00, 0x04, 0x43, 0x43, 0x4D, 0x44, 0x08, + ]; + let mut aml = Vec::new(); + + Field::new( + "PRST".into(), + FieldAccessType::Byte, + FieldUpdateRule::WriteAsZeroes, + vec![ + FieldEntry::Reserved(32), + FieldEntry::Named(*b"CPEN", 1), + FieldEntry::Named(*b"CINS", 1), + FieldEntry::Named(*b"CRMV", 1), + FieldEntry::Named(*b"CEJ0", 1), + FieldEntry::Reserved(4), + FieldEntry::Named(*b"CCMD", 8), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &field_data[..]); + + /* + Field (PRST, DWordAcc, NoLock, Preserve) + { + CSEL, 32, + Offset (0x08), + CDAT, 32 + } + */ + + let field_data = [ + 0x5Bu8, 0x81, 0x12, 0x50, 0x52, 0x53, 0x54, 0x03, 0x43, 0x53, 0x45, 0x4C, 0x20, 0x00, + 0x20, 0x43, 0x44, 0x41, 0x54, 0x20, + ]; + aml.clear(); + + Field::new( + "PRST".into(), + FieldAccessType::DWord, + FieldUpdateRule::Preserve, + vec![ + FieldEntry::Named(*b"CSEL", 32), + FieldEntry::Reserved(32), + FieldEntry::Named(*b"CDAT", 32), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &field_data[..]); + } + + #[test] + fn test_op_region() { + /* + OperationRegion (PRST, SystemIO, 0x0CD8, 0x0C) + */ + let op_region_data = [ + 0x5Bu8, 0x80, 0x50, 0x52, 0x53, 0x54, 0x01, 0x0B, 0xD8, 0x0C, 0x0A, 0x0C, + ]; + let mut aml = Vec::new(); + + OpRegion::new("PRST".into(), OpRegionSpace::SystemIO, 0xcd8, 0xc).to_aml_bytes(&mut aml); + assert_eq!(aml, &op_region_data[..]); + } + + #[test] + fn test_arg_if() { + /* + Method(TEST, 1, NotSerialized) { + If (Arg0 == Zero) { + Return(One) + } + Return(Zero) + } + */ + let arg_if_data = [ + 0x14, 0x0F, 0x54, 0x45, 0x53, 0x54, 0x01, 0xA0, 0x06, 0x93, 0x68, 0x00, 0xA4, 0x01, + 0xA4, 0x00, + ]; + let mut aml = Vec::new(); + + Method::new( + "TEST".into(), + 1, + false, + vec![ + &If::new(&Equal::new(&Arg(0), &ZERO), vec![&Return::new(&ONE)]), + &Return::new(&ZERO), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &arg_if_data); + } + + #[test] + fn test_local_if() { + /* + Method(TEST, 0, NotSerialized) { + Local0 = One + If (Local0 == Zero) { + Return(One) + } + Return(Zero) + } + */ + let local_if_data = [ + 0x14, 0x12, 0x54, 0x45, 0x53, 0x54, 0x00, 0x70, 0x01, 0x60, 0xA0, 0x06, 0x93, 0x60, + 0x00, 0xA4, 0x01, 0xA4, 0x00, + ]; + let mut aml = Vec::new(); + + Method::new( + "TEST".into(), + 0, + false, + vec![ + &Store::new(&Local(0), &ONE), + &If::new(&Equal::new(&Local(0), &ZERO), vec![&Return::new(&ONE)]), + &Return::new(&ZERO), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &local_if_data); + } + + #[test] + fn test_mutex() { + /* + Device (_SB_.MHPC) + { + Name (_HID, EisaId("PNP0A06") /* Generic Container Device */) // _HID: Hardware ID + Mutex (MLCK, 0x00) + Method (TEST, 0, NotSerialized) + { + Acquire (MLCK, 0xFFFF) + Local0 = One + Release (MLCK) + } + } + */ + + let mutex_data = [ + 0x5B, 0x82, 0x33, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x48, 0x50, 0x43, 0x08, 0x5F, + 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x06, 0x5B, 0x01, 0x4D, 0x4C, 0x43, 0x4B, + 0x00, 0x14, 0x17, 0x54, 0x45, 0x53, 0x54, 0x00, 0x5B, 0x23, 0x4D, 0x4C, 0x43, 0x4B, + 0xFF, 0xFF, 0x70, 0x01, 0x60, 0x5B, 0x27, 0x4D, 0x4C, 0x43, 0x4B, + ]; + let mut aml = Vec::new(); + + let mutex = Mutex::new("MLCK".into(), 0); + Device::new( + "_SB_.MHPC".into(), + vec![ + &Name::new("_HID".into(), &EISAName::new("PNP0A06")), + &mutex, + &Method::new( + "TEST".into(), + 0, + false, + vec![ + &Acquire::new("MLCK".into(), 0xffff), + &Store::new(&Local(0), &ONE), + &Release::new("MLCK".into()), + ], + ), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &mutex_data[..]); + } + + #[test] + fn test_notify() { + /* + Device (_SB.MHPC) + { + Name (_HID, EisaId ("PNP0A06") /* Generic Container Device */) // _HID: Hardware ID + Method (TEST, 0, NotSerialized) + { + Notify (MHPC, One) // Device Check + } + } + */ + let notify_data = [ + 0x5B, 0x82, 0x21, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x48, 0x50, 0x43, 0x08, 0x5F, + 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x06, 0x14, 0x0C, 0x54, 0x45, 0x53, 0x54, + 0x00, 0x86, 0x4D, 0x48, 0x50, 0x43, 0x01, + ]; + let mut aml = Vec::new(); + + Device::new( + "_SB_.MHPC".into(), + vec![ + &Name::new("_HID".into(), &EISAName::new("PNP0A06")), + &Method::new( + "TEST".into(), + 0, + false, + vec![&Notify::new(&Path::new("MHPC"), &ONE)], + ), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, ¬ify_data[..]); + } + + #[test] + fn test_while() { + /* + Device (_SB.MHPC) + { + Name (_HID, EisaId ("PNP0A06") /* Generic Container Device */) // _HID: Hardware ID + Method (TEST, 0, NotSerialized) + { + Local0 = Zero + While ((Local0 < 0x04)) + { + Local0 += One + } + } + } + */ + + let while_data = [ + 0x5B, 0x82, 0x28, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x48, 0x50, 0x43, 0x08, 0x5F, + 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x06, 0x14, 0x13, 0x54, 0x45, 0x53, 0x54, + 0x00, 0x70, 0x00, 0x60, 0xA2, 0x09, 0x95, 0x60, 0x0A, 0x04, 0x72, 0x60, 0x01, 0x60, + ]; + let mut aml = Vec::new(); + + Device::new( + "_SB_.MHPC".into(), + vec![ + &Name::new("_HID".into(), &EISAName::new("PNP0A06")), + &Method::new( + "TEST".into(), + 0, + false, + vec![ + &Store::new(&Local(0), &ZERO), + &While::new( + &LessThan::new(&Local(0), &4usize), + vec![&Add::new(&Local(0), &Local(0), &ONE)], + ), + ], + ), + ], + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &while_data[..]) + } + + #[test] + fn test_method_call() { + /* + Method (TST1, 1, NotSerialized) + { + TST2 (One, One) + } + + Method (TST2, 2, NotSerialized) + { + TST1 (One) + } + */ + let test_data = [ + 0x14, 0x0C, 0x54, 0x53, 0x54, 0x31, 0x01, 0x54, 0x53, 0x54, 0x32, 0x01, 0x01, 0x14, + 0x0B, 0x54, 0x53, 0x54, 0x32, 0x02, 0x54, 0x53, 0x54, 0x31, 0x01, + ]; + + let mut methods = Vec::new(); + Method::new( + "TST1".into(), + 1, + false, + vec![&MethodCall::new("TST2".into(), vec![&ONE, &ONE])], + ) + .to_aml_bytes(&mut methods); + Method::new( + "TST2".into(), + 2, + false, + vec![&MethodCall::new("TST1".into(), vec![&ONE])], + ) + .to_aml_bytes(&mut methods); + assert_eq!(&methods[..], &test_data[..]) + } + + #[test] + fn test_buffer() { + /* + Name (_MAT, Buffer (0x08) // _MAT: Multiple APIC Table Entry + { + 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 /* ........ */ + }) + */ + let buffer_data = [ + 0x08, 0x5F, 0x4D, 0x41, 0x54, 0x11, 0x0B, 0x0A, 0x08, 0x00, 0x08, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, + ]; + let mut aml = Vec::new(); + + Name::new( + "_MAT".into(), + &Buffer::new(vec![0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]), + ) + .to_aml_bytes(&mut aml); + assert_eq!(aml, &buffer_data[..]) + } +} diff --git a/acpi_tables/src/lib.rs b/acpi_tables/src/lib.rs index 49cf760..e45aa40 100644 --- a/acpi_tables/src/lib.rs +++ b/acpi_tables/src/lib.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. +pub mod aml; pub mod rsdp; pub mod sdt; diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 62e5b25..3445f18 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -5,6 +5,7 @@ pub mod android; pub mod fdt; pub mod pstore; +pub mod serial; use std::collections::BTreeMap; use std::error::Error as StdError; @@ -18,8 +19,8 @@ use std::sync::Arc; use devices::split_irqchip_common::GsiRelay; use devices::virtio::VirtioDevice; use devices::{ - Bus, BusDevice, BusError, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, ProxyDevice, - SerialParameters, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR, + Bus, BusDevice, BusError, PciAddress, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, + ProxyDevice, }; use io_jail::Minijail; use kvm::{IoeventAddress, Kvm, Vcpu, Vm}; @@ -28,6 +29,11 @@ use sync::Mutex; use sys_util::{syslog, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; use vm_control::VmIrqRequestSocket; +pub use serial::{ + add_serial_devices, get_serial_cmdline, set_default_serial_parameters, GetSerialCmdlineError, + SerialHardware, SerialParameters, SerialType, SERIAL_ADDR, +}; + pub enum VmImage { Kernel(File), Bios(File), @@ -93,7 +99,7 @@ pub trait LinuxArch { components: VmComponents, split_irqchip: bool, ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap<u8, SerialParameters>, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option<Minijail>, create_devices: F, ) -> Result<RunnableLinuxVm, Self::Error> @@ -119,11 +125,13 @@ pub enum DeviceRegistrationError { /// Unable to create a pipe. CreatePipe(sys_util::Error), /// Unable to create serial device from serial parameters. - CreateSerialDevice(devices::SerialError), + CreateSerialDevice(serial::Error), /// Could not clone an event fd. EventFdClone(sys_util::Error), /// Could not create an event fd. EventFdCreate(sys_util::Error), + /// Missing a required serial device. + MissingRequiredSerialDevice(u8), /// Could not add a device to the mmio bus. MmioInsert(BusError), /// Failed to register ioevent with VM. @@ -155,6 +163,7 @@ impl Display for DeviceRegistrationError { 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), + MissingRequiredSerialDevice(n) => write!(f, "missing required serial device {}", n), MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e), RegisterIoevent(e) => write!(f, "failed to register ioevent to VM: {}", e), RegisterIrqfd(e) => write!(f, "failed to register irq eventfd to VM: {}", e), @@ -175,14 +184,25 @@ pub fn generate_pci_root( mmio_bus: &mut Bus, resources: &mut SystemAllocator, vm: &mut Vm, -) -> Result<(PciRoot, Vec<(u32, PciInterruptPin)>, BTreeMap<u32, String>), DeviceRegistrationError> -{ +) -> Result< + ( + PciRoot, + Vec<(PciAddress, u32, PciInterruptPin)>, + BTreeMap<u32, String>, + ), + DeviceRegistrationError, +> { let mut root = PciRoot::new(); let mut pci_irqs = Vec::new(); let mut pid_labels = BTreeMap::new(); for (dev_idx, (mut device, jail)) in devices.into_iter().enumerate() { - // Only support one bus. - device.assign_bus_dev(0, dev_idx as u8); + // Auto assign PCI device numbers starting from 1 + let address = PciAddress { + bus: 0, + dev: 1 + dev_idx as u8, + func: 0, + }; + device.assign_address(address); let mut keep_fds = device.keep_fds(); syslog::push_fds(&mut keep_fds); @@ -216,7 +236,7 @@ pub fn generate_pci_root( 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); - pci_irqs.push((dev_idx as u32, pci_irq_pin)); + pci_irqs.push((address, irq_num, pci_irq_pin)); let ranges = device .allocate_io_bars(resources) @@ -242,7 +262,7 @@ pub fn generate_pci_root( device.on_sandboxed(); Arc::new(Mutex::new(device)) }; - root.add_device(arced_dev.clone()); + root.add_device(address, arced_dev.clone()); for range in &ranges { mmio_bus .insert(arced_dev.clone(), range.0, range.1, true) @@ -258,70 +278,6 @@ pub fn generate_pci_root( Ok((root, pci_irqs, pid_labels)) } -/// Adds serial devices to the provided bus based on the serial parameters given. Returns the serial -/// port number and serial device to be used for stdout if defined. -/// -/// # Arguments -/// -/// * `io_bus` - Bus to add the devices to -/// * `com_evt_1_3` - eventfd for com1 and com3 -/// * `com_evt_1_4` - eventfd for com2 and com4 -/// * `io_bus` - Bus to add the devices to -/// * `serial_parameters` - definitions of serial parameter configurations. If a setting is not -/// provided for a port, then it will use the default configuration. -pub fn add_serial_devices( - io_bus: &mut Bus, - com_evt_1_3: &EventFd, - com_evt_2_4: &EventFd, - serial_parameters: &BTreeMap<u8, SerialParameters>, - serial_jail: Option<Minijail>, -) -> Result<Option<u8>, DeviceRegistrationError> { - let mut stdio_serial_num = None; - - for x in 0..=3 { - let com_evt = match x { - 0 => com_evt_1_3, - 1 => com_evt_2_4, - 2 => com_evt_1_3, - 3 => com_evt_2_4, - _ => com_evt_1_3, - }; - - let param = serial_parameters - .get(&(x + 1)) - .unwrap_or(&DEFAULT_SERIAL_PARAMS[x as usize]); - - if param.console { - stdio_serial_num = Some(x + 1); - } - - let mut preserved_fds = Vec::new(); - let com = param - .create_serial_device(&com_evt, &mut preserved_fds) - .map_err(DeviceRegistrationError::CreateSerialDevice)?; - - match serial_jail.as_ref() { - Some(jail) => { - let com = Arc::new(Mutex::new( - ProxyDevice::new(com, &jail, preserved_fds) - .map_err(DeviceRegistrationError::ProxyDeviceCreation)?, - )); - io_bus - .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) - .unwrap(); - } - None => { - let com = Arc::new(Mutex::new(com)); - io_bus - .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) - .unwrap(); - } - } - } - - Ok(stdio_serial_num) -} - /// Errors for image loading. #[derive(Debug)] pub enum LoadImageError { diff --git a/arch/src/serial.rs b/arch/src/serial.rs new file mode 100644 index 0000000..f24f4bc --- /dev/null +++ b/arch/src/serial.rs @@ -0,0 +1,481 @@ +// 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 std::collections::BTreeMap; +use std::fmt::{self, Display}; +use std::fs::File; +use std::io::{self, stdin, stdout}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; + +use devices::{Bus, ProxyDevice, Serial, SerialDevice}; +use io_jail::Minijail; +use sync::Mutex; +use sys_util::{read_raw_stdin, syslog, EventFd}; + +use crate::DeviceRegistrationError; + +#[derive(Debug)] +pub enum Error { + CloneEventFd(sys_util::Error), + FileError(std::io::Error), + InvalidSerialHardware(String), + InvalidSerialType(String), + PathRequired, + Unimplemented(SerialType), +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e), + FileError(e) => write!(f, "unable to open/create file: {}", e), + InvalidSerialHardware(e) => write!(f, "invalid serial hardware: {}", e), + InvalidSerialType(e) => write!(f, "invalid serial type: {}", e), + PathRequired => write!(f, "serial device type file requires a path"), + Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()), + } + } +} + +/// Enum for possible type of serial devices +#[derive(Clone, Debug)] +pub enum SerialType { + File, + Stdout, + Sink, + Syslog, + UnixSocket, // NOT IMPLEMENTED +} + +impl Display for SerialType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match &self { + SerialType::File => "File".to_string(), + SerialType::Stdout => "Stdout".to_string(), + SerialType::Sink => "Sink".to_string(), + SerialType::Syslog => "Syslog".to_string(), + SerialType::UnixSocket => "UnixSocket".to_string(), + }; + + write!(f, "{}", s) + } +} + +impl FromStr for SerialType { + type Err = Error; + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + match s { + "file" | "File" => Ok(SerialType::File), + "stdout" | "Stdout" => Ok(SerialType::Stdout), + "sink" | "Sink" => Ok(SerialType::Sink), + "syslog" | "Syslog" => Ok(SerialType::Syslog), + "unix" | "UnixSocket" => Ok(SerialType::UnixSocket), + _ => Err(Error::InvalidSerialType(s.to_string())), + } + } +} + +/// Serial device hardware types +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SerialHardware { + Serial, // Standard PC-style (8250/16550 compatible) UART + VirtioConsole, // virtio-console device +} + +impl Display for SerialHardware { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match &self { + SerialHardware::Serial => "serial".to_string(), + SerialHardware::VirtioConsole => "virtio-console".to_string(), + }; + + write!(f, "{}", s) + } +} + +impl FromStr for SerialHardware { + type Err = Error; + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + match s { + "serial" => Ok(SerialHardware::Serial), + "virtio-console" => Ok(SerialHardware::VirtioConsole), + _ => Err(Error::InvalidSerialHardware(s.to_string())), + } + } +} + +/// Holds the parameters for a serial device +#[derive(Clone, Debug)] +pub struct SerialParameters { + pub type_: SerialType, + pub hardware: SerialHardware, + pub path: Option<PathBuf>, + pub input: Option<PathBuf>, + pub num: u8, + pub console: bool, + pub earlycon: bool, + pub stdin: bool, +} + +impl SerialParameters { + /// Helper function to create a serial device from the defined parameters. + /// + /// # Arguments + /// * `evt_fd` - eventfd used for interrupt events + /// * `keep_fds` - Vector of FDs required by this device if it were sandboxed in a child + /// process. `evt_fd` will always be added to this vector by this function. + pub fn create_serial_device<T: SerialDevice>( + &self, + evt_fd: &EventFd, + keep_fds: &mut Vec<RawFd>, + ) -> std::result::Result<T, Error> { + let evt_fd = evt_fd.try_clone().map_err(Error::CloneEventFd)?; + keep_fds.push(evt_fd.as_raw_fd()); + let input: Option<Box<dyn io::Read + Send>> = if let Some(input_path) = &self.input { + let input_file = File::open(input_path.as_path()).map_err(Error::FileError)?; + keep_fds.push(input_file.as_raw_fd()); + Some(Box::new(input_file)) + } else if self.stdin { + keep_fds.push(stdin().as_raw_fd()); + // This wrapper is used in place of the libstd native version because we don't want + // buffering for stdin. + struct StdinWrapper; + impl io::Read for StdinWrapper { + fn read(&mut self, out: &mut [u8]) -> io::Result<usize> { + read_raw_stdin(out).map_err(|e| e.into()) + } + } + Some(Box::new(StdinWrapper)) + } else { + None + }; + let output: Option<Box<dyn io::Write + Send>> = match self.type_ { + SerialType::Stdout => { + keep_fds.push(stdout().as_raw_fd()); + Some(Box::new(stdout())) + } + SerialType::Sink => None, + SerialType::Syslog => { + syslog::push_fds(keep_fds); + Some(Box::new(syslog::Syslogger::new( + syslog::Priority::Info, + syslog::Facility::Daemon, + ))) + } + SerialType::File => match &self.path { + Some(path) => { + let file = File::create(path.as_path()).map_err(Error::FileError)?; + keep_fds.push(file.as_raw_fd()); + Some(Box::new(file)) + } + None => return Err(Error::PathRequired), + }, + SerialType::UnixSocket => return Err(Error::Unimplemented(SerialType::UnixSocket)), + }; + Ok(T::new(evt_fd, input, output, keep_fds.to_vec())) + } +} + +/// Add the default serial parameters for serial ports that have not already been specified. +/// +/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style +/// serial ports (COM1-COM4). +/// +/// It also sets the first `SerialHardware::Serial` to be the default console device if no other +/// serial parameters exist with console=true and the first serial device has not already been +/// configured explicitly. +pub fn set_default_serial_parameters( + serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>, +) { + // If no console device exists and the first serial port has not been specified, + // set the first serial port as a stdout+stdin console. + let default_console = (SerialHardware::Serial, 1); + if !serial_parameters.iter().any(|(_, p)| p.console) { + serial_parameters + .entry(default_console) + .or_insert(SerialParameters { + type_: SerialType::Stdout, + hardware: SerialHardware::Serial, + path: None, + input: None, + num: 1, + console: true, + earlycon: false, + stdin: true, + }); + } + + // Ensure all four of the COM ports exist. + // If one of these four SerialHardware::Serial port was not configured by the user, + // set it up as a sink. + for num in 1..=4 { + let key = (SerialHardware::Serial, num); + serial_parameters.entry(key).or_insert(SerialParameters { + type_: SerialType::Sink, + hardware: SerialHardware::Serial, + path: None, + input: None, + num, + console: false, + earlycon: false, + stdin: false, + }); + } +} + +/// Address for Serial ports in x86 +pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8]; + +/// Adds serial devices to the provided bus based on the serial parameters given. +/// +/// Only devices with hardware type `SerialHardware::Serial` are added by this function. +/// +/// # Arguments +/// +/// * `io_bus` - Bus to add the devices to +/// * `com_evt_1_3` - eventfd for com1 and com3 +/// * `com_evt_1_4` - eventfd for com2 and com4 +/// * `io_bus` - Bus to add the devices to +/// * `serial_parameters` - definitions of serial parameter configurations. +/// All four of the traditional PC-style serial ports (COM1-COM4) must be specified. +pub fn add_serial_devices( + io_bus: &mut Bus, + com_evt_1_3: &EventFd, + com_evt_2_4: &EventFd, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, + serial_jail: Option<Minijail>, +) -> Result<(), DeviceRegistrationError> { + for x in 0..=3 { + let com_evt = match x { + 0 => com_evt_1_3, + 1 => com_evt_2_4, + 2 => com_evt_1_3, + 3 => com_evt_2_4, + _ => com_evt_1_3, + }; + + let param = serial_parameters + .get(&(SerialHardware::Serial, x + 1)) + .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?; + + let mut preserved_fds = Vec::new(); + let com = param + .create_serial_device::<Serial>(&com_evt, &mut preserved_fds) + .map_err(DeviceRegistrationError::CreateSerialDevice)?; + + match serial_jail.as_ref() { + Some(jail) => { + let com = Arc::new(Mutex::new( + ProxyDevice::new(com, &jail, preserved_fds) + .map_err(DeviceRegistrationError::ProxyDeviceCreation)?, + )); + io_bus + .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) + .unwrap(); + } + None => { + let com = Arc::new(Mutex::new(com)); + io_bus + .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) + .unwrap(); + } + } + } + + Ok(()) +} + +#[derive(Debug)] +pub enum GetSerialCmdlineError { + KernelCmdline(kernel_cmdline::Error), + UnsupportedEarlyconHardware(SerialHardware), +} + +impl Display for GetSerialCmdlineError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::GetSerialCmdlineError::*; + + match self { + KernelCmdline(e) => write!(f, "error appending to cmdline: {}", e), + UnsupportedEarlyconHardware(hw) => { + write!(f, "hardware {} not supported as earlycon", hw) + } + } + } +} + +pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>; + +/// Add serial options to the provided `cmdline` based on `serial_parameters`. +/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices +/// or "mmio" if the serial ports are memory mapped. +pub fn get_serial_cmdline( + cmdline: &mut kernel_cmdline::Cmdline, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, + serial_io_type: &str, +) -> GetSerialCmdlineResult<()> { + match serial_parameters + .iter() + .filter(|(_, p)| p.console) + .map(|(k, _)| k) + .next() + { + Some((SerialHardware::Serial, num)) => { + cmdline + .insert("console", &format!("ttyS{}", num - 1)) + .map_err(GetSerialCmdlineError::KernelCmdline)?; + } + Some((SerialHardware::VirtioConsole, num)) => { + cmdline + .insert("console", &format!("hvc{}", num - 1)) + .map_err(GetSerialCmdlineError::KernelCmdline)?; + } + None => {} + } + + match serial_parameters + .iter() + .filter(|(_, p)| p.earlycon) + .map(|(k, _)| k) + .next() + { + Some((SerialHardware::Serial, num)) => { + if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) { + cmdline + .insert( + "earlycon", + &format!("uart8250,{},0x{:x}", serial_io_type, addr), + ) + .map_err(GetSerialCmdlineError::KernelCmdline)?; + } + } + Some((hw, _num)) => { + return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw)); + } + None => {} + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use kernel_cmdline::Cmdline; + + #[test] + fn get_serial_cmdline_default() { + let mut cmdline = Cmdline::new(4096); + let mut serial_parameters = BTreeMap::new(); + + set_default_serial_parameters(&mut serial_parameters); + get_serial_cmdline(&mut cmdline, &serial_parameters, "io") + .expect("get_serial_cmdline failed"); + + let cmdline_str = cmdline.as_str(); + assert!(cmdline_str.contains("console=ttyS0")); + } + + #[test] + fn get_serial_cmdline_virtio_console() { + let mut cmdline = Cmdline::new(4096); + let mut serial_parameters = BTreeMap::new(); + + // Add a virtio-console device with console=true. + serial_parameters.insert( + (SerialHardware::VirtioConsole, 1), + SerialParameters { + type_: SerialType::Stdout, + hardware: SerialHardware::VirtioConsole, + path: None, + input: None, + num: 1, + console: true, + earlycon: false, + stdin: true, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + get_serial_cmdline(&mut cmdline, &serial_parameters, "io") + .expect("get_serial_cmdline failed"); + + let cmdline_str = cmdline.as_str(); + assert!(cmdline_str.contains("console=hvc0")); + } + + #[test] + fn get_serial_cmdline_virtio_console_serial_earlycon() { + let mut cmdline = Cmdline::new(4096); + let mut serial_parameters = BTreeMap::new(); + + // Add a virtio-console device with console=true. + serial_parameters.insert( + (SerialHardware::VirtioConsole, 1), + SerialParameters { + type_: SerialType::Stdout, + hardware: SerialHardware::VirtioConsole, + path: None, + input: None, + num: 1, + console: true, + earlycon: false, + stdin: true, + }, + ); + + // Override the default COM1 with an earlycon device. + serial_parameters.insert( + (SerialHardware::Serial, 1), + SerialParameters { + type_: SerialType::Stdout, + hardware: SerialHardware::Serial, + path: None, + input: None, + num: 1, + console: false, + earlycon: true, + stdin: false, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + get_serial_cmdline(&mut cmdline, &serial_parameters, "io") + .expect("get_serial_cmdline failed"); + + let cmdline_str = cmdline.as_str(); + assert!(cmdline_str.contains("console=hvc0")); + assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8")); + } + + #[test] + fn get_serial_cmdline_virtio_console_invalid_earlycon() { + let mut cmdline = Cmdline::new(4096); + let mut serial_parameters = BTreeMap::new(); + + // Try to add a virtio-console device with earlycon=true (unsupported). + serial_parameters.insert( + (SerialHardware::VirtioConsole, 1), + SerialParameters { + type_: SerialType::Stdout, + hardware: SerialHardware::VirtioConsole, + path: None, + input: None, + num: 1, + console: false, + earlycon: true, + stdin: true, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + get_serial_cmdline(&mut cmdline, &serial_parameters, "io") + .expect_err("get_serial_cmdline succeeded"); + } +} diff --git a/async_core/src/eventfd.rs b/async_core/src/eventfd.rs index b930f07..e0822a7 100644 --- a/async_core/src/eventfd.rs +++ b/async_core/src/eventfd.rs @@ -2,24 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use futures::Stream; use std::convert::TryFrom; use std::fmt::{self, Display}; +use std::future::Future; use std::os::unix::io::AsRawFd; use std::pin::Pin; use std::task::{Context, Poll}; use libc::{EWOULDBLOCK, O_NONBLOCK}; +use cros_async::{self, add_read_waker, cancel_waker, WakerToken}; use sys_util::{self, add_fd_flags}; -use cros_async::fd_executor::{self, add_read_waker}; - /// Errors generated while polling for events. #[derive(Debug)] pub enum Error { /// An error occurred attempting to register a waker with the executor. - AddingWaker(fd_executor::Error), + AddingWaker(cros_async::Error), /// Failure creating the event FD. EventFdCreate(sys_util::Error), /// An error occurred when reading the event FD. @@ -50,21 +49,20 @@ impl Display for Error { } } -/// Asynchronous version of `sys_util::EventFd`. Provides an implementation of `futures::Stream` so -/// that events can be consumed in an async context. +/// Asynchronous version of `sys_util::EventFd`. Provides asynchronous values that complete when the +/// next event can be read from the eventfd. /// /// # Example /// /// ``` /// use std::convert::TryInto; /// -/// use async_core::{EventFd }; -/// use futures::StreamExt; +/// use async_core::{EventFd}; /// use sys_util::{self}; /// /// async fn process_events() -> std::result::Result<(), Box<dyn std::error::Error>> { /// let mut async_events: EventFd = sys_util::EventFd::new()?.try_into()?; -/// while let Some(e) = async_events.next().await { +/// while let Ok(e) = async_events.read_next().await { /// // Handle event here. /// } /// Ok(()) @@ -72,13 +70,32 @@ impl Display for Error { /// ``` pub struct EventFd { inner: sys_util::EventFd, - done: bool, } impl EventFd { pub fn new() -> Result<EventFd> { Self::try_from(sys_util::EventFd::new().map_err(Error::EventFdCreate)?) } + + /// Asynchronously read the next value from the eventfd. + /// Returns a Future that can be `awaited` for the next value. + /// + /// # Example + /// + /// ``` + /// use async_core::EventFd; + /// async fn print_events(mut event_fd: EventFd) { + /// loop { + /// match event_fd.read_next().await { + /// Ok(e) => println!("Got event: {}", e), + /// Err(e) => break, + /// } + /// } + /// } + /// ``` + pub fn read_next(&mut self) -> NextValFuture { + NextValFuture::new(self) + } } impl TryFrom<sys_util::EventFd> for EventFd { @@ -87,58 +104,72 @@ impl TryFrom<sys_util::EventFd> for EventFd { fn try_from(eventfd: sys_util::EventFd) -> Result<EventFd> { let fd = eventfd.as_raw_fd(); add_fd_flags(fd, O_NONBLOCK).map_err(Error::SettingNonBlocking)?; - Ok(EventFd { - inner: eventfd, - done: false, - }) + Ok(EventFd { inner: eventfd }) + } +} + +/// A Future that yields the next value from the eventfd when it is ready. +pub struct NextValFuture<'a> { + eventfd: &'a mut EventFd, + waker_token: Option<WakerToken>, +} + +impl<'a> NextValFuture<'a> { + fn new(eventfd: &'a mut EventFd) -> NextValFuture<'a> { + NextValFuture { + eventfd, + waker_token: None, + } } } -impl Stream for EventFd { - type Item = Result<u64>; +impl<'a> Future for NextValFuture<'a> { + type Output = Result<u64>; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { - if self.done { - return Poll::Ready(None); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { + if let Some(token) = self.waker_token.take() { + let _ = cancel_waker(token); } - let res = self - .inner - .read() - .map(|v| Poll::Ready(Some(Ok(v)))) - .or_else(|e| { + match self.eventfd.inner.read() { + Ok(v) => Poll::Ready(Ok(v)), + Err(e) => { if e.errno() == EWOULDBLOCK { - add_read_waker(self.inner.as_raw_fd(), cx.waker().clone()) - .map(|()| Poll::Pending) - .map_err(Error::AddingWaker) + match add_read_waker(self.eventfd.inner.as_raw_fd(), cx.waker().clone()) { + Ok(token) => { + self.waker_token = Some(token); + Poll::Pending + } + Err(e) => Poll::Ready(Err(Error::AddingWaker(e))), + } } else { - Err(Error::EventFdRead(e)) + Poll::Ready(Err(Error::EventFdRead(e))) } - }); - - match res { - Ok(v) => v, - Err(e) => { - self.done = true; - Poll::Ready(Some(Err(e))) } } } } +impl<'a> Drop for NextValFuture<'a> { + fn drop(&mut self) { + if let Some(token) = self.waker_token.take() { + let _ = cancel_waker(token); + } + } +} + #[cfg(test)] mod tests { use super::*; use cros_async::{select2, SelectResult}; use futures::future::pending; use futures::pin_mut; - use futures::stream::StreamExt; #[test] fn eventfd_write_read() { let evt = EventFd::new().unwrap(); async fn read_one(mut evt: EventFd) -> u64 { - if let Some(Ok(e)) = evt.next().await { + if let Ok(e) = evt.read_next().await { e } else { 66 diff --git a/async_core/src/lib.rs b/async_core/src/lib.rs index 96cdcf1..e577bb5 100644 --- a/async_core/src/lib.rs +++ b/async_core/src/lib.rs @@ -7,5 +7,8 @@ //! crate. mod eventfd; +mod timerfd; pub use eventfd::EventFd; +pub use timerfd::Error as TimerFdError; +pub use timerfd::TimerFd; diff --git a/async_core/src/timerfd.rs b/async_core/src/timerfd.rs new file mode 100644 index 0000000..e8ae172 --- /dev/null +++ b/async_core/src/timerfd.rs @@ -0,0 +1,243 @@ +// 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 std::convert::TryFrom; +use std::fmt::{self, Display}; +use std::future::Future; +use std::os::unix::io::AsRawFd; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +use libc::{EWOULDBLOCK, O_NONBLOCK}; + +use cros_async::Error as ExecutorError; +use cros_async::{add_read_waker, cancel_waker, WakerToken}; +use sys_util::{self, add_fd_flags}; + +/// Errors generated while polling for events. +#[derive(Debug)] +pub enum Error { + /// An error occurred attempting to register a waker with the executor. + AddingWaker(ExecutorError), + /// An error occurred when reading the timer FD. + TimerFdRead(sys_util::Error), + /// An error occurred when resetting the timer FD. + TimerFdReset(sys_util::Error), + /// An error occurred when setting the timer FD non-blocking. + SettingNonBlocking(sys_util::Error), +} +pub type Result<T> = std::result::Result<T, Error>; + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + AddingWaker(e) => write!( + f, + "An error occurred attempting to register a waker with the executor: {}.", + e + ), + TimerFdRead(e) => write!(f, "An error occurred when reading the timer FD: {}.", e), + TimerFdReset(e) => write!(f, "An error occurred when resetting the timer FD: {}.", e), + SettingNonBlocking(e) => { + write!(f, "An error occurred setting the FD non-blocking: {}.", e) + } + } + } +} + +/// Asynchronous version of `sys_util::TimerFd`. Provides a method for asynchronously waiting for a +/// timer to fire. +/// +/// # Example +/// +/// ``` +/// use std::convert::TryInto; +/// +/// use async_core::TimerFd; +/// use sys_util::{self, Result}; +/// async fn process_events() -> Result<()> { +/// let mut async_timer: TimerFd = sys_util::TimerFd::new()?.try_into().unwrap(); +/// while let Ok(e) = async_timer.next_expiration().await { +/// // Handle timer here. +/// } +/// Ok(()) +/// } +/// ``` +pub struct TimerFd(sys_util::TimerFd); + +impl TimerFd { + /// Reset the inner timer to a new duration. + pub fn reset(&self, dur: Duration, interval: Option<Duration>) -> Result<()> { + self.0.reset(dur, interval).map_err(Error::TimerFdReset) + } + + /// Asynchronously wait for the timer to fire. + /// Returns a Future that can be `awaited` for the next interval. + /// + /// # Example + /// + /// ``` + /// use async_core::TimerFd; + /// async fn print_events(mut timer_fd: TimerFd) { + /// loop { + /// match timer_fd.next_expiration().await { + /// Ok(_) => println!("timer fired"), + /// Err(e) => break, + /// } + /// } + /// } + /// ``` + pub fn next_expiration(&self) -> TimerFuture { + TimerFuture::new(&self.0) + } +} + +impl TryFrom<sys_util::TimerFd> for TimerFd { + type Error = Error; + + fn try_from(timerfd: sys_util::TimerFd) -> Result<TimerFd> { + let fd = timerfd.as_raw_fd(); + add_fd_flags(fd, O_NONBLOCK).map_err(Error::SettingNonBlocking)?; + Ok(TimerFd(timerfd)) + } +} + +/// A Future that yields completes when the timer has fired the next time. +pub struct TimerFuture<'a> { + inner: &'a sys_util::TimerFd, + waker_token: Option<WakerToken>, +} + +impl<'a> TimerFuture<'a> { + fn new(inner: &'a sys_util::TimerFd) -> TimerFuture<'a> { + TimerFuture { + inner, + waker_token: None, + } + } +} + +impl<'a> Future for TimerFuture<'a> { + type Output = Result<u64>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { + if let Some(token) = self.waker_token.take() { + let _ = cancel_waker(token); + } + + match self.inner.wait() { + Ok(count) => Poll::Ready(Ok(count)), + Err(e) => { + if e.errno() == EWOULDBLOCK { + match add_read_waker(self.inner.as_raw_fd(), cx.waker().clone()) { + Ok(token) => { + self.waker_token = Some(token); + Poll::Pending + } + Err(e) => Poll::Ready(Err(Error::AddingWaker(e))), + } + } else { + // Indicate something went wrong and no more events will be provided. + Poll::Ready(Err(Error::TimerFdRead(e))) + } + } + } + } +} + +impl<'a> Drop for TimerFuture<'a> { + fn drop(&mut self) { + if let Some(token) = self.waker_token.take() { + let _ = cancel_waker(token); + } + } +} + +impl AsRef<sys_util::TimerFd> for TimerFd { + fn as_ref(&self) -> &sys_util::TimerFd { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use cros_async::{run_one, select2, SelectResult}; + use futures::future::pending; + use futures::future::Either; + use futures::pin_mut; + + use super::*; + + #[test] + fn timer_timeout() { + async fn timeout(duration: Duration) -> u64 { + let t = sys_util::TimerFd::new().unwrap(); + let async_timer = TimerFd::try_from(t).unwrap(); + async_timer.reset(duration, None).unwrap(); + async_timer.next_expiration().await.unwrap() + } + + let to = timeout(Duration::from_millis(10)); + pin_mut!(to); + if let Ok((SelectResult::Finished(timer_count), SelectResult::Pending(_pend_fut))) = + select2(to, pending::<()>()) + { + assert_eq!(timer_count, 1); + } else { + panic!("wrong futures returned from select2"); + } + } + + #[test] + fn select_with_eventfd() { + async fn async_test() { + let mut e = crate::eventfd::EventFd::new().unwrap(); + let t = sys_util::TimerFd::new().unwrap(); + let async_timer = TimerFd::try_from(t).unwrap(); + async_timer.reset(Duration::from_millis(10), None).unwrap(); + let timer = async_timer.next_expiration(); + let event = e.read_next(); + pin_mut!(timer); + pin_mut!(event); + match futures::future::select(timer, event).await { + Either::Left((_timer, _event)) => (), + _ => panic!("unexpected select result"), + } + } + + let fut = async_test(); + + run_one(Box::pin(fut)).unwrap(); + } + + #[test] + fn pend_unarmed_cancel() { + async fn async_test() { + let t = sys_util::TimerFd::new().unwrap(); + let async_timer = TimerFd::try_from(t).unwrap(); + let done = async { + std::thread::sleep(Duration::from_millis(10)); + 5usize + }; + let timer = async_timer.next_expiration(); + pin_mut!(done); + pin_mut!(timer); + match futures::future::select(timer, done).await { + Either::Right((_, _timer)) => (), + _ => panic!("unexpected select result"), + } + } + + let fut = async_test(); + + run_one(Box::pin(fut)).unwrap(); + } +} diff --git a/bin/clippy b/bin/clippy index b668875..3adce5f 100755 --- a/bin/clippy +++ b/bin/clippy @@ -16,10 +16,33 @@ cd "$(dirname "${BASH_SOURCE[0]}")" cd .. SUPPRESS=( - # To be resolved. + # TODO(crbug/908640): To be resolved. + borrowed_box + char_lit_as_u8 + clone_on_copy + collapsible_if + comparison_chain + extra_unused_lifetimes + for_kv_map + inefficient_to_string + into_iter_on_ref let_unit_value + missing_safety_doc + needless_doctest_main + needless_range_loop + needless_return + option_map_unit_fn question_mark range_plus_one + redundant_clone + redundant_closure + single_match + slow_vector_initialization + unnecessary_filter_map + unnecessary_mut_passed + unneeded_field_pattern + useless_format + wrong_self_convention # We don't care about these lints. Okay to remain suppressed globally. blacklisted_name @@ -49,4 +72,11 @@ SUPPRESS=( # Needed or else clippy won't re-run on code that has already compiled. cargo clean -cargo clippy --all-features -- ${SUPPRESS[@]/#/-Aclippy::} "$@" +# Need to set pass --sysroot for cargo-clippy manually. +# cf. https://github.com/rust-lang/rust-clippy/issues/3523 +RUST_SYSROOT=$(rustc --print sysroot) +RUSTFLAGS="${RUSTFLAGS:-}" +export RUSTFLAGS="$RUSTFLAGS --sysroot=$RUST_SYSROOT" + +cargo clippy --all-features --all-targets -- ${SUPPRESS[@]/#/-Aclippy::} "$@" \ + -D warnings diff --git a/bin/smoke_test b/bin/smoke_test index 50cb79c..74e1cd0 100755 --- a/bin/smoke_test +++ b/bin/smoke_test @@ -10,10 +10,13 @@ cd ../ rustup default "$(cat rust-toolchain)" rustup component add rustfmt-preview -cargo --version && rustc --version && rustfmt --version +cargo --version && rustc --version && rustfmt --version \ + && cargo clippy --version echo "Running cargo test" cargo test --no-fail-fast --features plugin,default-no-sandbox,wl-dmabuf,gpu,tpm \ --all --exclude aarch64 $TEST_FLAGS -- \ --test-threads=1 $TEST_RUNNER_FLAGS echo "Running cargo fmt" bin/fmt --check +echo "Running cargo clippy" +bin/clippy diff --git a/cros_async/src/complete.rs b/cros_async/src/complete.rs index 8455066..f29f4e8 100644 --- a/cros_async/src/complete.rs +++ b/cros_async/src/complete.rs @@ -21,7 +21,6 @@ macro_rules! generate { $(#[$doc:meta])* ($Complete:ident, <$($Fut:ident),*>), )*) => ($( - $(#[$doc])* #[must_use = "Combinations of futures don't do anything unless run in an executor."] paste::item! { pub(crate) struct $Complete<$($Fut: Future + Unpin),*> { diff --git a/cros_async/src/executor.rs b/cros_async/src/executor.rs index ac788f1..85d3f70 100644 --- a/cros_async/src/executor.rs +++ b/cros_async/src/executor.rs @@ -10,6 +10,8 @@ use std::rc::Rc; use std::task::Waker; use std::task::{Context, Poll}; +use futures::future::FutureExt; + use crate::waker::create_waker; /// Represents a future executor that can be run. Implementers of the trait will take a list of @@ -24,6 +26,9 @@ pub trait Executor { fn run(&mut self) -> Self::Output; } +/// A token returned from `add_waker` that can be used to cancel the waker before it completes. +pub struct WakerToken(pub(crate) u64); + // Tracks if a future needs to be polled and the waker to use. pub(crate) struct FutureState { pub needs_poll: Rc<Cell<bool>>, @@ -149,6 +154,49 @@ impl FutureList for UnitFutures { } } +// Execute one future until it completes. +pub(crate) struct RunOne<F: Future + Unpin> { + fut: F, + fut_state: FutureState, + added_futures: UnitFutures, +} + +impl<F: Future + Unpin> RunOne<F> { + pub fn new(f: F) -> RunOne<F> { + RunOne { + fut: f, + fut_state: FutureState::new(), + added_futures: UnitFutures::new(), + } + } +} + +impl<F: Future + Unpin> FutureList for RunOne<F> { + type Output = F::Output; + + fn futures_mut(&mut self) -> &mut UnitFutures { + &mut self.added_futures + } + + fn poll_results(&mut self) -> Option<Self::Output> { + let _ = self.added_futures.poll_results(); + + if self.fut_state.needs_poll.replace(false) { + let mut ctx = Context::from_waker(&self.fut_state.waker); + // The future impls `Unpin`, use `poll_unpin` to avoid wrapping it in + // `Pin` to call `poll`. + if let Poll::Ready(o) = self.fut.poll_unpin(&mut ctx) { + return Some(o); + } + }; + None + } + + fn any_ready(&self) -> bool { + self.added_futures.any_ready() || self.fut_state.needs_poll.get() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/cros_async/src/fd_executor.rs b/cros_async/src/fd_executor.rs index 58d6013..1ab9ca8 100644 --- a/cros_async/src/fd_executor.rs +++ b/cros_async/src/fd_executor.rs @@ -8,23 +8,6 @@ //! //! `FdExecutor` is meant to be used with the `futures-rs` crate that provides combinators and //! utility functions to combine futures. -//! -//! # Example of starting the framework and running a future: -//! -//! ``` -//! # use std::rc::Rc; -//! # use std::cell::RefCell; -//! use cros_async::Executor; -//! async fn my_async(mut x: Rc<RefCell<u64>>) { -//! x.replace(4); -//! } -//! -//! let mut ex = cros_async::empty_executor().expect("Failed creating executor"); -//! let x = Rc::new(RefCell::new(0)); -//! cros_async::fd_executor::add_future(Box::pin(my_async(x.clone()))); -//! ex.run(); -//! assert_eq!(*x.borrow(), 4); -//! ``` use std::cell::RefCell; use std::collections::{BTreeMap, VecDeque}; @@ -38,7 +21,7 @@ use std::task::Waker; use sys_util::{PollContext, WatchingEvents}; -use crate::executor::{ExecutableFuture, Executor, FutureList}; +use crate::executor::{ExecutableFuture, Executor, FutureList, WakerToken}; #[derive(Debug, PartialEq)] pub enum Error { @@ -80,7 +63,7 @@ impl Display for Error { // Tracks active wakers and the futures they are associated with. thread_local!(static STATE: RefCell<Option<FdWakerState>> = RefCell::new(None)); -fn add_waker(fd: RawFd, waker: Waker, events: WatchingEvents) -> Result<()> { +fn add_waker(fd: RawFd, waker: Waker, events: WatchingEvents) -> Result<WakerToken> { STATE.with(|state| { let mut state = state.borrow_mut(); if let Some(state) = state.as_mut() { @@ -95,7 +78,8 @@ fn add_waker(fd: RawFd, waker: Waker, events: WatchingEvents) -> Result<()> { /// The 'fd' must be fully owned by the future adding the waker, and must not be closed until the /// next time the future is polled. If the fd is closed, there is a race where another FD can be /// opened on top of it causing the next poll to access the new target file. -pub fn add_read_waker(fd: RawFd, waker: Waker) -> Result<()> { +/// Returns a `WakerToken` that can be used to cancel the waker before it completes. +pub fn add_read_waker(fd: RawFd, waker: Waker) -> Result<WakerToken> { add_waker(fd, waker, WatchingEvents::empty().set_read()) } @@ -103,10 +87,23 @@ pub fn add_read_waker(fd: RawFd, waker: Waker) -> Result<()> { /// The 'fd' must be fully owned by the future adding the waker, and must not be closed until the /// next time the future is polled. If the fd is closed, there is a race where another FD can be /// opened on top of it causing the next poll to access the new target file. -pub fn add_write_waker(fd: RawFd, waker: Waker) -> Result<()> { +/// Returns a `WakerToken` that can be used to cancel the waker before it completes. +pub fn add_write_waker(fd: RawFd, waker: Waker) -> Result<WakerToken> { add_waker(fd, waker, WatchingEvents::empty().set_write()) } +/// Cancels the waker that returned the given token if the waker hasn't yet fired. +pub fn cancel_waker(token: WakerToken) -> Result<()> { + STATE.with(|state| { + let mut state = state.borrow_mut(); + if let Some(state) = state.as_mut() { + state.cancel_waker(token) + } else { + Err(Error::InvalidContext) + } + }) +} + /// Adds a new top level future to the Executor. /// These futures must return `()`, indicating they are intended to create side-effects only. pub fn add_future(future: Pin<Box<dyn Future<Output = ()>>>) -> Result<()> { @@ -140,7 +137,7 @@ impl FdWakerState { } // Adds an fd that, when signaled, will trigger the given waker. - fn add_waker(&mut self, fd: RawFd, waker: Waker, events: WatchingEvents) -> Result<()> { + fn add_waker(&mut self, fd: RawFd, waker: Waker, events: WatchingEvents) -> Result<WakerToken> { let duped_fd = unsafe { // Safe because duplicating an FD doesn't affect memory safety, and the dup'd FD // will only be added to the poll loop. @@ -152,7 +149,7 @@ impl FdWakerState { let next_token = self.next_token; self.token_map.insert(next_token, (duped_fd, waker)); self.next_token += 1; - Ok(()) + Ok(WakerToken(next_token)) } // Waits until one of the FDs is readable and wakes the associated waker. @@ -166,6 +163,14 @@ impl FdWakerState { } Ok(()) } + + // Remove the waker for the given token if it hasn't fired yet. + fn cancel_waker(&mut self, token: WakerToken) -> Result<()> { + if let Some((fd, _waker)) = self.token_map.remove(&token.0) { + self.poll_ctx.delete(&fd).map_err(Error::PollContextError)?; + } + Ok(()) + } } /// Runs futures to completion on a single thread. Futures are allowed to block on file descriptors @@ -248,3 +253,85 @@ unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> { Ok(ret) } } + +#[cfg(test)] +mod test { + use std::cell::RefCell; + use std::future::Future; + use std::os::unix::io::AsRawFd; + use std::rc::Rc; + use std::task::{Context, Poll}; + + use futures::future::Either; + use futures::pin_mut; + + use super::*; + + struct TestFut { + f: File, + token: Option<WakerToken>, + } + + impl TestFut { + fn new(f: File) -> TestFut { + TestFut { f, token: None } + } + } + + impl Future for TestFut { + type Output = u64; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { + if self.token.is_none() { + self.token = Some(add_read_waker(self.f.as_raw_fd(), cx.waker().clone()).unwrap()); + } + Poll::Pending + } + } + + impl Drop for TestFut { + fn drop(&mut self) { + if let Some(token) = self.token.take() { + cancel_waker(token).unwrap(); + } + } + } + + #[test] + fn cancel() { + async fn do_test() { + let (r, _w) = sys_util::pipe(true).unwrap(); + let done = async { 5usize }; + let pending = TestFut::new(r); + pin_mut!(done); + pin_mut!(pending); + match futures::future::select(pending, done).await { + Either::Right((5, _pending)) => (), + _ => panic!("unexpected select result"), + } + } + + let fut = do_test(); + + let mut ex = FdExecutor::new(crate::UnitFutures::new()).expect("Failed creating executor"); + add_future(Box::pin(fut)).unwrap(); + ex.run().unwrap(); + STATE.with(|state| { + let state = state.borrow_mut(); + assert!(state.as_ref().unwrap().token_map.is_empty()); + }); + } + + #[test] + fn run() { + // Example of starting the framework and running a future: + async fn my_async(x: Rc<RefCell<u64>>) { + x.replace(4); + } + + let mut ex = FdExecutor::new(crate::UnitFutures::new()).expect("Failed creating executor"); + let x = Rc::new(RefCell::new(0)); + crate::fd_executor::add_future(Box::pin(my_async(x.clone()))).unwrap(); + ex.run().unwrap(); + assert_eq!(*x.borrow(), 4); + } +} diff --git a/cros_async/src/lib.rs b/cros_async/src/lib.rs index f671a0c..53f480d 100644 --- a/cros_async/src/lib.rs +++ b/cros_async/src/lib.rs @@ -41,8 +41,8 @@ //! //! # Implementing new FD-based futures. //! -//! When building futures to be run in an `FdExecutor` framework, use the following helper -//! functions to perform common tasks: +//! When building futures to be run in an `Executor` framework, use the following helper functions +//! to perform common tasks: //! //! [`add_read_waker`](fn.add_read_waker.html) - Used to associate a provided FD becoming readable //! with the future being woken. Used before returning Poll::Pending from a future that waits until @@ -57,16 +57,38 @@ mod complete; mod executor; -pub mod fd_executor; +mod fd_executor; mod select; mod waker; -pub use executor::Executor; +pub use executor::{Executor, WakerToken}; pub use select::SelectResult; -use executor::UnitFutures; -use fd_executor::{FdExecutor, Result}; +use executor::{RunOne, UnitFutures}; +use fd_executor::FdExecutor; + +use std::fmt::{self, Display}; use std::future::Future; +use std::os::unix::io::RawFd; +use std::pin::Pin; +use std::task::Waker; + +#[derive(Debug, PartialEq)] +pub enum Error { + /// Error from the FD executor. + FdExecutor(fd_executor::Error), +} +pub type Result<T> = std::result::Result<T, Error>; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + FdExecutor(e) => write!(f, "Failure in the FD executor: {}", e), + } + } +} /// Creates an empty FdExecutor that can have futures returning `()` added via /// [`add_future`](fn.add_future.html). @@ -85,7 +107,23 @@ use std::future::Future; /// ex.run(); /// ``` pub fn empty_executor() -> Result<impl Executor> { - FdExecutor::new(UnitFutures::new()) + FdExecutor::new(UnitFutures::new()).map_err(Error::FdExecutor) +} + +/// Creates a FdExecutor that runs one future to completion. +/// +/// # Example +/// +/// ``` +/// use cros_async::run_one; +/// +/// let fut = async { 55 }; +/// assert_eq!(Ok(55),run_one(Box::pin(fut))); +/// ``` +pub fn run_one<F: Future + Unpin>(fut: F) -> Result<F::Output> { + FdExecutor::new(RunOne::new(fut)) + .and_then(|mut ex| ex.run()) + .map_err(Error::FdExecutor) } // Select helpers to run until any future completes. @@ -114,7 +152,9 @@ pub fn select2<F1: Future + Unpin, F2: Future + Unpin>( f1: F1, f2: F2, ) -> Result<(SelectResult<F1>, SelectResult<F2>)> { - FdExecutor::new(select::Select2::new(f1, f2)).and_then(|mut f| f.run()) + FdExecutor::new(select::Select2::new(f1, f2)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the three given futures until one or more completes, returning a @@ -146,7 +186,9 @@ pub fn select3<F1: Future + Unpin, F2: Future + Unpin, F3: Future + Unpin>( f2: F2, f3: F3, ) -> Result<(SelectResult<F1>, SelectResult<F2>, SelectResult<F3>)> { - FdExecutor::new(select::Select3::new(f1, f2, f3)).and_then(|mut f| f.run()) + FdExecutor::new(select::Select3::new(f1, f2, f3)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the four given futures until one or more completes, returning a @@ -185,7 +227,9 @@ pub fn select4<F1: Future + Unpin, F2: Future + Unpin, F3: Future + Unpin, F4: F SelectResult<F3>, SelectResult<F4>, )> { - FdExecutor::new(select::Select4::new(f1, f2, f3, f4)).and_then(|mut f| f.run()) + FdExecutor::new(select::Select4::new(f1, f2, f3, f4)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the five given futures until one or more completes, returning a @@ -235,7 +279,66 @@ pub fn select5< SelectResult<F4>, SelectResult<F5>, )> { - FdExecutor::new(select::Select5::new(f1, f2, f3, f4, f5)).and_then(|mut f| f.run()) + FdExecutor::new(select::Select5::new(f1, f2, f3, f4, f5)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) +} + +/// Creates an executor that runs the six given futures until one or more completes, returning a +/// tuple containing the result of the finished future(s) and the still pending future(s). +/// +/// # Example +/// +/// ``` +/// use cros_async::{empty_executor, Executor, select6, SelectResult}; +/// use cros_async::fd_executor::add_future; +/// use futures::future::pending; +/// use futures::pin_mut; +/// +/// let first = async {1}; +/// let second = async {let () = pending().await;}; +/// let third = async {3}; +/// let fourth = async {let () = pending().await;}; +/// let fifth = async {5}; +/// let sixth = async {6}; +/// pin_mut!(first); +/// pin_mut!(second); +/// pin_mut!(third); +/// pin_mut!(fourth); +/// pin_mut!(fifth); +/// pin_mut!(sixth); +/// match select6(first, second, third, fourth, fifth, sixth) { +/// Ok((SelectResult::Finished(1), SelectResult::Pending(_second), +/// SelectResult::Finished(3), SelectResult::Pending(_fourth), +/// SelectResult::Finished(5), SelectResult::Finished(6))) => (), +/// _ => panic!("Select didn't return the futures"), +/// }; +/// ``` +pub fn select6< + F1: Future + Unpin, + F2: Future + Unpin, + F3: Future + Unpin, + F4: Future + Unpin, + F5: Future + Unpin, + F6: Future + Unpin, +>( + f1: F1, + f2: F2, + f3: F3, + f4: F4, + f5: F5, + f6: F6, +) -> Result<( + SelectResult<F1>, + SelectResult<F2>, + SelectResult<F3>, + SelectResult<F4>, + SelectResult<F5>, + SelectResult<F6>, +)> { + FdExecutor::new(select::Select6::new(f1, f2, f3, f4, f5, f6)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } // Combination helpers to run until all futures are complete. @@ -259,7 +362,9 @@ pub fn complete2<F1: Future + Unpin, F2: Future + Unpin>( f1: F1, f2: F2, ) -> Result<(F1::Output, F2::Output)> { - FdExecutor::new(complete::Complete2::new(f1, f2)).and_then(|mut f| f.run()) + FdExecutor::new(complete::Complete2::new(f1, f2)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the three given futures to completion, returning a tuple of the @@ -284,7 +389,9 @@ pub fn complete3<F1: Future + Unpin, F2: Future + Unpin, F3: Future + Unpin>( f2: F2, f3: F3, ) -> Result<(F1::Output, F2::Output, F3::Output)> { - FdExecutor::new(complete::Complete3::new(f1, f2, f3)).and_then(|mut f| f.run()) + FdExecutor::new(complete::Complete3::new(f1, f2, f3)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the four given futures to completion, returning a tuple of the @@ -312,7 +419,9 @@ pub fn complete4<F1: Future + Unpin, F2: Future + Unpin, F3: Future + Unpin, F4: f3: F3, f4: F4, ) -> Result<(F1::Output, F2::Output, F3::Output, F4::Output)> { - FdExecutor::new(complete::Complete4::new(f1, f2, f3, f4)).and_then(|mut f| f.run()) + FdExecutor::new(complete::Complete4::new(f1, f2, f3, f4)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) } /// Creates an executor that runs the five given futures to completion, returning a tuple of the @@ -350,5 +459,38 @@ pub fn complete5< f4: F4, f5: F5, ) -> Result<(F1::Output, F2::Output, F3::Output, F4::Output, F5::Output)> { - FdExecutor::new(complete::Complete5::new(f1, f2, f3, f4, f5)).and_then(|mut f| f.run()) + FdExecutor::new(complete::Complete5::new(f1, f2, f3, f4, f5)) + .and_then(|mut f| f.run()) + .map_err(Error::FdExecutor) +} + +// Functions to be used by `Future` implementations + +/// Tells the waking system to wake `waker` when `fd` becomes readable. +/// The 'fd' must be fully owned by the future adding the waker, and must not be closed until the +/// next time the future is polled. If the fd is closed, there is a race where another FD can be +/// opened on top of it causing the next poll to access the new target file. +/// Returns a `WakerToken` that can be used to cancel the waker before it completes. +pub fn add_read_waker(fd: RawFd, waker: Waker) -> Result<WakerToken> { + fd_executor::add_read_waker(fd, waker).map_err(Error::FdExecutor) +} + +/// Tells the waking system to wake `waker` when `fd` becomes writable. +/// The 'fd' must be fully owned by the future adding the waker, and must not be closed until the +/// next time the future is polled. If the fd is closed, there is a race where another FD can be +/// opened on top of it causing the next poll to access the new target file. +/// Returns a `WakerToken` that can be used to cancel the waker before it completes. +pub fn add_write_waker(fd: RawFd, waker: Waker) -> Result<WakerToken> { + fd_executor::add_write_waker(fd, waker).map_err(Error::FdExecutor) +} + +/// Cancels the waker that returned the given token if the waker hasn't yet fired. +pub fn cancel_waker(token: WakerToken) -> Result<()> { + fd_executor::cancel_waker(token).map_err(Error::FdExecutor) +} + +/// Adds a new top level future to the Executor. +/// These futures must return `()`, indicating they are intended to create side-effects only. +pub fn add_future(future: Pin<Box<dyn Future<Output = ()>>>) -> Result<()> { + fd_executor::add_future(future).map_err(Error::FdExecutor) } diff --git a/cros_async/src/select.rs b/cros_async/src/select.rs index 8eef317..b10bc2c 100644 --- a/cros_async/src/select.rs +++ b/cros_async/src/select.rs @@ -26,7 +26,6 @@ macro_rules! generate { $(#[$doc:meta])* ($Select:ident, <$($Fut:ident),*>), )*) => ($( - $(#[$doc])* #[must_use = "Combinations of futures don't do anything unless run in an executor."] paste::item! { pub(crate) struct $Select<$($Fut: Future + Unpin),*> { @@ -107,4 +106,7 @@ generate! { /// _Future for the [`select5`] function. (Select5, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5>), + + /// _Future for the [`select6`] function. + (Select6, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5, _Fut6>), } diff --git a/cros_async/src/waker.rs b/cros_async/src/waker.rs index f0dac0f..6d277ee 100644 --- a/cros_async/src/waker.rs +++ b/cros_async/src/waker.rs @@ -16,7 +16,9 @@ unsafe fn waker_drop(data_ptr: *const ()) { let _rc_bool = Rc::<AtomicBool>::from_raw(data_ptr as *const _); } -unsafe fn waker_wake(_: *const ()) {} +unsafe fn waker_wake(data_ptr: *const ()) { + waker_wake_by_ref(data_ptr) +} // Called when the bool should be set to true to wake the waker. unsafe fn waker_wake_by_ref(data_ptr: *const ()) { diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 9ed8417..01c2b46 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -17,6 +17,7 @@ mod proxy; mod register_space; pub mod acpi; mod serial; +mod serial_device; pub mod split_irqchip_common; pub mod usb; mod utils; @@ -30,18 +31,16 @@ pub use self::cmos::Cmos; pub use self::i8042::I8042Device; pub use self::ioapic::{Ioapic, IOAPIC_BASE_ADDRESS, IOAPIC_MEM_LENGTH_BYTES}; pub use self::pci::{ - Ac97Backend, Ac97Dev, Ac97Parameters, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, - PciInterruptPin, PciRoot, VfioPciDevice, + Ac97Backend, Ac97Dev, Ac97Parameters, PciAddress, PciConfigIo, PciConfigMmio, PciDevice, + PciDeviceError, PciInterruptPin, PciRoot, VfioPciDevice, }; pub use self::pic::Pic; pub use self::pit::{Pit, PitError}; pub use self::pl030::Pl030; pub use self::proxy::Error as ProxyError; pub use self::proxy::ProxyDevice; -pub use self::serial::Error as SerialError; -pub use self::serial::{ - get_serial_tty_string, Serial, SerialParameters, SerialType, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR, -}; +pub use self::serial::Serial; +pub use self::serial_device::SerialDevice; pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; pub use self::usb::xhci::xhci_controller::XhciController; pub use self::vfio::{VfioContainer, VfioDevice}; diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs index d061746..8a6748d 100644 --- a/devices/src/pci/ac97.rs +++ b/devices/src/pci/ac97.rs @@ -12,7 +12,7 @@ use audio_streams::{ shm_streams::{NullShmStreamSource, ShmStreamSource}, StreamEffect, }; -use libcras::{CrasClient, CrasClientType}; +use libcras::{CrasClient, CrasClientType, CrasSocketType}; use resources::{Alloc, MmioType, SystemAllocator}; use sys_util::{error, EventFd, GuestMemory}; @@ -23,7 +23,7 @@ use crate::pci::pci_configuration::{ PciBarConfiguration, PciClassCode, PciConfiguration, PciHeaderType, PciMultimediaSubclass, }; use crate::pci::pci_device::{self, PciDevice, Result}; -use crate::pci::PciInterruptPin; +use crate::pci::{PciAddress, PciInterruptPin}; // Use 82801AA because it's what qemu does. const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415; @@ -82,7 +82,7 @@ pub struct Ac97Parameters { pub struct Ac97Dev { config_regs: PciConfiguration, - pci_bus_dev: Option<(u8, u8)>, + pci_address: Option<PciAddress>, // The irq events are temporarily saved here. They need to be passed to the device after the // jail forks. This happens when the bus is first written. irq_evt: Option<EventFd>, @@ -108,7 +108,7 @@ impl Ac97Dev { Ac97Dev { config_regs, - pci_bus_dev: None, + pci_address: None, irq_evt: None, irq_resample_evt: None, bus_master: Ac97BusMaster::new(mem, audio_server), @@ -117,8 +117,10 @@ impl Ac97Dev { } fn create_cras_audio_device(params: Ac97Parameters, mem: GuestMemory) -> Result<Ac97Dev> { - let mut server = - Box::new(CrasClient::new().map_err(|e| pci_device::Error::CreateCrasClientFailed(e))?); + let mut server = Box::new( + CrasClient::with_type(CrasSocketType::Unified) + .map_err(|e| pci_device::Error::CreateCrasClientFailed(e))?, + ); server.set_client_type(CrasClientType::CRAS_CLIENT_TYPE_CROSVM); if params.capture { server.enable_cras_capture(); @@ -214,8 +216,8 @@ impl PciDevice for Ac97Dev { "AC97".to_owned() } - fn assign_bus_dev(&mut self, bus: u8, device: u8) { - self.pci_bus_dev = Some((bus, device)); + fn assign_address(&mut self, address: PciAddress) { + self.pci_address = Some(address); } fn assign_irq( @@ -231,15 +233,20 @@ impl PciDevice for Ac97Dev { } fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> { - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_io_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_io_bars"); let mut ranges = Vec::new(); let mixer_regs_addr = resources .mmio_allocator(MmioType::Low) .allocate_with_align( MIXER_REGS_SIZE, - Alloc::PciBar { bus, dev, bar: 0 }, + Alloc::PciBar { + bus: address.bus, + dev: address.dev, + func: address.func, + bar: 0, + }, "ac97-mixer_regs".to_string(), MIXER_REGS_SIZE, ) @@ -257,7 +264,12 @@ impl PciDevice for Ac97Dev { .mmio_allocator(MmioType::Low) .allocate_with_align( MASTER_REGS_SIZE, - Alloc::PciBar { bus, dev, bar: 1 }, + Alloc::PciBar { + bus: address.bus, + dev: address.dev, + func: address.func, + bar: 1, + }, "ac97-master_regs".to_string(), MASTER_REGS_SIZE, ) @@ -336,7 +348,11 @@ mod tests { .add_high_mmio_addresses(0x3000_0000, 0x1000_0000) .create_allocator(5, false) .unwrap(); - ac97_dev.assign_bus_dev(0, 0); + ac97_dev.assign_address(PciAddress { + bus: 0, + dev: 0, + func: 0, + }); assert!(ac97_dev.allocate_io_bars(&mut allocator).is_ok()); } } diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index 809f31f..22f3c92 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::collections::VecDeque; use std::convert::AsRef; use std::convert::TryInto; diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs index b9ebc19..0edc3b2 100644 --- a/devices/src/pci/mod.rs +++ b/devices/src/pci/mod.rs @@ -23,7 +23,7 @@ pub use self::pci_configuration::{ }; pub use self::pci_device::Error as PciDeviceError; pub use self::pci_device::PciDevice; -pub use self::pci_root::{PciConfigIo, PciConfigMmio, PciRoot}; +pub use self::pci_root::{PciAddress, PciConfigIo, PciConfigMmio, PciRoot}; pub use self::vfio_pci::VfioPciDevice; /// PCI has four interrupt pins A->D. diff --git a/devices/src/pci/pci_device.rs b/devices/src/pci/pci_device.rs index a1a3cca..244edbd 100644 --- a/devices/src/pci/pci_device.rs +++ b/devices/src/pci/pci_device.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::fmt::{self, Display}; use std::os::unix::io::RawFd; @@ -11,7 +10,7 @@ use resources::{Error as SystemAllocatorFaliure, SystemAllocator}; use sys_util::EventFd; use crate::pci::pci_configuration; -use crate::pci::PciInterruptPin; +use crate::pci::{PciAddress, PciInterruptPin}; use crate::BusDevice; #[derive(Debug)] @@ -49,8 +48,8 @@ impl Display for Error { pub trait PciDevice: Send { /// Returns a label suitable for debug output. fn debug_label(&self) -> String; - /// Assign a unique bus and device number to this device. - fn assign_bus_dev(&mut self, _bus: u8, _device: u8 /*u5*/) {} + /// Assign a unique bus, device and function number to this device. + fn assign_address(&mut self, _address: PciAddress) {} /// A vector of device-specific file descriptors that must be kept open /// after jailing. Must be called before the process is jailed. fn keep_fds(&self) -> Vec<RawFd>; @@ -149,8 +148,8 @@ impl<T: PciDevice + ?Sized> PciDevice for Box<T> { fn debug_label(&self) -> String { (**self).debug_label() } - fn assign_bus_dev(&mut self, bus: u8, device: u8 /*u5*/) { - (**self).assign_bus_dev(bus, device) + fn assign_address(&mut self, address: PciAddress) { + (**self).assign_address(address) } fn keep_fds(&self) -> Vec<RawFd> { (**self).keep_fds() diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs index eef642e..91d6094 100644 --- a/devices/src/pci/pci_root.rs +++ b/devices/src/pci/pci_root.rs @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::collections::BTreeMap; use std::convert::TryInto; +use std::fmt::{self, Display}; use std::os::unix::io::RawFd; use std::sync::Arc; @@ -39,12 +41,67 @@ impl PciDevice for PciRootConfiguration { fn write_bar(&mut self, _addr: u64, _data: &[u8]) {} } +/// PCI Device Address, AKA Bus:Device.Function +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PciAddress { + pub bus: u8, + pub dev: u8, /* u5 */ + pub func: u8, /* u3 */ +} + +impl Display for PciAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:04x}:{:02x}.{:0x}", self.bus, self.dev, self.func) + } +} + +impl PciAddress { + const BUS_OFFSET: usize = 16; + const BUS_MASK: u32 = 0x00ff; + const DEVICE_OFFSET: usize = 11; + const DEVICE_MASK: u32 = 0x1f; + const FUNCTION_OFFSET: usize = 8; + const FUNCTION_MASK: u32 = 0x07; + const REGISTER_OFFSET: usize = 2; + const REGISTER_MASK: u32 = 0x3f; + + /// Construct PciAddress and register tuple from CONFIG_ADDRESS value. + pub fn from_config_address(config_address: u32) -> (Self, usize) { + let bus = ((config_address >> Self::BUS_OFFSET) & Self::BUS_MASK) as u8; + let dev = ((config_address >> Self::DEVICE_OFFSET) & Self::DEVICE_MASK) as u8; + let func = ((config_address >> Self::FUNCTION_OFFSET) & Self::FUNCTION_MASK) as u8; + let register = ((config_address >> Self::REGISTER_OFFSET) & Self::REGISTER_MASK) as usize; + + (PciAddress { bus, dev, func }, register) + } + + /// Encode PciAddress into CONFIG_ADDRESS value. + pub fn to_config_address(&self, register: usize) -> u32 { + ((Self::BUS_MASK & self.bus as u32) << Self::BUS_OFFSET) + | ((Self::DEVICE_MASK & self.dev as u32) << Self::DEVICE_OFFSET) + | ((Self::FUNCTION_MASK & self.func as u32) << Self::FUNCTION_OFFSET) + | ((Self::REGISTER_MASK & register as u32) << Self::REGISTER_OFFSET) + } + + /// Returns true if the address points to PCI root host-bridge. + fn is_root(&self) -> bool { + matches!( + &self, + PciAddress { + bus: 0, + dev: 0, + func: 0 + } + ) + } +} + /// Emulates the PCI Root bridge. pub struct PciRoot { /// Bus configuration for the root device. root_configuration: PciRootConfiguration, /// Devices attached to this bridge. - devices: Vec<Arc<Mutex<dyn BusDevice>>>, + devices: BTreeMap<PciAddress, Arc<Mutex<dyn BusDevice>>>, } const PCI_VENDOR_ID_INTEL: u16 = 0x8086; @@ -66,44 +123,31 @@ impl PciRoot { 0, ), }, - devices: Vec::new(), + devices: BTreeMap::new(), } } /// Add a `device` to this root PCI bus. - pub fn add_device(&mut self, device: Arc<Mutex<dyn BusDevice>>) { - self.devices.push(device); - } - - pub fn config_space_read( - &self, - bus: usize, - device: usize, - _function: usize, - register: usize, - ) -> u32 { - // Only support one bus. - if bus != 0 { - return 0xffff_ffff; + pub fn add_device(&mut self, address: PciAddress, device: Arc<Mutex<dyn BusDevice>>) { + // Ignore attempt to replace PCI Root host bridge. + if !address.is_root() { + self.devices.insert(address, device); } + } - match device { - 0 => { - // If bus and device are both zero, then read from the root config. - self.root_configuration.config_register_read(register) - } - dev_num => self - .devices - .get(dev_num - 1) - .map_or(0xffff_ffff, |d| d.lock().config_register_read(register)), + pub fn config_space_read(&self, address: PciAddress, register: usize) -> u32 { + if address.is_root() { + self.root_configuration.config_register_read(register) + } else { + self.devices + .get(&address) + .map_or(0xffff_ffff, |d| d.lock().config_register_read(register)) } } pub fn config_space_write( &mut self, - bus: usize, - device: usize, - _function: usize, + address: PciAddress, register: usize, offset: u64, data: &[u8], @@ -111,24 +155,11 @@ impl PciRoot { if offset as usize + data.len() > 4 { return; } - - // Only support one bus. - if bus != 0 { - return; - } - - match device { - 0 => { - // If bus and device are both zero, then read from the root config. - self.root_configuration - .config_register_write(register, offset, data); - } - dev_num => { - // dev_num is 1-indexed here. - if let Some(d) = self.devices.get(dev_num - 1) { - d.lock().config_register_write(register, offset, data); - } - } + if address.is_root() { + self.root_configuration + .config_register_write(register, offset, data); + } else if let Some(d) = self.devices.get(&address) { + d.lock().config_register_write(register, offset, data); } } } @@ -155,10 +186,8 @@ impl PciConfigIo { return 0xffff_ffff; } - let (bus, device, function, register) = - parse_config_address(self.config_address & !0x8000_0000); - self.pci_root - .config_space_read(bus, device, function, register) + let (address, register) = PciAddress::from_config_address(self.config_address); + self.pci_root.config_space_read(address, register) } fn config_space_write(&mut self, offset: u64, data: &[u8]) { @@ -167,10 +196,9 @@ impl PciConfigIo { return; } - let (bus, device, function, register) = - parse_config_address(self.config_address & !0x8000_0000); + let (address, register) = PciAddress::from_config_address(self.config_address); self.pci_root - .config_space_write(bus, device, function, register, offset, data) + .config_space_write(address, register, offset, data) } fn set_config_address(&mut self, offset: u64, data: &[u8]) { @@ -242,15 +270,14 @@ impl PciConfigMmio { } fn config_space_read(&self, config_address: u32) -> u32 { - let (bus, device, function, register) = parse_config_address(config_address); - self.pci_root - .config_space_read(bus, device, function, register) + let (address, register) = PciAddress::from_config_address(config_address); + self.pci_root.config_space_read(address, register) } fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) { - let (bus, device, function, register) = parse_config_address(config_address); + let (address, register) = PciAddress::from_config_address(config_address); self.pci_root - .config_space_write(bus, device, function, register, offset, data) + .config_space_write(address, register, offset, data) } } @@ -283,24 +310,3 @@ impl BusDevice for PciConfigMmio { self.config_space_write(offset as u32, offset % 4, data) } } - -// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple. -fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) { - const BUS_NUMBER_OFFSET: usize = 16; - const BUS_NUMBER_MASK: u32 = 0x00ff; - const DEVICE_NUMBER_OFFSET: usize = 11; - const DEVICE_NUMBER_MASK: u32 = 0x1f; - const FUNCTION_NUMBER_OFFSET: usize = 8; - const FUNCTION_NUMBER_MASK: u32 = 0x07; - const REGISTER_NUMBER_OFFSET: usize = 2; - const REGISTER_NUMBER_MASK: u32 = 0x3f; - - let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize; - let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize; - let function_number = - ((config_address >> FUNCTION_NUMBER_OFFSET) & FUNCTION_NUMBER_MASK) as usize; - let register_number = - ((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize; - - (bus_number, device_number, function_number, register_number) -} diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs index 5c13d28..671e8cd 100644 --- a/devices/src/pci/vfio_pci.rs +++ b/devices/src/pci/vfio_pci.rs @@ -22,7 +22,7 @@ use crate::pci::msix::{ }; use crate::pci::pci_device::{Error as PciDeviceError, PciDevice}; -use crate::pci::{PciClassCode, PciInterruptPin}; +use crate::pci::{PciAddress, PciClassCode, PciInterruptPin}; use crate::vfio::{VfioDevice, VfioIrqType}; @@ -457,7 +457,7 @@ enum DeviceData { pub struct VfioPciDevice { device: Arc<VfioDevice>, config: VfioPciConfig, - pci_bus_dev: Option<(u8, u8)>, + pci_address: Option<PciAddress>, interrupt_evt: Option<EventFd>, interrupt_resample_evt: Option<EventFd>, mmio_regions: Vec<MmioInfo>, @@ -522,7 +522,7 @@ impl VfioPciDevice { VfioPciDevice { device: dev, config, - pci_bus_dev: None, + pci_address: None, interrupt_evt: None, interrupt_resample_evt: None, mmio_regions: Vec::new(), @@ -776,8 +776,8 @@ impl PciDevice for VfioPciDevice { "vfio pci device".to_string() } - fn assign_bus_dev(&mut self, bus: u8, device: u8) { - self.pci_bus_dev = Some((bus, device)); + fn assign_address(&mut self, address: PciAddress) { + self.pci_address = Some(address); } fn keep_fds(&self) -> Vec<RawFd> { @@ -816,9 +816,9 @@ impl PciDevice for VfioPciDevice { ) -> Result<Vec<(u64, u64)>, PciDeviceError> { let mut ranges = Vec::new(); let mut i = VFIO_PCI_BAR0_REGION_INDEX; - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_io_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_io_bars"); while i <= VFIO_PCI_ROM_REGION_INDEX { let mut low: u32 = 0xffffffff; @@ -854,8 +854,9 @@ impl PciDevice for VfioPciDevice { .allocate_with_align( size, Alloc::PciBar { - bus, - dev, + bus: address.bus, + dev: address.dev, + func: address.func, bar: i as u8, }, "vfio_bar".to_string(), @@ -914,16 +915,17 @@ impl PciDevice for VfioPciDevice { VFIO_REGION_TYPE_PCI_VENDOR_TYPE | (INTEL_VENDOR_ID as u32), VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, ) { - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_device_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_device_bars"); let bar_addr = resources .mmio_allocator(MmioType::Low) .allocate( size, Alloc::PciBar { - bus, - dev, + bus: address.bus, + dev: address.dev, + func: address.func, bar: (index * 4) as u8, }, "vfio_bar".to_string(), diff --git a/devices/src/register_space/register.rs b/devices/src/register_space/register.rs index c2f5184..2a4f551 100644 --- a/devices/src/register_space/register.rs +++ b/devices/src/register_space/register.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::boxed::Box; use std::cmp::{max, min, Ord, Ordering, PartialOrd}; use std::mem::size_of; diff --git a/devices/src/serial.rs b/devices/src/serial.rs index 6629d7e..2af988e 100644 --- a/devices/src/serial.rs +++ b/devices/src/serial.rs @@ -3,20 +3,16 @@ // found in the LICENSE file. use std::collections::VecDeque; -use std::fmt::{self, Display}; -use std::fs::File; -use std::io::{self, stdin, stdout, Read, Write}; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::path::PathBuf; -use std::str::FromStr; +use std::io::{self, Write}; +use std::os::unix::io::RawFd; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::mpsc::{channel, Receiver, TryRecvError}; use std::sync::Arc; use std::thread::{self}; -use sys_util::{error, read_raw_stdin, syslog, EventFd, Result}; +use sys_util::{error, EventFd, Result}; -use crate::BusDevice; +use crate::{BusDevice, SerialDevice}; const LOOP_SIZE: usize = 0x40; @@ -62,187 +58,6 @@ const DEFAULT_MODEM_CONTROL: u8 = MCR_OUT2_BIT; const DEFAULT_MODEM_STATUS: u8 = MSR_DSR_BIT | MSR_CTS_BIT | MSR_DCD_BIT; const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps -#[derive(Debug)] -pub enum Error { - CloneEventFd(sys_util::Error), - InvalidSerialType(String), - PathRequired, - FileError(std::io::Error), - Unimplemented(SerialType), -} - -impl Display for Error { - #[remain::check] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Error::*; - - #[sorted] - match self { - CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e), - FileError(e) => write!(f, "Unable to open/create file: {}", e), - InvalidSerialType(e) => write!(f, "invalid serial type: {}", e), - PathRequired => write!(f, "serial device type file requires a path"), - Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()), - } - } -} - -/// Enum for possible type of serial devices -#[derive(Debug)] -pub enum SerialType { - File, - Stdout, - Sink, - Syslog, - UnixSocket, // NOT IMPLEMENTED -} - -impl Display for SerialType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match &self { - SerialType::File => "File".to_string(), - SerialType::Stdout => "Stdout".to_string(), - SerialType::Sink => "Sink".to_string(), - SerialType::Syslog => "Syslog".to_string(), - SerialType::UnixSocket => "UnixSocket".to_string(), - }; - - write!(f, "{}", s) - } -} - -impl FromStr for SerialType { - type Err = Error; - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - match s { - "file" | "File" => Ok(SerialType::File), - "stdout" | "Stdout" => Ok(SerialType::Stdout), - "sink" | "Sink" => Ok(SerialType::Sink), - "syslog" | "Syslog" => Ok(SerialType::Syslog), - "unix" | "UnixSocket" => Ok(SerialType::UnixSocket), - _ => Err(Error::InvalidSerialType(s.to_string())), - } - } -} - -/// Holds the parameters for a serial device -#[derive(Debug)] -pub struct SerialParameters { - pub type_: SerialType, - pub path: Option<PathBuf>, - pub num: u8, - pub console: bool, - pub stdin: bool, -} - -impl SerialParameters { - /// Helper function to create a serial device from the defined parameters. - /// - /// # Arguments - /// * `evt_fd` - eventfd used for interrupt events - /// * `keep_fds` - Vector of FDs required by this device if it were sandboxed in a child - /// process. `evt_fd` will always be added to this vector by this function. - pub fn create_serial_device( - &self, - evt_fd: &EventFd, - keep_fds: &mut Vec<RawFd>, - ) -> std::result::Result<Serial, Error> { - let evt_fd = evt_fd.try_clone().map_err(Error::CloneEventFd)?; - keep_fds.push(evt_fd.as_raw_fd()); - let input: Option<Box<dyn io::Read + Send>> = if self.stdin { - keep_fds.push(stdin().as_raw_fd()); - // This wrapper is used in place of the libstd native version because we don't want - // buffering for stdin. - struct StdinWrapper; - impl io::Read for StdinWrapper { - fn read(&mut self, out: &mut [u8]) -> io::Result<usize> { - read_raw_stdin(out).map_err(|e| e.into()) - } - } - Some(Box::new(StdinWrapper)) - } else { - None - }; - match self.type_ { - SerialType::Stdout => { - keep_fds.push(stdout().as_raw_fd()); - Ok(Serial::new(evt_fd, input, Some(Box::new(stdout())))) - } - SerialType::Sink => Ok(Serial::new(evt_fd, input, None)), - SerialType::Syslog => { - syslog::push_fds(keep_fds); - Ok(Serial::new( - evt_fd, - input, - Some(Box::new(syslog::Syslogger::new( - syslog::Priority::Info, - syslog::Facility::Daemon, - ))), - )) - } - SerialType::File => match &self.path { - None => Err(Error::PathRequired), - Some(path) => { - let file = File::create(path.as_path()).map_err(Error::FileError)?; - keep_fds.push(file.as_raw_fd()); - Ok(Serial::new(evt_fd, input, Some(Box::new(file)))) - } - }, - SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)), - } - } -} - -// Structure for holding the default configuration of the serial devices. -pub const DEFAULT_SERIAL_PARAMS: [SerialParameters; 4] = [ - SerialParameters { - type_: SerialType::Stdout, - path: None, - num: 1, - console: true, - stdin: true, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - num: 2, - console: false, - stdin: false, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - num: 3, - console: false, - stdin: false, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - num: 4, - console: false, - stdin: false, - }, -]; - -/// Address for Serial ports in x86 -pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8]; - -/// String representations of serial devices -pub const SERIAL_TTY_STRINGS: [&str; 4] = ["ttyS0", "ttyS1", "ttyS2", "ttyS3"]; - -/// Helper function to get the tty string of a serial device based on the port number. Will default -/// to ttyS0 if an invalid number is given. -pub fn get_serial_tty_string(stdio_serial_num: u8) -> String { - match stdio_serial_num { - 1 => SERIAL_TTY_STRINGS[0].to_string(), - 2 => SERIAL_TTY_STRINGS[1].to_string(), - 3 => SERIAL_TTY_STRINGS[2].to_string(), - 4 => SERIAL_TTY_STRINGS[3].to_string(), - _ => SERIAL_TTY_STRINGS[0].to_string(), - } -} - /// Emulates serial COM ports commonly seen on x86 I/O ports 0x3f8/0x2f8/0x3e8/0x2e8. /// /// This can optionally write the guest's output to a Write trait object. To send input to the @@ -267,11 +82,12 @@ pub struct Serial { out: Option<Box<dyn io::Write + Send>>, } -impl Serial { +impl SerialDevice for Serial { fn new( interrupt_evt: EventFd, input: Option<Box<dyn io::Read + Send>>, out: Option<Box<dyn io::Write + Send>>, + _keep_fds: Vec<RawFd>, ) -> Serial { Serial { interrupt_enable: Default::default(), @@ -289,28 +105,9 @@ impl Serial { out, } } +} - /// Constructs a Serial port ready for input and output. - /// - /// The stream `input` should not block, instead returning 0 bytes if are no bytes available. - pub fn new_in_out( - interrupt_evt: EventFd, - input: Box<dyn io::Read + Send>, - out: Box<dyn io::Write + Send>, - ) -> Serial { - Self::new(interrupt_evt, Some(input), Some(out)) - } - - /// Constructs a Serial port ready for output but not input. - pub fn new_out(interrupt_evt: EventFd, out: Box<dyn io::Write + Send>) -> Serial { - Self::new(interrupt_evt, None, Some(out)) - } - - /// Constructs a Serial port with no connected input or output. - pub fn new_sink(interrupt_evt: EventFd) -> Serial { - Self::new(interrupt_evt, None, None) - } - +impl Serial { /// Queues raw bytes for the guest to read and signals the interrupt if the line status would /// change. These bytes will be read by the guest before any bytes from the input stream that /// have not already been queued. @@ -611,7 +408,12 @@ mod tests { let intr_evt = EventFd::new().unwrap(); let serial_out = SharedBuffer::new(); - let mut serial = Serial::new_out(intr_evt, Box::new(serial_out.clone())); + let mut serial = Serial::new( + intr_evt, + None, + Some(Box::new(serial_out.clone())), + Vec::new(), + ); serial.write(DATA as u64, &['a' as u8]); serial.write(DATA as u64, &['b' as u8]); @@ -627,8 +429,12 @@ mod tests { let intr_evt = EventFd::new().unwrap(); let serial_out = SharedBuffer::new(); - let mut serial = - Serial::new_out(intr_evt.try_clone().unwrap(), Box::new(serial_out.clone())); + let mut serial = Serial::new( + intr_evt.try_clone().unwrap(), + None, + Some(Box::new(serial_out.clone())), + Vec::new(), + ); serial.write(IER as u64, &[IER_RECV_BIT]); serial diff --git a/devices/src/serial_device.rs b/devices/src/serial_device.rs new file mode 100644 index 0000000..9377556 --- /dev/null +++ b/devices/src/serial_device.rs @@ -0,0 +1,19 @@ +// 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 std::io; +use std::os::unix::io::RawFd; + +use sys_util::EventFd; + +/// Abstraction over serial-like devices that can be created given an event and optional input and +/// output streams. +pub trait SerialDevice { + fn new( + interrupt_evt: EventFd, + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, + keep_fds: Vec<RawFd>, + ) -> Self; +} diff --git a/devices/src/usb/host_backend/host_device.rs b/devices/src/usb/host_backend/host_device.rs index 196fa50..8f8723d 100644 --- a/devices/src/usb/host_backend/host_device.rs +++ b/devices/src/usb/host_backend/host_device.rs @@ -140,30 +140,21 @@ impl HostDevice { return Ok(()); } - // Default buffer size for control data transfer. - const CONTROL_DATA_BUFFER_SIZE: usize = 1024; - - // Buffer type for control transfer. The first 8 bytes is a UsbRequestSetup struct. - #[derive(Copy, Clone)] - #[repr(C, packed)] - struct ControlTransferBuffer { - pub setup: UsbRequestSetup, - pub data: [u8; CONTROL_DATA_BUFFER_SIZE], - } - - // Safe because it only has data and has no implicit padding. - unsafe impl DataInit for ControlTransferBuffer {} + // Allocate a buffer for the control transfer. + // This buffer will hold a UsbRequestSetup struct followed by the data. + let control_buffer_len = + mem::size_of::<UsbRequestSetup>() + self.control_request_setup.length as usize; + let mut control_buffer = vec![0u8; control_buffer_len]; - let mut control_request = ControlTransferBuffer { - setup: self.control_request_setup, - data: [0; CONTROL_DATA_BUFFER_SIZE], - }; + // Copy the control request header. + control_buffer[..mem::size_of::<UsbRequestSetup>()] + .copy_from_slice(self.control_request_setup.as_slice()); let direction = self.control_request_setup.get_direction(); let buffer = if direction == ControlRequestDataPhaseTransferDirection::HostToDevice { if let Some(buffer) = buffer { buffer - .read(&mut control_request.data) + .read(&mut control_buffer[mem::size_of::<UsbRequestSetup>()..]) .map_err(Error::ReadBuffer)?; } // buffer is consumed here for HostToDevice transfers. @@ -173,8 +164,6 @@ impl HostDevice { buffer }; - let control_buffer = control_request.as_slice().to_vec(); - let mut control_transfer = Transfer::new_control(control_buffer).map_err(Error::CreateTransfer)?; diff --git a/devices/src/usb/xhci/event_ring.rs b/devices/src/usb/xhci/event_ring.rs index fcaff23..1742c77 100644 --- a/devices/src/usb/xhci/event_ring.rs +++ b/devices/src/usb/xhci/event_ring.rs @@ -3,7 +3,6 @@ // found in the LICENSE file. use data_model::DataInit; -use std; use std::fmt::{self, Display}; use std::mem::size_of; use std::sync::atomic::{fence, Ordering}; diff --git a/devices/src/usb/xhci/xhci_abi.rs b/devices/src/usb/xhci/xhci_abi.rs index 5a8748e..e9be3c3 100644 --- a/devices/src/usb/xhci/xhci_abi.rs +++ b/devices/src/usb/xhci/xhci_abi.rs @@ -8,8 +8,6 @@ use data_model::DataInit; use std::fmt::{self, Display}; use sys_util::GuestAddress; -use std; - #[derive(Debug)] pub enum Error { UnknownTrbType(BitFieldError), diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs index b77a73a..e357ae3 100644 --- a/devices/src/usb/xhci/xhci_controller.rs +++ b/devices/src/usb/xhci/xhci_controller.rs @@ -3,8 +3,8 @@ // found in the LICENSE file. use crate::pci::{ - PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, - PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass, + PciAddress, PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, + PciHeaderType, PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass, }; use crate::register_space::{Register, RegisterSpace}; use crate::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; @@ -94,7 +94,7 @@ enum XhciControllerState { /// xHCI PCI interface implementation. pub struct XhciController { config_regs: PciConfiguration, - pci_bus_dev: Option<(u8, u8)>, + pci_address: Option<PciAddress>, mem: GuestMemory, state: XhciControllerState, } @@ -114,7 +114,7 @@ impl XhciController { ); XhciController { config_regs, - pci_bus_dev: None, + pci_address: None, mem, state: XhciControllerState::Created { device_provider: usb_provider, @@ -166,8 +166,8 @@ impl PciDevice for XhciController { "xhci controller".to_owned() } - fn assign_bus_dev(&mut self, bus: u8, device: u8) { - self.pci_bus_dev = Some((bus, device)); + fn assign_address(&mut self, address: PciAddress) { + self.pci_address = Some(address); } fn keep_fds(&self) -> Vec<RawFd> { @@ -206,15 +206,20 @@ impl PciDevice for XhciController { &mut self, resources: &mut SystemAllocator, ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> { - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_io_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_io_bars"); // xHCI spec 5.2.1. let bar0_addr = resources .mmio_allocator(MmioType::Low) .allocate_with_align( XHCI_BAR0_SIZE, - Alloc::PciBar { bus, dev, bar: 0 }, + Alloc::PciBar { + bus: address.bus, + dev: address.dev, + func: address.func, + bar: 0, + }, "xhci_bar0".to_string(), XHCI_BAR0_SIZE, ) diff --git a/devices/src/virtio/balloon.rs b/devices/src/virtio/balloon.rs index 3e6d602..b549258 100644 --- a/devices/src/virtio/balloon.rs +++ b/devices/src/virtio/balloon.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::fmt::{self, Display}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::atomic::{AtomicUsize, Ordering}; diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs index 38f5bf1..50a5a76 100644 --- a/devices/src/virtio/console.rs +++ b/devices/src/virtio/console.rs @@ -13,6 +13,7 @@ use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken}; use super::{ copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_CONSOLE, VIRTIO_F_VERSION_1, }; +use crate::SerialDevice; const QUEUE_SIZE: u16 = 256; @@ -303,8 +304,9 @@ pub struct Console { keep_fds: Vec<RawFd>, } -impl Console { +impl SerialDevice for Console { fn new( + _evt_fd: EventFd, input: Option<Box<dyn io::Read + Send>>, output: Option<Box<dyn io::Write + Send>>, keep_fds: Vec<RawFd>, @@ -317,25 +319,6 @@ impl Console { keep_fds, } } - - /// Constructs a console with input and output streams. - pub fn new_in_out( - input: Box<dyn io::Read + Send>, - out: Box<dyn io::Write + Send>, - keep_fds: Vec<RawFd>, - ) -> Console { - Self::new(Some(input), Some(out), keep_fds) - } - - /// Constructs a console with an output stream but no input. - pub fn new_out(out: Box<dyn io::Write + Send>, keep_fds: Vec<RawFd>) -> Console { - Self::new(None, Some(out), keep_fds) - } - - /// Constructs a console with no connected input or output. - pub fn new_sink() -> Console { - Self::new(None, None, Vec::new()) - } } impl Drop for Console { diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs index 990f147..f90264a 100644 --- a/devices/src/virtio/descriptor_utils.rs +++ b/devices/src/virtio/descriptor_utils.rs @@ -467,9 +467,17 @@ impl<'a> Writer<'a> { self.write_all(val.as_slice()) } + /// Writes all objects produced by `iter` into the descriptor chain buffer. Unlike `consume`, + /// this doesn't require the values to be stored in an intermediate collection first. It also + /// allows callers to choose which elements in a collection to write, for example by using the + /// `filter` or `take` methods of the `Iterator` trait. + pub fn write_iter<T: DataInit, I: Iterator<Item = T>>(&mut self, iter: I) -> io::Result<()> { + iter.map(|v| self.write_obj(v)).collect() + } + /// Writes a collection of objects into the descriptor chain buffer. pub fn consume<T: DataInit, C: IntoIterator<Item = T>>(&mut self, vals: C) -> io::Result<()> { - vals.into_iter().map(|v| self.write_obj(v)).collect() + self.write_iter(vals.into_iter()) } /// Returns number of bytes available for writing. May return an error if the combined diff --git a/devices/src/virtio/fs/filesystem.rs b/devices/src/virtio/fs/filesystem.rs index eb9726c..474a278 100644 --- a/devices/src/virtio/fs/filesystem.rs +++ b/devices/src/virtio/fs/filesystem.rs @@ -9,8 +9,6 @@ use std::io; use std::mem; use std::time::Duration; -use libc; - use crate::virtio::fs::fuse; pub use fuse::{FsOptions, IoctlFlags, IoctlIovec, OpenOptions, SetattrValid, ROOT_ID}; @@ -77,7 +75,7 @@ pub struct DirEntry<'a> { /// The name of this directory entry. There are no requirements for the contents of this field /// and any sequence of bytes is considered valid. - pub name: &'a [u8], + pub name: &'a CStr, } /// A reply to a `getxattr` method call. diff --git a/devices/src/virtio/fs/fuse.rs b/devices/src/virtio/fs/fuse.rs index 5c531cf..981596b 100644 --- a/devices/src/virtio/fs/fuse.rs +++ b/devices/src/virtio/fs/fuse.rs @@ -7,7 +7,6 @@ use std::mem; use bitflags::bitflags; use data_model::DataInit; use enumn::N; -use libc; /// Version number of this interface. pub const KERNEL_VERSION: u32 = 7; diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs index d2034ba..bcc7c5d 100644 --- a/devices/src/virtio/fs/passthrough.rs +++ b/devices/src/virtio/fs/passthrough.rs @@ -15,7 +15,6 @@ use std::sync::Arc; use std::time::Duration; use data_model::DataInit; -use libc; use sync::Mutex; use sys_util::{error, ioctl_ior_nr, ioctl_iow_nr, ioctl_with_mut_ptr, ioctl_with_ptr}; @@ -26,8 +25,6 @@ use crate::virtio::fs::filesystem::{ use crate::virtio::fs::fuse; use crate::virtio::fs::multikey::MultikeyBTreeMap; -const CURRENT_DIR_CSTR: &[u8] = b".\0"; -const PARENT_DIR_CSTR: &[u8] = b"..\0"; const EMPTY_CSTR: &[u8] = b"\0"; const ROOT_CSTR: &[u8] = b"/\0"; const PROC_CSTR: &[u8] = b"/proc\0"; @@ -199,6 +196,20 @@ fn stat(f: &File) -> io::Result<libc::stat64> { } } +// Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b` +// doesn't contain any '\0' bytes. +fn strip_padding(b: &[u8]) -> &CStr { + // It would be nice if we could use memchr here but that's locked behind an unstable gate. + let pos = b + .iter() + .position(|&c| c == 0) + .expect("`b` doesn't contain any nul bytes"); + + // Safe because we are creating this string with the first nul-byte we found so we can + // guarantee that it is nul-terminated and doesn't contain any interior nuls. + unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) } +} + /// The caching policy that the file system should report to the FUSE client. By default the FUSE /// protocol uses close-to-open consistency. This means that any cached contents of the file are /// invalidated the next time that file is opened. @@ -576,19 +587,15 @@ impl PassthroughFs { let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>(); debug_assert!(namelen <= back.len(), "back is smaller than `namelen`"); - let name = &back[..namelen]; - let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { - // We don't want to report the "." and ".." entries. However, returning `Ok(0)` will - // break the loop so return `Ok` with a non-zero value instead. - Ok(1) - } else { - add_entry(DirEntry { - ino: dirent64.d_ino, - offset: dirent64.d_off as u64, - type_: dirent64.d_ty as u32, - name, - }) - }; + // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so + // we need to strip those off here. + let name = strip_padding(&back[..namelen]); + let res = add_entry(DirEntry { + ino: dirent64.d_ino, + offset: dirent64.d_off as u64, + type_: dirent64.d_ty as u32, + name, + }); debug_assert!( rem.len() >= dirent64.d_reclen as usize, @@ -806,7 +813,8 @@ impl FileSystem for PassthroughFs { }), ); - let mut opts = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO; + let mut opts = + FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO | FsOptions::EXPORT_SUPPORT; if self.cfg.writeback && capable.contains(FsOptions::WRITEBACK_CACHE) { opts |= FsOptions::WRITEBACK_CACHE; self.writeback.store(true, Ordering::Relaxed); @@ -933,16 +941,38 @@ impl FileSystem for PassthroughFs { F: FnMut(DirEntry, Entry) -> io::Result<usize>, { self.do_readdir(inode, handle, size, offset, |dir_entry| { - // Safe because the kernel guarantees that the buffer is nul-terminated. Additionally, - // the kernel will pad the name with '\0' bytes up to 8-byte alignment and there's no - // way for us to know exactly how many padding bytes there are. This would cause - // `CStr::from_bytes_with_nul` to return an error because it would think there are - // interior '\0' bytes. We trust the kernel to provide us with properly formatted data - // so we'll just skip the checks here. - let name = unsafe { CStr::from_bytes_with_nul_unchecked(dir_entry.name) }; - let entry = self.do_lookup(inode, name)?; - - add_entry(dir_entry, entry) + let name = dir_entry.name.to_bytes(); + let entry = if name == b"." || name == b".." { + // Don't do lookups on the current directory or the parent directory. Safe because + // this only contains integer fields and any value is valid. + let mut attr = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() }; + attr.st_ino = dir_entry.ino; + attr.st_mode = dir_entry.type_; + + // We use 0 for the inode value to indicate a negative entry. + Entry { + inode: 0, + generation: 0, + attr, + attr_timeout: Duration::from_secs(0), + entry_timeout: Duration::from_secs(0), + } + } else { + self.do_lookup(inode, dir_entry.name)? + }; + + let entry_inode = entry.inode; + add_entry(dir_entry, entry).map_err(|e| { + if entry_inode != 0 { + // Undo the `do_lookup` for this inode since we aren't going to report it to + // the kernel. If `entry_inode` was 0 then that means this was the "." or + // ".." entry and there wasn't a lookup in the first place. + let mut inodes = self.inodes.lock(); + forget_one(&mut inodes, entry_inode, 1); + } + + e + }) }) } @@ -1747,3 +1777,29 @@ impl FileSystem for PassthroughFs { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn padded_cstrings() { + assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b"."); + assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b".."); + assert_eq!( + strip_padding(b"normal cstring\0").to_bytes(), + b"normal cstring" + ); + assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b""); + assert_eq!( + strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(), + b"interior" + ); + } + + #[test] + #[should_panic(expected = "`b` doesn't contain any nul bytes")] + fn no_nul_byte() { + strip_padding(b"no nul bytes in string"); + } +} diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs index c9025f2..33b7c98 100644 --- a/devices/src/virtio/fs/server.rs +++ b/devices/src/virtio/fs/server.rs @@ -8,7 +8,6 @@ use std::io::{self, Read, Write}; use std::mem::size_of; use data_model::DataInit; -use libc; use sys_util::error; use crate::virtio::fs::filesystem::{ @@ -19,7 +18,7 @@ use crate::virtio::fs::fuse::*; use crate::virtio::fs::{Error, Result}; use crate::virtio::{Reader, Writer}; -const MAX_BUFFER_SIZE: u32 = (1 << 20); +const MAX_BUFFER_SIZE: u32 = 1 << 20; const DIRENT_PADDING: [u8; 8] = [0; 8]; struct ZCReader<'a>(Reader<'a>); @@ -1369,12 +1368,14 @@ fn add_dirent( d: DirEntry, entry: Option<Entry>, ) -> io::Result<usize> { - if d.name.len() > ::std::u32::MAX as usize { + // Strip the trailing '\0'. + let name = d.name.to_bytes(); + if name.len() > ::std::u32::MAX as usize { return Err(io::Error::from_raw_os_error(libc::EOVERFLOW)); } let dirent_len = size_of::<Dirent>() - .checked_add(d.name.len()) + .checked_add(name.len()) .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; // Directory entries must be padded to 8-byte alignment. If adding 7 causes @@ -1402,12 +1403,12 @@ fn add_dirent( let dirent = Dirent { ino: d.ino, off: d.offset, - namelen: d.name.len() as u32, + namelen: name.len() as u32, type_: d.type_, }; cursor.write_all(dirent.as_slice())?; - cursor.write_all(d.name)?; + cursor.write_all(name)?; // We know that `dirent_len` <= `padded_dirent_len` due to the check above // so there's no need for checked arithmetic. diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index fec2904..b305089 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -42,7 +42,9 @@ 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 crate::pci::{ + PciAddress, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, +}; use vm_control::VmMemoryControlRequestSocket; @@ -1203,10 +1205,11 @@ impl VirtioDevice for Gpu { } // Require 1 BAR for mapping 3D buffers - fn get_device_bars(&mut self, bus: u8, dev: u8) -> Vec<PciBarConfiguration> { + fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> { self.pci_bar = Some(Alloc::PciBar { - bus, - dev, + bus: address.bus, + dev: address.dev, + func: address.func, bar: GPU_BAR_NUM, }); vec![PciBarConfiguration::new( diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs index 3df6975..5605dd3 100644 --- a/devices/src/virtio/gpu/protocol.rs +++ b/devices/src/virtio/gpu/protocol.rs @@ -137,7 +137,7 @@ pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str { } } -pub const VIRTIO_GPU_FLAG_FENCE: u32 = (1 << 0); +pub const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0; #[derive(Copy, Clone, Debug, Default)] #[repr(C)] @@ -336,7 +336,7 @@ pub struct virtio_gpu_transfer_host_3d { unsafe impl DataInit for virtio_gpu_transfer_host_3d {} /* VIRTIO_GPU_CMD_RESOURCE_CREATE_3D */ -pub const VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP: u32 = (1 << 0); +pub const VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP: u32 = 1 << 0; #[derive(Copy, Clone, Debug, Default)] #[repr(C)] pub struct virtio_gpu_resource_create_3d { diff --git a/devices/src/virtio/input/constants.rs b/devices/src/virtio/input/constants.rs index 84799b2..7973312 100644 --- a/devices/src/virtio/input/constants.rs +++ b/devices/src/virtio/input/constants.rs @@ -12,7 +12,7 @@ pub const INPUT_PROP_POINTING_STICK: u16 = 0x05; pub const INPUT_PROP_ACCELEROMETER: u16 = 0x06; pub const INPUT_PROP_MAX: u16 = 0x1f; -pub const INPUT_PROP_CNT: u16 = (INPUT_PROP_MAX + 1); +pub const INPUT_PROP_CNT: u16 = INPUT_PROP_MAX + 1; pub const EV_SYN: u16 = 0x00; pub const EV_KEY: u16 = 0x01; @@ -638,7 +638,7 @@ pub const BTN_TRIGGER_HAPPY40: u16 = 0x2e7; pub const KEY_MIN_INTERESTING: u16 = KEY_MUTE; pub const KEY_MAX: u16 = 0x2ff; -pub const KEY_CNT: u16 = (KEY_MAX + 1); +pub const KEY_CNT: u16 = KEY_MAX + 1; pub const REL_X: u16 = 0x00; pub const REL_Y: u16 = 0x01; @@ -651,7 +651,7 @@ pub const REL_DIAL: u16 = 0x07; pub const REL_WHEEL: u16 = 0x08; pub const REL_MISC: u16 = 0x09; pub const REL_MAX: u16 = 0x0f; -pub const REL_CNT: u16 = (REL_MAX + 1); +pub const REL_CNT: u16 = REL_MAX + 1; pub const ABS_X: u16 = 0x00; pub const ABS_Y: u16 = 0x01; @@ -699,7 +699,7 @@ pub const ABS_MT_TOOL_X: u16 = 0x3c; pub const ABS_MT_TOOL_Y: u16 = 0x3d; pub const ABS_MAX: u16 = 0x3f; -pub const ABS_CNT: u16 = (ABS_MAX + 1); +pub const ABS_CNT: u16 = ABS_MAX + 1; pub const MSC_SERIAL: u16 = 0x00; pub const MSC_PULSELED: u16 = 0x01; @@ -708,7 +708,7 @@ pub const MSC_RAW: u16 = 0x03; pub const MSC_SCAN: u16 = 0x04; pub const MSC_TIMESTAMP: u16 = 0x05; pub const MSC_MAX: u16 = 0x07; -pub const MSC_CNT: u16 = (MSC_MAX + 1); +pub const MSC_CNT: u16 = MSC_MAX + 1; pub const LED_NUML: u16 = 0x00; pub const LED_CAPSL: u16 = 0x01; @@ -722,12 +722,12 @@ pub const LED_MISC: u16 = 0x08; pub const LED_MAIL: u16 = 0x09; pub const LED_CHARGING: u16 = 0x0a; pub const LED_MAX: u16 = 0x0f; -pub const LED_CNT: u16 = (LED_MAX + 1); +pub const LED_CNT: u16 = LED_MAX + 1; pub const REP_DELAY: u16 = 0x00; pub const REP_PERIOD: u16 = 0x01; pub const REP_MAX: u16 = 0x01; -pub const REP_CNT: u16 = (REP_MAX + 1); +pub const REP_CNT: u16 = REP_MAX + 1; // Should match linux/virtio_input.h pub const VIRTIO_INPUT_CFG_UNSET: u8 = 0x00; diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs index a15ab03..8e9aa9e 100644 --- a/devices/src/virtio/net.rs +++ b/devices/src/virtio/net.rs @@ -13,7 +13,6 @@ use std::sync::Arc; use std::thread; use data_model::{DataInit, Le16, Le64}; -use net_sys; use net_util::{Error as TapError, MacAddress, TapT}; use sys_util::Error as SysError; use sys_util::{error, warn, EventFd, GuestMemory, PollContext, PollToken, WatchingEvents}; diff --git a/devices/src/virtio/p9.rs b/devices/src/virtio/p9.rs index 5e483c3..7854c39 100644 --- a/devices/src/virtio/p9.rs +++ b/devices/src/virtio/p9.rs @@ -10,7 +10,6 @@ use std::path::{Path, PathBuf}; use std::result; use std::thread; -use p9; use sys_util::{error, warn, Error as SysError, EventFd, GuestMemory, PollContext, PollToken}; use virtio_sys::vhost::VIRTIO_F_VERSION_1; diff --git a/devices/src/virtio/pmem.rs b/devices/src/virtio/pmem.rs index 499e110..cbeecc3 100644 --- a/devices/src/virtio/pmem.rs +++ b/devices/src/virtio/pmem.rs @@ -8,8 +8,8 @@ use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::thread; -use sys_util::Result as SysResult; use sys_util::{error, EventFd, GuestAddress, GuestMemory, PollContext, PollToken}; +use sys_util::{Error as SysError, Result as SysResult}; use data_model::{DataInit, Le32, Le64}; @@ -89,6 +89,7 @@ struct Worker { memory: GuestMemory, pmem_device_socket: VmMsyncRequestSocket, mapping_arena_slot: u32, + mapping_size: usize, } impl Worker { @@ -98,6 +99,7 @@ impl Worker { let request = VmMsyncRequest::MsyncArena { slot: self.mapping_arena_slot, offset: 0, // The pmem backing file is always at offset 0 in the arena. + size: self.mapping_size, }; if let Err(e) = self.pmem_device_socket.send(&request) { @@ -235,6 +237,10 @@ impl Pmem { mapping_size: u64, pmem_device_socket: Option<VmMsyncRequestSocket>, ) -> SysResult<Pmem> { + if mapping_size > usize::max_value() as u64 { + return Err(SysError::new(libc::EOVERFLOW)); + } + Ok(Pmem { kill_event: None, worker_thread: None, @@ -308,6 +314,8 @@ impl VirtioDevice for Pmem { let queue_event = queue_events.remove(0); let mapping_arena_slot = self.mapping_arena_slot; + // We checked that this fits in a usize in `Pmem::new`. + let mapping_size = self.mapping_size as usize; if let Some(pmem_device_socket) = self.pmem_device_socket.take() { let (self_kill_event, kill_event) = @@ -329,6 +337,7 @@ impl VirtioDevice for Pmem { queue, pmem_device_socket, mapping_arena_slot, + mapping_size, }; worker.run(queue_event, kill_event); }); diff --git a/devices/src/virtio/rng.rs b/devices/src/virtio/rng.rs index 9ce8dae..3897ee3 100644 --- a/devices/src/virtio/rng.rs +++ b/devices/src/virtio/rng.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::fmt::{self, Display}; use std::fs::File; use std::io; diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs index 95f7092..727c634 100644 --- a/devices/src/virtio/tpm.rs +++ b/devices/src/virtio/tpm.rs @@ -12,7 +12,6 @@ use std::path::PathBuf; use std::thread; use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken}; -use tpm2; use super::{ DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_TPM, diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs index 86ed81e..4d7623f 100644 --- a/devices/src/virtio/vhost/mod.rs +++ b/devices/src/virtio/vhost/mod.rs @@ -4,7 +4,6 @@ //! Implements vhost-based virtio devices. -use std; use std::fmt::{self, Display}; use net_util::Error as TapError; diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs index 681cfe8..ce13226 100644 --- a/devices/src/virtio/vhost/net.rs +++ b/devices/src/virtio/vhost/net.rs @@ -7,7 +7,6 @@ use std::net::Ipv4Addr; use std::os::unix::io::{AsRawFd, RawFd}; use std::thread; -use net_sys; use net_util::{MacAddress, TapT}; use sys_util::{error, warn, EventFd, GuestMemory}; diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs index 6eb5548..7c07651 100644 --- a/devices/src/virtio/virtio_device.rs +++ b/devices/src/virtio/virtio_device.rs @@ -7,7 +7,7 @@ use std::os::unix::io::RawFd; use sys_util::{EventFd, GuestMemory}; use super::*; -use crate::pci::{MsixStatus, PciBarConfiguration, PciCapability}; +use crate::pci::{MsixStatus, PciAddress, PciBarConfiguration, PciCapability}; /// Trait for virtio devices to be driven by a virtio transport. /// @@ -75,7 +75,7 @@ pub trait VirtioDevice: Send { } /// Returns any additional BAR configuration required by the device. - fn get_device_bars(&mut self, _bus: u8, _dev: u8) -> Vec<PciBarConfiguration> { + fn get_device_bars(&mut self, _address: PciAddress) -> Vec<PciBarConfiguration> { Vec::new() } diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs index e63abe9..eb91e58 100644 --- a/devices/src/virtio/virtio_pci_device.rs +++ b/devices/src/virtio/virtio_pci_device.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -16,10 +15,10 @@ use sys_util::{warn, EventFd, GuestMemory, Result}; use super::*; use crate::pci::{ - MsixCap, MsixConfig, PciBarConfiguration, PciCapability, PciCapabilityID, PciClassCode, - PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin, PciSubclass, + MsixCap, MsixConfig, PciAddress, PciBarConfiguration, PciCapability, PciCapabilityID, + PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin, + PciSubclass, }; - use vm_control::VmIrqRequestSocket; use self::virtio_pci_common_config::VirtioPciCommonConfig; @@ -212,7 +211,7 @@ const VIRTIO_PCI_DEVICE_ID_BASE: u16 = 0x1040; // Add to device type to get devi /// transport for virtio devices. pub struct VirtioPciDevice { config_regs: PciConfiguration, - pci_bus_dev: Option<(u8, u8)>, + pci_address: Option<PciAddress>, device: Box<dyn VirtioDevice>, device_activated: bool, @@ -270,7 +269,7 @@ impl VirtioPciDevice { Ok(VirtioPciDevice { config_regs, - pci_bus_dev: None, + pci_address: None, device, device_activated: false, interrupt_status: Arc::new(AtomicUsize::new(0)), @@ -393,8 +392,8 @@ impl PciDevice for VirtioPciDevice { format!("virtio-pci ({})", self.device.debug_label()) } - fn assign_bus_dev(&mut self, bus: u8, device: u8) { - self.pci_bus_dev = Some((bus, device)); + fn assign_address(&mut self, address: PciAddress) { + self.pci_address = Some(address); } fn keep_fds(&self) -> Vec<RawFd> { @@ -426,16 +425,21 @@ impl PciDevice for VirtioPciDevice { &mut self, resources: &mut SystemAllocator, ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> { - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_io_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_io_bars"); // Allocate one bar for the structures pointed to by the capability structures. let mut ranges = Vec::new(); let settings_config_addr = resources .mmio_allocator(MmioType::Low) .allocate_with_align( CAPABILITY_BAR_SIZE, - Alloc::PciBar { bus, dev, bar: 0 }, + Alloc::PciBar { + bus: address.bus, + dev: address.dev, + func: address.func, + bar: 0, + }, format!( "virtio-{}-cap_bar", type_to_str(self.device.device_type()).unwrap_or("?") @@ -464,18 +468,19 @@ impl PciDevice for VirtioPciDevice { &mut self, resources: &mut SystemAllocator, ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> { - let (bus, dev) = self - .pci_bus_dev - .expect("assign_bus_dev must be called prior to allocate_device_bars"); + let address = self + .pci_address + .expect("assign_address must be called prior to allocate_device_bars"); let mut ranges = Vec::new(); - for config in self.device.get_device_bars(bus, dev) { + for config in self.device.get_device_bars(address) { let device_addr = resources .mmio_allocator(MmioType::High) .allocate_with_align( config.get_size(), Alloc::PciBar { - bus, - dev, + bus: address.bus, + dev: address.dev, + func: address.func, bar: config.get_register_index() as u8, }, format!( diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs index 95075b4..517a22e 100644 --- a/devices/src/virtio/wl.rs +++ b/devices/src/virtio/wl.rs @@ -47,7 +47,7 @@ use std::thread; use std::time::Duration; #[cfg(feature = "wl-dmabuf")] -use libc::{dup, EBADF, EINVAL}; +use libc::{EBADF, EINVAL}; use data_model::VolatileMemoryError; use data_model::*; @@ -543,9 +543,6 @@ impl fmt::Debug for WlVfd { impl WlVfd { fn connect<P: AsRef<Path>>(path: P) -> WlResult<WlVfd> { let socket = UnixStream::connect(path).map_err(WlError::SocketConnect)?; - socket - .set_nonblocking(true) - .map_err(WlError::SocketNonBlock)?; let mut vfd = WlVfd::default(); vfd.socket = Some(socket); Ok(vfd) @@ -587,15 +584,13 @@ impl WlVfd { })?; match allocate_and_register_gpu_memory_response { VmMemoryResponse::AllocateAndRegisterGpuMemory { - fd, + fd: MaybeOwnedFd::Owned(file), pfn, slot, desc, } => { let mut vfd = WlVfd::default(); - // Duplicate FD for shared memory instance. - let raw_fd = unsafe { File::from_raw_fd(dup(fd.as_raw_fd())) }; - let vfd_shm = SharedMemory::from_raw_fd(raw_fd).map_err(WlError::NewAlloc)?; + let vfd_shm = SharedMemory::from_file(file).map_err(WlError::NewAlloc)?; vfd.guest_shared_memory = Some((vfd_shm.size(), vfd_shm.into())); vfd.slot = Some((slot, pfn, vm)); vfd.is_dmabuf = true; diff --git a/disk/src/qcow/refcount.rs b/disk/src/qcow/refcount.rs index 438de5b..ab399c3 100644 --- a/disk/src/qcow/refcount.rs +++ b/disk/src/qcow/refcount.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::fmt::{self, Display}; use std::io; diff --git a/disk/src/qcow/vec_cache.rs b/disk/src/qcow/vec_cache.rs index ecd7673..18cfc69 100644 --- a/disk/src/qcow/vec_cache.rs +++ b/disk/src/qcow/vec_cache.rs @@ -120,7 +120,7 @@ impl<T: Cacheable> CacheMap<T> { { if self.map.len() == self.capacity { // TODO(dgreid) - smarter eviction strategy. - let to_evict = *self.map.iter().nth(0).unwrap().0; + let to_evict = *self.map.iter().next().unwrap().0; if let Some(evicted) = self.map.remove(&to_evict) { if evicted.dirty() { write_callback(to_evict, evicted)?; diff --git a/docker/Dockerfile b/docker/Dockerfile index 1bd1086..ecafd0a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,16 +36,15 @@ 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.41.0 \ + RUST_VERSION=1.42.0 \ RUSTFLAGS='--cfg hermetic' # Debian usually has an old rust version in the repository. Instead of using that, we use rustup to # pull in a toolchain versions of our choosing. -RUN curl -LO "https://static.rust-lang.org/rustup/archive/1.14.0/x86_64-unknown-linux-gnu/rustup-init" \ - && echo "0077ff9c19f722e2be202698c037413099e1188c0c233c12a2297bf18e9ff6e7 *rustup-init" | sha256sum -c - \ +RUN curl -LO "https://static.rust-lang.org/rustup/archive/1.21.1/x86_64-unknown-linux-gnu/rustup-init" \ + && echo "ad1f8b5199b3b9e231472ed7aa08d2e5d1d539198a15c5b1e53c746aad81d27b *rustup-init" | sha256sum -c - \ && chmod +x rustup-init \ && ./rustup-init -y --no-modify-path --default-toolchain $RUST_VERSION \ - && rustup component add rustfmt-preview \ && rm rustup-init \ && chmod -R a+w $RUSTUP_HOME $CARGO_HOME \ && rustup --version \ @@ -124,17 +123,20 @@ RUN git clone https://chromium.googlesource.com/chromiumos/platform2 $PLATFORM2_ && cp librendernodehost.a /lib \ && git clean -f -# Create a dummy pc file for libvda to run 'cargo check' with video features. -RUN echo "Name: libvda_pc\nDescription:\nVersion:0.1\nLibs: -lvda" > /usr/lib/pkgconfig/libvda.pc - # Set up sysroot from which system_api proto files are built. ENV SYSROOT=/sysroot RUN mkdir -p $SYSROOT/usr/include/chromeos/dbus/trunks \ && cp $PLATFORM2_ROOT/trunks/interface.proto \ $SYSROOT/usr/include/chromeos/dbus/trunks +# Copy it under rustc's sysroot as well for cargo clippy. +RUN export RUST_SYSROOT=$(rustc --print sysroot); echo $RUST_SYSROOT +RUN mkdir -p $RUST_SYSROOT/usr/include/chromeos/dbus/trunks \ + && cp $PLATFORM2_ROOT/trunks/interface.proto \ + $RUST_SYSROOT/usr/include/chromeos/dbus/trunks # Inform pkg-config where libraries we install are placed. -COPY pkgconfig/* /usr/lib/pkgconfig +# Also, copy a dummy libvda.pc to compile crosvm with video features. +COPY pkgconfig/* /usr/lib/pkgconfig/ # Reduces image size and prevents accidentally using /scratch files RUN rm -r /scratch /usr/bin/meson diff --git a/docker/Dockerfile.crosvm b/docker/Dockerfile.crosvm index 00649ba..60ba5a9 100644 --- a/docker/Dockerfile.crosvm +++ b/docker/Dockerfile.crosvm @@ -2,10 +2,6 @@ FROM crosvm-base COPY . /platform/crosvm -# Compile crosvm with all features, including experimental and/or platform specific ones that -# are disabled at 'cargo install' below. -RUN cargo check --all-features - RUN cargo install --features 'default-no-sandbox wl-dmabuf gpu x' --path . --root /usr ARG UID=1000 diff --git a/docker/checkout_commits.env b/docker/checkout_commits.env index 60aecc1..712b5f3 100644 --- a/docker/checkout_commits.env +++ b/docker/checkout_commits.env @@ -1,6 +1,6 @@ MESON_COMMIT=a1a8772034aef90e8d58230d8bcfce54ab27bf6a LIBEPOXY_COMMIT=af38a466caf9c2ae49b8acda4ff842ae44d57f78 TPM2_COMMIT=a9bc45bb7fafc65ea8a787894434d409f533b1f1 -PLATFORM2_COMMIT=9239a43f2dc2e98e57e9d77aac72fa3ce8169e5f -ADHD_COMMIT=db796cecdea7013b8679f90dfae34915edc9246f +PLATFORM2_COMMIT=fabad43f1182bf71b3771735b4488180d08f3d59 +ADHD_COMMIT=8405d713c2c293646723a424c218af4a72e598f2 DRM_COMMIT=00320d7d68ddc7d815d073bb7c92d9a1f9bb8c31 diff --git a/docker/pkgconfig/libvda.pc b/docker/pkgconfig/libvda.pc new file mode 100644 index 0000000..ee57864 --- /dev/null +++ b/docker/pkgconfig/libvda.pc @@ -0,0 +1,4 @@ +Name: libvda +Description: CrOS VDA Connection Library +Version: 0.1 +Libs: -lvda diff --git a/gpu_buffer/src/raw.rs b/gpu_buffer/src/raw.rs index 6f5686a..ba07259 100644 --- a/gpu_buffer/src/raw.rs +++ b/gpu_buffer/src/raw.rs @@ -6,7 +6,7 @@ // Then modified manually /* Added below line manually */ -#![allow(dead_code)] +#![allow(dead_code, non_camel_case_types)] /* Added below line manually */ use std::os::raw::{c_char, c_int, c_uint, c_void}; diff --git a/gpu_display/src/gpu_display_stub.rs b/gpu_display/src/gpu_display_stub.rs index 9b33a96..605e009 100644 --- a/gpu_display/src/gpu_display_stub.rs +++ b/gpu_display/src/gpu_display_stub.rs @@ -13,7 +13,7 @@ use sys_util::EventFd; type SurfaceId = NonZeroU32; -#[allow(unused_variables)] +#[allow(dead_code)] struct Buffer { width: u32, height: u32, diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs index 2940f40..0882dc9 100644 --- a/gpu_display/src/gpu_display_x.rs +++ b/gpu_display/src/gpu_display_x.rs @@ -20,7 +20,7 @@ use std::num::NonZeroU32; use std::os::raw::c_ulong; use std::os::unix::io::{AsRawFd, RawFd}; use std::ptr::{null, null_mut, NonNull}; -use std::rc::{Rc, Weak}; +use std::rc::Rc; use std::time::Duration; use libc::{shmat, shmctl, shmdt, shmget, IPC_CREAT, IPC_PRIVATE, IPC_RMID}; diff --git a/gpu_display/src/keycode_converter/mod.rs b/gpu_display/src/keycode_converter/mod.rs index 236e65d..00549ed 100644 --- a/gpu_display/src/keycode_converter/mod.rs +++ b/gpu_display/src/keycode_converter/mod.rs @@ -8,6 +8,7 @@ use data::{MapEntry, KEYCODE_MAP}; use std::collections::HashMap; /// Specifies which type of scancode to convert *from* in the KeycodeTranslator. +#[allow(dead_code)] pub enum KeycodeTypes { XkbScancode, WindowsScancode, diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml new file mode 100644 index 0000000..de82105 --- /dev/null +++ b/hypervisor/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hypervisor" +version = "0.1.0" +authors = ["The Chromium OS Authors"] +edition = "2018" + +[dependencies] +libc = "*" +kvm_sys = { path = "../kvm_sys" } +sys_util = { path = "../sys_util" } \ No newline at end of file diff --git a/hypervisor/src/caps.rs b/hypervisor/src/caps.rs new file mode 100644 index 0000000..d088ca7 --- /dev/null +++ b/hypervisor/src/caps.rs @@ -0,0 +1,6 @@ +// 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. + +/// An enumeration of different hypervisor capabilities. +pub enum HypervisorCap {} diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs new file mode 100644 index 0000000..7058921 --- /dev/null +++ b/hypervisor/src/kvm/mod.rs @@ -0,0 +1,62 @@ +// 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 super::{CpuId, Hypervisor, HypervisorCap}; +use libc::{open, O_CLOEXEC, O_RDWR}; +use std::os::raw::c_char; +use sys_util::{ + errno_result, AsRawDescriptor, FromRawDescriptor, RawDescriptor, Result, SafeDescriptor, +}; + +pub struct Kvm { + kvm: SafeDescriptor, +} + +impl Kvm { + /// Opens `/dev/kvm/` and returns a Kvm object on success. + pub fn new() -> Result<Kvm> { + // Open calls are safe because we give a constant nul-terminated string and verify the + // result. + let ret = unsafe { open("/dev/kvm\0".as_ptr() as *const c_char, O_RDWR | O_CLOEXEC) }; + if ret < 0 { + return errno_result(); + } + // Safe because we verify that ret is valid and we own the fd. + Ok(Kvm { + kvm: unsafe { SafeDescriptor::from_raw_descriptor(ret) }, + }) + } +} + +impl AsRawDescriptor for Kvm { + fn as_raw_descriptor(&self) -> RawDescriptor { + self.kvm.as_raw_descriptor() + } +} + +impl Hypervisor for Kvm { + fn check_capability(&self, _cap: &HypervisorCap) -> bool { + unimplemented!("check_capability for Kvm is not yet implemented"); + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_supported_cpuid(&self) -> Result<CpuId> { + unimplemented!("get_supported_cpuid for Kvm is not yet implemented"); + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_emulated_cpuid(&self) -> Result<CpuId> { + unimplemented!("get_emulated_cpuid for Kvm is not yet implemented"); + } +} + +#[cfg(test)] +mod tests { + use super::Kvm; + + #[test] + fn new() { + Kvm::new().unwrap(); + } +} diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs new file mode 100644 index 0000000..056070b --- /dev/null +++ b/hypervisor/src/lib.rs @@ -0,0 +1,25 @@ +// 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. + +//! A crate for abstracting the underlying kernel hypervisor used in crosvm. +pub mod caps; +pub mod kvm; +pub mod types; + +use sys_util::Result; + +pub use crate::caps::*; +pub use crate::types::*; + +/// A trait for managing the underlying cpu information for the hypervisor and to check its capabilities. +trait Hypervisor { + // Checks if a particular `HypervisorCap` is available. + fn check_capability(&self, cap: &HypervisorCap) -> bool; + // Get the system supported CPUID values. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_supported_cpuid(&self) -> Result<CpuId>; + // Get the system emulated CPUID values. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_emulated_cpuid(&self) -> Result<CpuId>; +} diff --git a/hypervisor/src/types/mod.rs b/hypervisor/src/types/mod.rs new file mode 100644 index 0000000..69fa9e4 --- /dev/null +++ b/hypervisor/src/types/mod.rs @@ -0,0 +1,9 @@ +// 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. + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub mod x86; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use self::x86::*; diff --git a/hypervisor/src/types/x86.rs b/hypervisor/src/types/x86.rs new file mode 100644 index 0000000..cd5236a --- /dev/null +++ b/hypervisor/src/types/x86.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. + +use kvm_sys::kvm_cpuid_entry2; + +pub type CpuIdEntry = kvm_cpuid_entry2; +pub struct CpuId { + _cpu_id_entries: Vec<CpuIdEntry>, +} diff --git a/io_jail/src/lib.rs b/io_jail/src/lib.rs index a927cdb..7a12307 100644 --- a/io_jail/src/lib.rs +++ b/io_jail/src/lib.rs @@ -68,6 +68,8 @@ pub enum Error { PreservingFd(i32), /// Program size is too large ProgramTooLarge, + /// Alignment of file should be divisible by the alignment of sock_filter. + WrongProgramAlignment, /// File size should be non-zero and a multiple of sock_filter WrongProgramSize, } @@ -148,6 +150,10 @@ impl Display for Error { ProcFd(s) => write!(f, "an entry in /proc/self/fd is not an integer: {}", s), PreservingFd(e) => write!(f, "fork failed in minijail_preserve_fd with error {}", e), ProgramTooLarge => write!(f, "bpf program is too large (max 64K instructions)"), + WrongProgramAlignment => write!( + f, + "the alignment of bpf file was not a multiple of that of sock_filter" + ), WrongProgramSize => write!(f, "bpf file was empty or not a multiple of sock_filter"), } } @@ -287,6 +293,13 @@ impl Minijail { if count > (!0 as u16) as usize { return Err(Error::ProgramTooLarge); } + if buffer.as_ptr() as usize % std::mem::align_of::<sock_filter>() != 0 { + return Err(Error::WrongProgramAlignment); + } + + // Safe cast because we checked that the buffer address is divisible by the alignment of + // sock_filter. + #[allow(clippy::cast_ptr_alignment)] let header = sock_fprog { len: count as c_ushort, filter: buffer.as_ptr() as *mut sock_filter, diff --git a/kvm/src/cap.rs b/kvm/src/cap.rs index 7dfd965..ff65b59 100644 --- a/kvm/src/cap.rs +++ b/kvm/src/cap.rs @@ -119,4 +119,5 @@ pub enum Cap { CheckExtensionVm = KVM_CAP_CHECK_EXTENSION_VM, S390UserSigp = KVM_CAP_S390_USER_SIGP, ImmediateExit = KVM_CAP_IMMEDIATE_EXIT, + ArmPmuV3 = KVM_CAP_ARM_PMU_V3, } diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index b8cd12b..8b98b7c 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -1763,6 +1763,50 @@ impl Vcpu { if ret < 0 { return errno_result(); } + + Ok(()) + } + + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + pub fn arm_pmu_init(&self, irq: u64) -> Result<()> { + let irq_addr = &irq as *const u64; + + // The in-kernel PMU virtualization is initialized by setting the irq + // with KVM_ARM_VCPU_PMU_V3_IRQ and then by KVM_ARM_VCPU_PMU_V3_INIT. + + let irq_attr = kvm_device_attr { + group: kvm_sys::KVM_ARM_VCPU_PMU_V3_CTRL, + attr: kvm_sys::KVM_ARM_VCPU_PMU_V3_IRQ as u64, + addr: irq_addr as u64, + flags: 0, + }; + // 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_sys::KVM_HAS_DEVICE_ATTR(), &irq_attr) }; + if ret < 0 { + return errno_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_sys::KVM_SET_DEVICE_ATTR(), &irq_attr) }; + if ret < 0 { + return errno_result(); + } + + let init_attr = kvm_device_attr { + group: kvm_sys::KVM_ARM_VCPU_PMU_V3_CTRL, + attr: kvm_sys::KVM_ARM_VCPU_PMU_V3_INIT as u64, + addr: 0, + flags: 0, + }; + // 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_sys::KVM_SET_DEVICE_ATTR(), &init_attr) }; + if ret < 0 { + return errno_result(); + } + Ok(()) } diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs index 084fc13..0dd7f5a 100644 --- a/kvm_sys/src/aarch64/bindings.rs +++ b/kvm_sys/src/aarch64/bindings.rs @@ -157,6 +157,10 @@ pub const KVM_VGIC_V3_ADDR_TYPE_REDIST: ::std::os::raw::c_uint = 3; pub const KVM_ARM_VCPU_POWER_OFF: ::std::os::raw::c_uint = 0; pub const KVM_ARM_VCPU_EL1_32BIT: ::std::os::raw::c_uint = 1; pub const KVM_ARM_VCPU_PSCI_0_2: ::std::os::raw::c_uint = 2; +pub const KVM_ARM_VCPU_PMU_V3: ::std::os::raw::c_uint = 3; +pub const KVM_ARM_VCPU_PMU_V3_CTRL: ::std::os::raw::c_uint = 0; +pub const KVM_ARM_VCPU_PMU_V3_IRQ: ::std::os::raw::c_uint = 0; +pub const KVM_ARM_VCPU_PMU_V3_INIT: ::std::os::raw::c_uint = 1; pub const KVM_ARM_MAX_DBG_REGS: ::std::os::raw::c_uint = 16; pub const KVM_GUESTDBG_USE_SW_BP: ::std::os::raw::c_uint = 65536; pub const KVM_GUESTDBG_USE_HW: ::std::os::raw::c_uint = 131072; @@ -439,6 +443,7 @@ 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_ARM_PMU_V3: ::std::os::raw::c_uint = 126; pub const KVM_CAP_IMMEDIATE_EXIT: ::std::os::raw::c_uint = 136; pub const KVM_IRQ_ROUTING_IRQCHIP: ::std::os::raw::c_uint = 1; pub const KVM_IRQ_ROUTING_MSI: ::std::os::raw::c_uint = 2; diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs index 7236611..629d83a 100644 --- a/kvm_sys/src/x86/bindings.rs +++ b/kvm_sys/src/x86/bindings.rs @@ -426,6 +426,7 @@ 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_ARM_PMU_V3: ::std::os::raw::c_uint = 126; 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; diff --git a/msg_socket/Cargo.toml b/msg_socket/Cargo.toml index c803bed..80eba0b 100644 --- a/msg_socket/Cargo.toml +++ b/msg_socket/Cargo.toml @@ -11,3 +11,4 @@ futures = "*" libc = "*" msg_on_socket_derive = { path = "msg_on_socket_derive" } sys_util = { path = "../sys_util" } +sync = { path = "../sync" } diff --git a/msg_socket/msg_on_socket_derive/msg_on_socket_derive.rs b/msg_socket/msg_on_socket_derive/msg_on_socket_derive.rs index 7b16546..a5f31f8 100644 --- a/msg_socket/msg_on_socket_derive/msg_on_socket_derive.rs +++ b/msg_socket/msg_on_socket_derive/msg_on_socket_derive.rs @@ -9,11 +9,12 @@ use std::vec::Vec; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Fields, Ident, Index, Member, Type, + parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Fields, Ident, Index, Member, Meta, + NestedMeta, Type, }; /// The function that derives the recursive implementation for struct or enum. -#[proc_macro_derive(MsgOnSocket)] +#[proc_macro_derive(MsgOnSocket, attributes(msg_on_socket))] pub fn msg_on_socket_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let impl_for_input = msg_socket_impl(input); @@ -46,6 +47,13 @@ fn is_named_struct(ds: &DataStruct) -> bool { } /************************** Named Struct Impls ********************************************/ + +struct StructField { + member: Member, + ty: Type, + skipped: bool, +} + fn impl_for_named_struct(name: Ident, ds: DataStruct) -> TokenStream { let fields = get_struct_fields(ds); let uses_fd_impl = define_uses_fd_for_struct(&fields); @@ -64,7 +72,7 @@ fn impl_for_named_struct(name: Ident, ds: DataStruct) -> TokenStream { } // Flatten struct fields. -fn get_struct_fields(ds: DataStruct) -> Vec<(Member, Type)> { +fn get_struct_fields(ds: DataStruct) -> Vec<StructField> { let fields = match ds.fields { Fields::Named(fields_named) => fields_named.named, _ => { @@ -78,17 +86,48 @@ fn get_struct_fields(ds: DataStruct) -> Vec<(Member, Type)> { None => panic!("Unknown Error."), }; let ty = field.ty; - vec.push((member, ty)); + let mut skipped = false; + for attr in field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("msg_on_socket")) + { + match attr.parse_meta().unwrap() { + Meta::List(meta) => { + for nested in meta.nested { + match nested { + NestedMeta::Meta(Meta::Path(meta_path)) + if meta_path.is_ident("skip") => + { + skipped = true; + } + _ => panic!("unrecognized attribute meta `{}`", quote! { #nested }), + } + } + } + _ => panic!("unrecognized attribute `{}`", quote! { #attr }), + } + } + vec.push(StructField { + member, + ty, + skipped, + }); } vec } -fn define_uses_fd_for_struct(fields: &[(Member, Type)]) -> TokenStream { - if fields.len() == 0 { +fn define_uses_fd_for_struct(fields: &[StructField]) -> TokenStream { + let field_types: Vec<_> = fields + .iter() + .filter(|f| !f.skipped) + .map(|f| &f.ty) + .collect(); + + if field_types.is_empty() { return quote!(); } - let field_types = fields.iter().map(|(_, ty)| ty); quote! { fn uses_fd() -> bool { #(<#field_types>::uses_fd())||* @@ -96,7 +135,7 @@ fn define_uses_fd_for_struct(fields: &[(Member, Type)]) -> TokenStream { } } -fn define_buffer_size_for_struct(fields: &[(Member, Type)]) -> TokenStream { +fn define_buffer_size_for_struct(fields: &[StructField]) -> TokenStream { let (msg_size, fd_count) = get_fields_buffer_size_sum(fields); quote! { fn msg_size(&self) -> usize { @@ -108,17 +147,24 @@ fn define_buffer_size_for_struct(fields: &[(Member, Type)]) -> TokenStream { } } -fn define_read_buffer_for_struct(_name: &Ident, fields: &[(Member, Type)]) -> TokenStream { +fn define_read_buffer_for_struct(_name: &Ident, fields: &[StructField]) -> TokenStream { let mut read_fields = Vec::new(); let mut init_fields = Vec::new(); - for (field_member, field_ty) in fields { - let ident = match field_member { + for field in fields { + let ident = match &field.member { Member::Named(ident) => ident, Member::Unnamed(_) => unreachable!(), }; - let read_field = read_from_buffer_and_move_offset(&ident, &field_ty); - read_fields.push(read_field); let name = ident.clone(); + if field.skipped { + let ty = &field.ty; + init_fields.push(quote! { + #name: <#ty>::default() + }); + continue; + } + let read_field = read_from_buffer_and_move_offset(&ident, &field.ty); + read_fields.push(read_field); init_fields.push(quote!(#name)); } quote! { @@ -139,10 +185,13 @@ fn define_read_buffer_for_struct(_name: &Ident, fields: &[(Member, Type)]) -> To } } -fn define_write_buffer_for_struct(_name: &Ident, fields: &[(Member, Type)]) -> TokenStream { +fn define_write_buffer_for_struct(_name: &Ident, fields: &[StructField]) -> TokenStream { let mut write_fields = Vec::new(); - for (field_member, _) in fields { - let ident = match field_member { + for field in fields { + if field.skipped { + continue; + } + let ident = match &field.member { Member::Named(ident) => ident, Member::Unnamed(_) => unreachable!(), }; @@ -187,17 +236,13 @@ fn define_uses_fd_for_enum(de: &DataEnum) -> TokenStream { } } - if variant_field_types.is_empty() { - quote! { - fn uses_fd() -> bool { - false - } - } - } else { - quote! { - fn uses_fd() -> bool { - #(<#variant_field_types>::uses_fd())||* - } + if variant_field_types.len() == 0 { + return quote!(); + } + + quote! { + fn uses_fd() -> bool { + #(<#variant_field_types>::uses_fd())||* } } } @@ -438,7 +483,7 @@ fn impl_for_tuple_struct(name: Ident, ds: DataStruct) -> TokenStream { } } -fn get_tuple_fields(ds: DataStruct) -> Vec<(Member, Type)> { +fn get_tuple_fields(ds: DataStruct) -> Vec<StructField> { let mut field_idents = Vec::new(); let fields = match ds.fields { Fields::Unnamed(fields_unnamed) => fields_unnamed.unnamed, @@ -449,17 +494,21 @@ fn get_tuple_fields(ds: DataStruct) -> Vec<(Member, Type)> { for (idx, field) in fields.iter().enumerate() { let member = Member::Unnamed(Index::from(idx)); let ty = field.ty.clone(); - field_idents.push((member, ty)); + field_idents.push(StructField { + member, + ty, + skipped: false, + }); } field_idents } -fn define_uses_fd_for_tuples(fields: &[(Member, Type)]) -> TokenStream { +fn define_uses_fd_for_tuples(fields: &[StructField]) -> TokenStream { if fields.len() == 0 { return quote!(); } - let field_types = fields.iter().map(|(_, ty)| ty); + let field_types = fields.iter().map(|f| &f.ty); quote! { fn uses_fd() -> bool { #(<#field_types>::uses_fd())||* @@ -467,13 +516,13 @@ fn define_uses_fd_for_tuples(fields: &[(Member, Type)]) -> TokenStream { } } -fn define_read_buffer_for_tuples(name: &Ident, fields: &[(Member, Type)]) -> TokenStream { +fn define_read_buffer_for_tuples(name: &Ident, fields: &[StructField]) -> TokenStream { let mut read_fields = Vec::new(); let mut init_fields = Vec::new(); - for (idx, (_, field_ty)) in fields.iter().enumerate() { + for (idx, field) in fields.iter().enumerate() { let tmp_name = format!("tuple_tmp{}", idx); let tmp_name = Ident::new(&tmp_name, Span::call_site()); - let read_field = read_from_buffer_and_move_offset(&tmp_name, field_ty); + let read_field = read_from_buffer_and_move_offset(&tmp_name, &field.ty); read_fields.push(read_field); init_fields.push(quote!(#tmp_name)); } @@ -496,7 +545,7 @@ fn define_read_buffer_for_tuples(name: &Ident, fields: &[(Member, Type)]) -> Tok } } -fn define_write_buffer_for_tuples(name: &Ident, fields: &[(Member, Type)]) -> TokenStream { +fn define_write_buffer_for_tuples(name: &Ident, fields: &[StructField]) -> TokenStream { let mut write_fields = Vec::new(); let mut tmp_names = Vec::new(); for idx in 0..fields.len() { @@ -520,8 +569,12 @@ fn define_write_buffer_for_tuples(name: &Ident, fields: &[(Member, Type)]) -> To } } /************************** Helpers ********************************************/ -fn get_fields_buffer_size_sum(fields: &[(Member, Type)]) -> (TokenStream, TokenStream) { - let fields: Vec<_> = fields.iter().map(|(m, _)| m).collect(); +fn get_fields_buffer_size_sum(fields: &[StructField]) -> (TokenStream, TokenStream) { + let fields: Vec<_> = fields + .iter() + .filter(|f| !f.skipped) + .map(|f| &f.member) + .collect(); if fields.len() > 0 { ( quote! { @@ -832,4 +885,45 @@ mod tests { assert_eq!(msg_socket_impl(input).to_string(), expected.to_string()); } + + #[test] + fn end_to_end_struct_skip_test() { + let input: DeriveInput = parse_quote! { + struct MyMsg { + #[msg_on_socket(skip)] + a: u8, + } + }; + + let expected = quote! { + impl msg_socket::MsgOnSocket for MyMsg { + fn msg_size(&self) -> usize { + 0 + } + fn fd_count(&self) -> usize { + 0 + } + unsafe fn read_from_buffer( + buffer: &[u8], + fds: &[std::os::unix::io::RawFd], + ) -> msg_socket::MsgResult<(Self, usize)> { + let mut __offset = 0usize; + let mut __fd_offset = 0usize; + Ok((Self { a: <u8>::default() }, __fd_offset)) + } + fn write_to_buffer( + &self, + buffer: &mut [u8], + fds: &mut [std::os::unix::io::RawFd], + ) -> msg_socket::MsgResult<usize> { + let mut __offset = 0usize; + let mut __fd_offset = 0usize; + Ok(__fd_offset) + } + } + + }; + + assert_eq!(socket_msg_impl(input).to_string(), expected.to_string()); + } } diff --git a/msg_socket/src/lib.rs b/msg_socket/src/lib.rs index 5871735..7f5d1a6 100644 --- a/msg_socket/src/lib.rs +++ b/msg_socket/src/lib.rs @@ -13,7 +13,7 @@ use std::task::{Context, Poll}; use futures::Stream; use libc::{EWOULDBLOCK, O_NONBLOCK}; -use cros_async::fd_executor::add_read_waker; +use cros_async::add_read_waker; use sys_util::{ add_fd_flags, clear_fd_flags, error, handle_eintr, net::UnixSeqpacket, Error as SysError, ScmSocket, @@ -50,7 +50,7 @@ impl<I: MsgOnSocket, O: MsgOnSocket> MsgSocket<I, O> { } // Creates an async receiver that implements `futures::Stream`. - pub fn async_receiver(&mut self) -> MsgResult<AsyncReceiver<I, O>> { + pub fn async_receiver(&self) -> MsgResult<AsyncReceiver<I, O>> { AsyncReceiver::new(self) } } @@ -164,6 +164,20 @@ pub trait MsgReceiver: AsRef<UnixSeqpacket> { ) } }; + + if msg_buffer.len() == 0 && Self::M::fixed_size() != Some(0) { + return Err(MsgError::RecvZero); + } + + if let Some(fixed_size) = Self::M::fixed_size() { + if fixed_size != msg_buffer.len() { + return Err(MsgError::BadRecvSize { + expected: fixed_size, + actual: msg_buffer.len(), + }); + } + } + // Safe because fd buffer is read from socket. let (v, read_fd_size) = unsafe { Self::M::read_from_buffer(&msg_buffer, &fd_buffer)? }; if fd_buffer.len() != read_fd_size { @@ -189,12 +203,12 @@ impl<O: MsgOnSocket> MsgReceiver for Receiver<O> { /// Asynchronous adaptor for `MsgSocket`. pub struct AsyncReceiver<'a, I: MsgOnSocket, O: MsgOnSocket> { - inner: &'a mut MsgSocket<I, O>, + inner: &'a MsgSocket<I, O>, 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<I, O>) -> MsgResult<AsyncReceiver<I, O>> { + fn new(msg_socket: &MsgSocket<I, O>) -> MsgResult<AsyncReceiver<I, O>> { add_fd_flags(msg_socket.as_raw_fd(), O_NONBLOCK).map_err(MsgError::SettingFdFlags)?; Ok(AsyncReceiver { inner: msg_socket, diff --git a/msg_socket/src/msg_on_socket.rs b/msg_socket/src/msg_on_socket.rs index 624d514..67d26aa 100644 --- a/msg_socket/src/msg_on_socket.rs +++ b/msg_socket/src/msg_on_socket.rs @@ -10,15 +10,17 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; use std::ptr::drop_in_place; use std::result; +use std::sync::Arc; use data_model::*; +use sync::Mutex; 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), + AddingWaker(cros_async::Error), /// Error while sending a request or response. Send(SysError), /// Error while receiving a request or response. @@ -28,6 +30,8 @@ pub enum MsgError { /// There was not the expected amount of data when receiving a message. The inner /// value is how much data is expected and how much data was actually received. BadRecvSize { expected: usize, actual: usize }, + /// There was no data received when the socket `recv`-ed. + RecvZero, /// There was no associated file descriptor received for a request that expected it. ExpectFd, /// There was some associated file descriptor received but not used when deserialize. @@ -58,6 +62,7 @@ impl Display for MsgError { "wrong amount of data received; expected {} bytes; got {} bytes", expected, actual ), + RecvZero => write!(f, "received zero data"), 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), @@ -207,6 +212,50 @@ impl<T: MsgOnSocket> MsgOnSocket for Option<T> { } } +impl<T: MsgOnSocket> MsgOnSocket for Mutex<T> { + fn uses_fd() -> bool { + T::uses_fd() + } + + fn msg_size(&self) -> usize { + self.lock().msg_size() + } + + fn fd_count(&self) -> usize { + self.lock().fd_count() + } + + unsafe fn read_from_buffer(buffer: &[u8], fds: &[RawFd]) -> MsgResult<(Self, usize)> { + T::read_from_buffer(buffer, fds).map(|(v, count)| (Mutex::new(v), count)) + } + + fn write_to_buffer(&self, buffer: &mut [u8], fds: &mut [RawFd]) -> MsgResult<usize> { + self.lock().write_to_buffer(buffer, fds) + } +} + +impl<T: MsgOnSocket> MsgOnSocket for Arc<T> { + fn uses_fd() -> bool { + T::uses_fd() + } + + fn msg_size(&self) -> usize { + (**self).msg_size() + } + + fn fd_count(&self) -> usize { + (**self).fd_count() + } + + unsafe fn read_from_buffer(buffer: &[u8], fds: &[RawFd]) -> MsgResult<(Self, usize)> { + T::read_from_buffer(buffer, fds).map(|(v, count)| (Arc::new(v), count)) + } + + fn write_to_buffer(&self, buffer: &mut [u8], fds: &mut [RawFd]) -> MsgResult<usize> { + (**self).write_to_buffer(buffer, fds) + } +} + impl MsgOnSocket for () { fn fixed_size() -> Option<usize> { Some(0) diff --git a/p9/src/protocol/wire_format.rs b/p9/src/protocol/wire_format.rs index 84408e2..afc1231 100644 --- a/p9/src/protocol/wire_format.rs +++ b/p9/src/protocol/wire_format.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::fmt; use std::io; use std::io::{ErrorKind, Read, Write}; diff --git a/p9/src/server.rs b/p9/src/server.rs index e1ffd16..957923d 100644 --- a/p9/src/server.rs +++ b/p9/src/server.rs @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use libc; - use std::cmp::min; use std::collections::{btree_map, BTreeMap}; use std::ffi::CString; diff --git a/resources/src/gpu_allocator.rs b/resources/src/gpu_allocator.rs index 478d0d4..cbf21a2 100644 --- a/resources/src/gpu_allocator.rs +++ b/resources/src/gpu_allocator.rs @@ -8,10 +8,7 @@ use std::fs::File; #[cfg(feature = "wl-dmabuf")] use libc::EINVAL; -#[cfg(feature = "wl-dmabuf")] -use gpu_buffer; use msg_socket::MsgOnSocket; -use sys_util; #[allow(dead_code)] #[derive(Debug, Eq, PartialEq)] diff --git a/resources/src/lib.rs b/resources/src/lib.rs index a6beceb..cf36cc1 100644 --- a/resources/src/lib.rs +++ b/resources/src/lib.rs @@ -24,8 +24,8 @@ pub enum Alloc { /// Should only be instantiated through `SystemAllocator::get_anon_alloc()`. /// Avoid using these. Instead, use / create a more descriptive Alloc variant. Anon(usize), - /// A PCI BAR region with associated bus, device, and bar numbers. - PciBar { bus: u8, dev: u8, bar: u8 }, + /// A PCI BAR region with associated bus, device, function and bar numbers. + PciBar { bus: u8, dev: u8, func: u8, bar: u8 }, /// GPU render node region. GpuRenderNode, /// Pmem device region with associated device index. diff --git a/resources/src/system_allocator.rs b/resources/src/system_allocator.rs index 984bc51..889fe01 100644 --- a/resources/src/system_allocator.rs +++ b/resources/src/system_allocator.rs @@ -25,13 +25,14 @@ use crate::{Alloc, Error, Result}; /// a.mmio_allocator(MmioType::High) /// .allocate( /// 0x100, -/// Alloc::PciBar { bus: 0, dev: 0, bar: 0 }, +/// Alloc::PciBar { bus: 0, dev: 0, func: 0, bar: 0 }, /// "bar0".to_string() /// ), /// Ok(0x10000000) /// ); /// assert_eq!( -/// a.mmio_allocator(MmioType::High).get(&Alloc::PciBar { bus: 0, dev: 0, bar: 0 }), +/// a.mmio_allocator(MmioType::High) +/// .get(&Alloc::PciBar { bus: 0, dev: 0, func: 0, bar: 0 }), /// Some(&(0x10000000, 0x100, "bar0".to_string())) /// ); /// } diff --git a/seccomp/aarch64/gpu_device.policy b/seccomp/aarch64/gpu_device.policy index 7ef95b2..3eaae05 100644 --- a/seccomp/aarch64/gpu_device.policy +++ b/seccomp/aarch64/gpu_device.policy @@ -52,6 +52,7 @@ statx: 1 fstat: 1 newfstatat: 1 getdents64: 1 +sysinfo: 1 # 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali) ioctl: arg1 & 0x6400 || arg1 & 0x8000 diff --git a/seccomp/arm/gpu_device.policy b/seccomp/arm/gpu_device.policy index 4e3a052..1e42302 100644 --- a/seccomp/arm/gpu_device.policy +++ b/seccomp/arm/gpu_device.policy @@ -54,6 +54,7 @@ stat64: 1 fstat64: 1 getdents: 1 getdents64: 1 +sysinfo: 1 # 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali) ioctl: arg1 & 0x6400 || arg1 & 0x8000 diff --git a/seccomp/x86_64/gpu_device.policy b/seccomp/x86_64/gpu_device.policy index 23b6b6c..331fc49 100644 --- a/seccomp/x86_64/gpu_device.policy +++ b/seccomp/x86_64/gpu_device.policy @@ -75,3 +75,4 @@ sysinfo: 1 uname: 1 sched_setscheduler: 1 sched_setaffinity: 1 +kcmp: 1 diff --git a/src/argument.rs b/src/argument.rs index 0d0e142..26950a9 100644 --- a/src/argument.rs +++ b/src/argument.rs @@ -292,14 +292,12 @@ where State::Positional } State::Value { name } => { - if arg.starts_with("-") { + if arg.starts_with('-') { arg_consumed = false; f(&name, None)?; - } else { - if let Err(e) = f(&name, Some(&arg)) { - arg_consumed = false; - f(&name, None).map_err(|_| e)?; - } + } else if let Err(e) = f(&name, Some(&arg)) { + arg_consumed = false; + f(&name, None).map_err(|_| e)?; } State::Top } diff --git a/src/crosvm.rs b/src/crosvm.rs index a55d2e4..49a08c0 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -16,11 +16,11 @@ use std::os::unix::io::RawFd; use std::path::{Path, PathBuf}; use std::str::FromStr; -use arch::Pstore; +use arch::{Pstore, SerialHardware, SerialParameters}; use devices::virtio::fs::passthrough; #[cfg(feature = "gpu")] use devices::virtio::gpu::GpuParameters; -use devices::{Ac97Parameters, SerialParameters}; +use devices::Ac97Parameters; use libc::{getegid, geteuid}; static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm"; @@ -193,7 +193,7 @@ pub struct Config { pub display_window_keyboard: bool, pub display_window_mouse: bool, pub ac97_parameters: Vec<Ac97Parameters>, - pub serial_parameters: BTreeMap<u8, SerialParameters>, + pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>, pub syslog_tag: Option<String>, pub virtio_single_touch: Option<TouchDeviceOption>, pub virtio_trackpad: Option<TouchDeviceOption>, diff --git a/src/linux.rs b/src/linux.rs index 9dbdb5c..e480a4c 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::cmp::max; use std::convert::TryFrom; use std::error::Error as StdError; @@ -29,7 +28,7 @@ use libc::{self, c_int, gid_t, uid_t}; #[cfg(feature = "gpu")] use devices::virtio::EventDevice; -use devices::virtio::{self, VirtioDevice}; +use devices::virtio::{self, Console, VirtioDevice}; use devices::{ self, Ac97Backend, Ac97Dev, HostBackendDeviceProvider, PciDevice, VfioContainer, VfioDevice, VfioPciDevice, VirtioPciDevice, XhciController, @@ -50,7 +49,6 @@ use sys_util::{ Killable, MemoryMappingArena, PollContext, PollToken, Protection, ScopedEvent, SignalFd, Terminal, TimerFd, WatchingEvents, SIGRTMIN, }; -use vhost; use vm_control::{ BalloonControlCommand, BalloonControlRequestSocket, BalloonControlResponseSocket, BalloonControlResult, DiskControlCommand, DiskControlRequestSocket, DiskControlResponseSocket, @@ -61,7 +59,10 @@ use vm_control::{ }; use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption}; -use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; +use arch::{ + self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VirtioDeviceStub, + VmComponents, VmImage, +}; #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] use aarch64::AArch64 as Arch; @@ -82,6 +83,7 @@ pub enum Error { ChownTpmStorage(sys_util::Error), CloneEventFd(sys_util::Error), CreateAc97(devices::PciDeviceError), + CreateConsole(arch::serial::Error), CreateDiskError(disk::Error), CreateEventFd(sys_util::Error), CreatePollContext(sys_util::Error), @@ -166,6 +168,7 @@ impl Display for Error { ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e), CloneEventFd(e) => write!(f, "failed to clone eventfd: {}", e), CreateAc97(e) => write!(f, "failed to create ac97 device: {}", e), + CreateConsole(e) => write!(f, "failed to create console device: {}", e), CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e), CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e), CreatePollContext(e) => write!(f, "failed to create poll context: {}", e), @@ -816,7 +819,12 @@ fn create_fs_device( log_failures: cfg.seccomp_log_failures, seccomp_policy: &seccomp_policy, }; - create_base_minijail(src, Some(max_open_files), Some(&config))? + 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 fs device's + // namespace. + jail.set_remount_mode(libc::MS_SLAVE); + + jail } else { create_base_minijail(src, Some(max_open_files), None)? }; @@ -885,7 +893,7 @@ fn create_pmem_device( .open(&disk.path) .map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?; - let (disk_size, arena_size) = { + let arena_size = { let metadata = std::fs::metadata(&disk.path).map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?; let disk_len = metadata.len(); @@ -900,12 +908,9 @@ fn create_pmem_device( } else { 0 }; - ( - disk_len, - disk_len - .checked_add(align_adjust) - .ok_or(Error::PmemDeviceImageTooBig)?, - ) + disk_len + .checked_add(align_adjust) + .ok_or(Error::PmemDeviceImageTooBig)? }; let protection = { @@ -919,11 +924,10 @@ fn create_pmem_device( let arena = { // Conversion from u64 to usize may fail on 32bit system. let arena_size = usize::try_from(arena_size).map_err(|_| Error::PmemDeviceImageTooBig)?; - let disk_size = usize::try_from(disk_size).map_err(|_| Error::PmemDeviceImageTooBig)?; let mut arena = MemoryMappingArena::new(arena_size).map_err(Error::ReservePmemMemory)?; arena - .add_fd_offset_protection(0, disk_size, &fd, 0, protection) + .add_fd_offset_protection(0, arena_size, &fd, 0, protection) .map_err(Error::ReservePmemMemory)?; arena }; @@ -963,6 +967,19 @@ fn create_pmem_device( }) } +fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult { + let mut keep_fds = Vec::new(); + let evt = EventFd::new().map_err(Error::CreateEventFd)?; + let dev = param + .create_serial_device::<Console>(&evt, &mut keep_fds) + .map_err(Error::CreateConsole)?; + + Ok(VirtioDeviceStub { + dev: Box::new(dev), + jail: simple_jail(&cfg, "serial")?, // TODO(dverkamp): use a separate policy for console? + }) +} + // gpu_device_socket is not used when GPU support is disabled. #[cfg_attr(not(feature = "gpu"), allow(unused_variables))] fn create_virtio_devices( @@ -979,6 +996,15 @@ fn create_virtio_devices( ) -> DeviceResult<Vec<VirtioDeviceStub>> { let mut devs = Vec::new(); + for (_, param) in cfg + .serial_parameters + .iter() + .filter(|(_k, v)| v.hardware == SerialHardware::VirtioConsole) + { + let dev = create_console_device(cfg, param)?; + devs.push(dev); + } + for disk in &cfg.disks { let disk_device_socket = disk_device_sockets.remove(0); devs.push(create_block_device(cfg, disk, disk_device_socket)?); @@ -1398,7 +1424,7 @@ fn run_vcpu( // implementation accomplishes that. let _scoped_exit_evt = ScopedEvent::from(exit_evt); - if vcpu_affinity.len() != 0 { + if !vcpu_affinity.is_empty() { if let Err(e) = set_cpu_affinity(vcpu_affinity) { error!("Failed to set CPU affinity: {}", e); } @@ -1740,7 +1766,7 @@ fn run_control( } // Balance available memory between guest and host every second. - let mut balancemem_timer = TimerFd::new().map_err(Error::CreateTimerFd)?; + let balancemem_timer = TimerFd::new().map_err(Error::CreateTimerFd)?; if Path::new(LOWMEM_AVAILABLE).exists() { // Create timer request balloon stats every 1s. poll_ctx @@ -2024,7 +2050,7 @@ fn run_control( } } Err(e) => { - if let MsgError::BadRecvSize { actual: 0, .. } = e { + if let MsgError::RecvZero = e { vm_control_indices_to_remove.push(index); } else { error!("failed to recv VmRequest: {}", e); @@ -2040,7 +2066,7 @@ fn run_control( } } Err(e) => { - if let MsgError::BadRecvSize { actual: 0, .. } = e { + if let MsgError::RecvZero = e { vm_control_indices_to_remove.push(index); } else { error!("failed to recv VmMemoryControlRequest: {}", e); @@ -2056,7 +2082,7 @@ fn run_control( } } Err(e) => { - if let MsgError::BadRecvSize { actual: 0, .. } = e { + if let MsgError::RecvZero = e { vm_control_indices_to_remove.push(index); } else { error!("failed to recv VmIrqRequest: {}", e); diff --git a/src/main.rs b/src/main.rs index e33be66..7fd3eca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use std::string::String; use std::thread::sleep; use std::time::Duration; -use arch::Pstore; +use arch::{set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType}; use audio_streams::StreamEffect; use crosvm::{ argument::{self, print_help, set_arguments, Argument}, @@ -25,7 +25,7 @@ use crosvm::{ }; #[cfg(feature = "gpu")] use devices::virtio::gpu::{GpuMode, GpuParameters}; -use devices::{Ac97Backend, Ac97Parameters, SerialParameters, SerialType}; +use devices::{Ac97Backend, Ac97Parameters}; use disk::QcowFile; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; use sys_util::{ @@ -74,7 +74,7 @@ fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> { let mut cpuset = Vec::new(); for part in s.split(',') { let range: Vec<&str> = part.split('-').collect(); - if range.len() == 0 || range.len() > 2 { + if range.is_empty() || range.len() > 2 { return Err(argument::Error::InvalidValue { value: part.to_owned(), expected: String::from("invalid list syntax"), @@ -117,8 +117,8 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { if let Some(s) = s { let opts = s - .split(",") - .map(|frag| frag.split("=")) + .split(',') + .map(|frag| frag.split('=')) .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or(""))); for (k, v) in opts { @@ -252,8 +252,8 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> { let mut ac97_params: Ac97Parameters = Default::default(); let opts = s - .split(",") - .map(|frag| frag.split("=")) + .split(',') + .map(|frag| frag.split('=')) .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or(""))); for (k, v) in opts { @@ -273,7 +273,7 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> { } "capture_effects" => { ac97_params.capture_effects = v - .split("|") + .split('|') .map(|val| { val.parse::<StreamEffect>() .map_err(|e| argument::Error::InvalidValue { @@ -298,19 +298,27 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> { fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { let mut serial_setting = SerialParameters { type_: SerialType::Sink, + hardware: SerialHardware::Serial, path: None, + input: None, num: 1, console: false, + earlycon: false, stdin: false, }; let opts = s - .split(",") - .map(|frag| frag.split("=")) + .split(',') + .map(|frag| frag.split('=')) .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or(""))); for (k, v) in opts { match k { + "hardware" => { + serial_setting.hardware = v + .parse::<SerialHardware>() + .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))? + } "type" => { serial_setting.type_ = v .parse::<SerialType>() @@ -336,12 +344,33 @@ fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { )) })? } + "earlycon" => { + serial_setting.earlycon = v.parse::<bool>().map_err(|e| { + argument::Error::Syntax(format!( + "serial device earlycon is not parseable: {}", + e, + )) + })? + } "stdin" => { serial_setting.stdin = v.parse::<bool>().map_err(|e| { argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e)) - })? + })?; + if serial_setting.stdin && serial_setting.input.is_some() { + return Err(argument::Error::TooManyArguments( + "Cannot specify both stdin and input options".to_string(), + )); + } } "path" => serial_setting.path = Some(PathBuf::from(v)), + "input" => { + if serial_setting.stdin { + return Err(argument::Error::TooManyArguments( + "Cannot specify both stdin and input options".to_string(), + )); + } + serial_setting.input = Some(PathBuf::from(v)); + } _ => { return Err(argument::Error::UnknownArgument(format!( "serial parameter {}", @@ -355,7 +384,7 @@ fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { } fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { - let components: Vec<&str> = value.split(":").collect(); + let components: Vec<&str> = value.split(':').collect(); if components.is_empty() || components.len() > 3 || components[0].is_empty() { return Err(argument::Error::InvalidValue { value: value.to_owned(), @@ -402,7 +431,7 @@ fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { } fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> { - let components: Vec<&str> = value.split(":").collect(); + let components: Vec<&str> = value.split(':').collect(); if components.is_empty() || components.len() > 3 || components[0].is_empty() { return Err(argument::Error::InvalidValue { value: value.to_owned(), @@ -501,7 +530,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: ) } "cpu-affinity" => { - if cfg.vcpu_affinity.len() != 0 { + if !cfg.vcpu_affinity.is_empty() { return Err(argument::Error::TooManyArguments( "`cpu-affinity` already given".to_owned(), )); @@ -532,10 +561,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "serial" => { let serial_params = parse_serial_options(value.unwrap())?; let num = serial_params.num; - if cfg.serial_parameters.contains_key(&num) { + let key = (serial_params.hardware, num); + if cfg.serial_parameters.contains_key(&key) { return Err(argument::Error::TooManyArguments(format!( - "serial num {}", - num + "serial hardware {} num {}", + serial_params.hardware, num, ))); } @@ -543,8 +573,29 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: for params in cfg.serial_parameters.values() { if params.console { return Err(argument::Error::TooManyArguments(format!( - "serial device {} already set as console", - params.num + "{} device {} already set as console", + params.hardware, params.num, + ))); + } + } + } + + if serial_params.earlycon { + // Only SerialHardware::Serial supports earlycon= currently. + match serial_params.hardware { + SerialHardware::Serial => {} + _ => { + return Err(argument::Error::InvalidValue { + value: serial_params.hardware.to_string().to_owned(), + expected: String::from("earlycon not supported for hardware"), + }); + } + } + for params in cfg.serial_parameters.values() { + if params.earlycon { + return Err(argument::Error::TooManyArguments(format!( + "{} device {} already set as earlycon", + params.hardware, params.num, ))); } } @@ -553,13 +604,13 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if serial_params.stdin { if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) { return Err(argument::Error::TooManyArguments(format!( - "serial device {} already connected to standard input", - previous_stdin.num + "{} device {} already connected to standard input", + previous_stdin.hardware, previous_stdin.num, ))); } } - cfg.serial_parameters.insert(num, serial_params); + cfg.serial_parameters.insert(key, serial_params); } "syslog-tag" => { if cfg.syslog_tag.is_some() { @@ -1030,7 +1081,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let reader = BufReader::new(file); for l in reader.lines() { let line = l.unwrap(); - let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim(); + let trimmed_line = line.splitn(2, '#').next().unwrap().trim(); if !trimmed_line.is_empty() { let mount = parse_plugin_mount_option(trimmed_line)?; cfg.plugin_mounts.push(mount); @@ -1049,7 +1100,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let reader = BufReader::new(file); for l in reader.lines() { let line = l.unwrap(); - let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim(); + let trimmed_line = line.splitn(2, '#').next().unwrap().trim(); if !trimmed_line.is_empty() { let map = parse_plugin_gid_map_option(trimmed_line)?; cfg.plugin_gid_maps.push(map); @@ -1084,7 +1135,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "`single-touch` already given".to_owned(), )); } - let mut it = value.unwrap().split(":"); + let mut it = value.unwrap().split(':'); let mut single_touch_spec = TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned())); @@ -1102,7 +1153,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "`trackpad` already given".to_owned(), )); } - let mut it = value.unwrap().split(":"); + let mut it = value.unwrap().split(':'); let mut trackpad_spec = TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned())); @@ -1214,6 +1265,7 @@ fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Err } } } + set_default_serial_parameters(&mut cfg.serial_parameters); Ok(()) } @@ -1326,10 +1378,9 @@ Possible key values: effect value now is EchoCancellation or aec. "#, ), - Argument::value( "serial", - "type=TYPE,[num=NUM,path=PATH,console,stdin]", + "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]", "\ Comma separated key=value pairs for setting up serial devices. Can be given more than once. @@ -1339,16 +1390,26 @@ Possible key values: type=(stdout,syslog,sink,file) Where to route the serial device. + hardware=(serial,virtio-console) + Which type of serial hardware to emulate. Defaults to 8250 UART + (serial). + num=(1,2,3,4) Serial Device Number. If not provided, num will default to 1. path=PATH The path to the file to write to when type=file. + input=PATH + The path to the file to read from when not stdin. + console Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided. + earlycon + Use this serial device as the early console. Can only be given once. + stdin Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided. @@ -1738,7 +1799,7 @@ fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> { println!("Set the balloon size of the crosvm instance to `SIZE` bytes."); return Err(()); } - let num_bytes = match args.nth(0).unwrap().parse::<u64>() { + let num_bytes = match args.next().unwrap().parse::<u64>() { Ok(n) => n, Err(_) => { error!("Failed to parse number of bytes"); @@ -1750,6 +1811,19 @@ fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> { vms_request(&VmRequest::BalloonCommand(command), args) } +fn balloon_stats(args: std::env::Args) -> std::result::Result<(), ()> { + if args.len() != 1 { + print_help("crosvm balloon_stats", "VM_SOCKET", &[]); + println!("Prints virtio balloon statistics for a `VM_SOCKET`."); + return Err(()); + } + let command = BalloonControlCommand::Stats {}; + let request = &VmRequest::BalloonCommand(command); + let response = handle_request(request, args)?; + println!("{}", response); + Ok(()) +} + fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> { let arguments = [ Argument::positional("PATH", "where to create the qcow2 image"), @@ -1796,7 +1870,7 @@ fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> { .map_err(|e| { error!("Unable to parse command line arguments: {}", e); })?; - if file_path.len() == 0 || !(size.is_some() ^ backing_file.is_some()) { + if file_path.is_empty() || !(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 @@ -1837,11 +1911,11 @@ fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> { println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET"); return Err(()); } - let subcommand: &str = &args.nth(0).unwrap(); + let subcommand: &str = &args.next().unwrap(); let request = match subcommand { "resize" => { - let disk_index = match args.nth(0).unwrap().parse::<usize>() { + let disk_index = match args.next().unwrap().parse::<usize>() { Ok(n) => n, Err(_) => { error!("Failed to parse disk index"); @@ -1849,7 +1923,7 @@ fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> { } }; - let new_size = match args.nth(0).unwrap().parse::<u64>() { + let new_size = match args.next().unwrap().parse::<u64>() { Ok(n) => n, Err(_) => { error!("Failed to parse disk size"); @@ -1911,7 +1985,7 @@ type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>; fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> { debug!("parse_bus_id_addr: {}", v); - let mut ids = v.split(":"); + let mut ids = v.split(':'); match (ids.next(), ids.next(), ids.next(), ids.next()) { (Some(bus_id), Some(addr), Some(vid), Some(pid)) => { let bus_id = bus_id @@ -2069,7 +2143,7 @@ fn pkg_version() -> std::result::Result<(), ()> { print!("crosvm {}", VERSION.unwrap_or("UNKNOWN")); match PKG_VERSION { Some(v) => println!("-{}", v), - None => println!(""), + None => println!(), } Ok(()) } @@ -2099,6 +2173,7 @@ fn crosvm_main() -> std::result::Result<(), ()> { Some("resume") => resume_vms(args), Some("run") => run_vm(args), Some("balloon") => balloon_vms(args), + Some("balloon_stats") => balloon_stats(args), Some("create_qcow2") => create_qcow2(args), Some("disk") => disk_cmd(args), Some("usb") => modify_usb(args), diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index ae7e19c..470d5f0 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -8,7 +8,7 @@ mod vcpu; use std::fmt::{self, Display}; use std::fs::File; use std::io; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::net::UnixDatagram; use std::path::Path; use std::result; @@ -181,10 +181,6 @@ impl Display for Error { type Result<T> = result::Result<T, Error>; -fn downcast_file<F: IntoRawFd>(f: F) -> File { - unsafe { File::from_raw_fd(f.into_raw_fd()) } -} - fn new_seqpacket_pair() -> SysResult<(UnixDatagram, UnixDatagram)> { let mut fds = [0, 0]; unsafe { diff --git a/src/plugin/process.rs b/src/plugin/process.rs index 51fc892..783239a 100644 --- a/src/plugin/process.rs +++ b/src/plugin/process.rs @@ -7,19 +7,17 @@ use std::env::set_var; use std::fs::File; use std::io::Write; use std::mem::transmute; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{IntoRawFd, RawFd}; use std::os::unix::net::UnixDatagram; use std::path::Path; use std::process::Command; use std::sync::{Arc, RwLock}; use std::thread::JoinHandle; -use net_util; use net_util::Error as NetError; use libc::{pid_t, waitpid, EINVAL, ENODATA, ENOTTY, WEXITSTATUS, WIFEXITED, WNOHANG, WTERMSIG}; -use protobuf; use protobuf::Message; use io_jail::Minijail; @@ -348,7 +346,7 @@ impl Process { read_only: bool, dirty_log: bool, ) -> SysResult<()> { - let shm = SharedMemory::from_raw_fd(memfd)?; + let shm = SharedMemory::from_file(memfd)?; // Checking the seals ensures the plugin process won't shrink the mmapped file, causing us // to SIGBUS in the future. let seals = shm.get_seals()?; @@ -517,7 +515,14 @@ impl Process { let request = protobuf::parse_from_bytes::<MainRequest>(&self.request_buffer[..msg_size]) .map_err(Error::DecodeRequest)?; - let mut response_files = Vec::new(); + /// Use this to make it easier to stuff various kinds of File-like objects into the + /// `boxed_fds` list. + fn box_owned_fd<F: IntoRawFd + 'static>(f: F) -> Box<dyn IntoRawFd> { + Box::new(f) + } + + // This vec is used to extend ownership of certain FDs until the end of this function. + let mut boxed_fds = Vec::new(); let mut response_fds = Vec::new(); let mut response = MainResponse::new(); let res = if request.has_create() { @@ -559,7 +564,7 @@ impl Process { Ok(()) => { response_fds.push(evt.as_raw_fd()); response_fds.push(resample_evt.as_raw_fd()); - response_files.push(downcast_file(resample_evt)); + boxed_fds.push(box_owned_fd(resample_evt)); entry.insert(PluginObject::IrqEvent { irq_id: irq_event.irq_id, evt, @@ -588,7 +593,7 @@ impl Process { Ok((request_socket, child_socket)) => { self.request_sockets.push(request_socket); response_fds.push(child_socket.as_raw_fd()); - response_files.push(downcast_file(child_socket)); + boxed_fds.push(box_owned_fd(child_socket)); Ok(()) } Err(e) => Err(e), diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index a5bf73c..4bd3ea8 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -13,7 +13,6 @@ use std::sync::{Arc, RwLock}; use libc::{EINVAL, ENOENT, ENOTTY, EPERM, EPIPE, EPROTO}; -use protobuf; use protobuf::Message; use assertions::const_assert; diff --git a/sys_util/src/descriptor.rs b/sys_util/src/descriptor.rs new file mode 100644 index 0000000..1af72fc --- /dev/null +++ b/sys_util/src/descriptor.rs @@ -0,0 +1,88 @@ +// 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 std::os::unix::io::RawFd; + +use crate::{errno_result, Result}; +use std::mem; +use std::ops::Drop; + +pub type RawDescriptor = RawFd; + +/// Trait for forfeiting ownership of the current raw descriptor, and returning the raw descriptor +pub trait IntoRawDescriptor { + fn into_raw_descriptor(self) -> RawDescriptor; +} + +/// Trait for returning the underlying raw descriptor, without giving up ownership of the +/// descriptor. +pub trait AsRawDescriptor { + fn as_raw_descriptor(&self) -> RawDescriptor; +} + +pub trait FromRawDescriptor { + /// # Safety + /// Safe only if the caller ensures nothing has access to the descriptor after passing it to + /// `from_raw_descriptor` + unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self; +} + +/// Wraps a RawDescriptor and safely closes it when self falls out of scope. +#[derive(Debug, PartialEq)] +pub struct SafeDescriptor { + descriptor: RawDescriptor, +} + +impl Drop for SafeDescriptor { + fn drop(&mut self) { + let _ = unsafe { libc::close(self.descriptor) }; + } +} + +impl AsRawDescriptor for SafeDescriptor { + fn as_raw_descriptor(&self) -> RawDescriptor { + self.descriptor + } +} + +impl IntoRawDescriptor for SafeDescriptor { + fn into_raw_descriptor(self) -> RawDescriptor { + let descriptor = self.descriptor; + mem::forget(self); + descriptor + } +} + +impl FromRawDescriptor for SafeDescriptor { + unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self { + SafeDescriptor { descriptor } + } +} + +impl SafeDescriptor { + /// Clones this descriptor, internally creating a new descriptor. The new SafeDescriptor will + /// share the same underlying count within the kernel. + pub fn try_clone(&self) -> Result<SafeDescriptor> { + // Safe because self.as_raw_descriptor() returns a valid value + let copy_fd = unsafe { libc::dup(self.as_raw_descriptor()) }; + if copy_fd < 0 { + return errno_result(); + } + // Safe becuase we just successfully duplicated and this object will uniquely + // own the raw descriptor. + Ok(unsafe { SafeDescriptor::from_raw_descriptor(copy_fd) }) + } +} + +/// For use cases where a simple wrapper around a RawDescriptor is needed. +/// This is a simply a wrapper and does not manage the lifetime of the descriptor. +/// Most usages should prefer SafeDescriptor or using a RawDescriptor directly +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Descriptor(pub RawDescriptor); +impl AsRawDescriptor for Descriptor { + fn as_raw_descriptor(&self) -> RawDescriptor { + self.0 + } +} diff --git a/sys_util/src/ioctl.rs b/sys_util/src/ioctl.rs index a0309ff..f8c7604 100644 --- a/sys_util/src/ioctl.rs +++ b/sys_util/src/ioctl.rs @@ -7,8 +7,6 @@ use std::os::raw::*; use std::os::unix::io::AsRawFd; -use libc; - /// Raw macro to declare the expression that calculates an ioctl number #[macro_export] macro_rules! ioctl_expr { diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs index 357e6b4..98c9dc6 100644 --- a/sys_util/src/lib.rs +++ b/sys_util/src/lib.rs @@ -14,6 +14,7 @@ pub mod ioctl; pub mod syslog; mod capabilities; mod clock; +mod descriptor; mod errno; mod eventfd; mod file_flags; @@ -41,8 +42,8 @@ pub use crate::affinity::*; pub use crate::alloc::LayoutAllocation; pub use crate::capabilities::drop_capabilities; pub use crate::clock::{Clock, FakeClock}; -use crate::errno::errno_result; -pub use crate::errno::{Error, Result}; +pub use crate::descriptor::*; +pub use crate::errno::{errno_result, Error, Result}; pub use crate::eventfd::*; pub use crate::file_flags::*; pub use crate::fork::*; diff --git a/sys_util/src/mmap.rs b/sys_util/src/mmap.rs index 006b0a8..c6a52ea 100644 --- a/sys_util/src/mmap.rs +++ b/sys_util/src/mmap.rs @@ -6,10 +6,9 @@ //! mmap object leaves scope. use std::cmp::min; -use std::collections::BTreeMap; use std::fmt::{self, Display}; use std::io; -use std::mem::{size_of, ManuallyDrop}; +use std::mem::size_of; use std::os::unix::io::AsRawFd; use std::ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned}; @@ -28,8 +27,6 @@ pub enum Error { InvalidOffset, /// Requested mapping is not page aligned NotPageAligned, - /// Overlapping regions - Overlapping(usize, usize), /// Requested memory range spans past the end of the region. InvalidRange(usize, usize, usize), /// `mmap` returned the given error. @@ -49,11 +46,6 @@ impl Display for Error { InvalidAddress => write!(f, "requested memory out of range"), InvalidOffset => write!(f, "requested offset is out of range of off_t"), NotPageAligned => write!(f, "requested memory is not page aligned"), - Overlapping(offset, count) => write!( - f, - "requested memory range overlaps with existing region: offset={} size={}", - offset, count - ), InvalidRange(offset, count, region_size) => write!( f, "requested memory range spans past the end of the region: offset={} count={} region_size={}", @@ -643,13 +635,6 @@ impl Drop for MemoryMapping { pub struct MemoryMappingArena { addr: *mut u8, size: usize, - // When doing in-place swaps of MemoryMappings, the BTreeMap returns a owned - // instance of the old MemoryMapping. When the old MemoryMapping falls out - // of scope, it calls munmap on the same region as the new MemoryMapping - // that was just mapped in. To avoid accidentally munmapping the new, - // MemoryMapping, all mappings are wrapped in a ManuallyDrop, and then - // "forgotten" when removed from the BTreeMap - maps: BTreeMap<usize, ManuallyDrop<MemoryMapping>>, } // Send and Sync aren't automatically inherited for the raw address pointer. @@ -666,17 +651,7 @@ impl MemoryMappingArena { /// * `size` - Size of memory region in bytes. pub fn new(size: usize) -> Result<MemoryMappingArena> { // Reserve the arena's memory using an anonymous read-only mmap. - // The actual MemoryMapping object is forgotten, with - // MemoryMappingArena manually calling munmap on drop. - let mmap = MemoryMapping::new_protection(size, Protection::none().set_read())?; - let addr = mmap.as_ptr(); - let size = mmap.size(); - std::mem::forget(mmap); - Ok(MemoryMappingArena { - addr, - size, - maps: BTreeMap::new(), - }) + MemoryMapping::new_protection(size, Protection::none().set_read()).map(From::from) } /// Anonymously maps `size` bytes at `offset` bytes from the start of the arena. @@ -755,58 +730,43 @@ impl MemoryMappingArena { let mmap = unsafe { match fd { Some((fd, fd_offset)) => MemoryMapping::from_fd_offset_protection_fixed( - (self.addr as usize + offset) as *mut u8, + self.addr.add(offset), fd, size, fd_offset, prot, )?, - None => MemoryMapping::new_protection_fixed( - (self.addr as usize + offset) as *mut u8, - size, - prot, - )?, + None => MemoryMapping::new_protection_fixed(self.addr.add(offset), size, prot)?, } }; - self.maps.insert(offset, ManuallyDrop::new(mmap)); + // This mapping will get automatically removed when we drop the whole arena. + std::mem::forget(mmap); Ok(()) } - /// Removes a mapping at `offset` from the start of the arena. - /// Returns a boolean indicating if there was a mapping present at `offset`. - /// If none was present, this method is a noop. - pub fn remove(&mut self, offset: usize) -> Result<bool> { - if let Some(mmap) = self.maps.remove(&offset) { - // Instead of munmapping the memory map, leaving an unprotected hole - // in the arena, swap this mmap with an anonymous protection. - // This is safe since the memory mapping perfectly overlaps with an - // existing, known good memory mapping. - let mmap = unsafe { - MemoryMapping::new_protection_fixed( - mmap.as_ptr(), - mmap.size(), - Protection::none().set_read(), - )? - }; - self.maps.insert(offset, ManuallyDrop::new(mmap)); - Ok(true) - } else { - Ok(false) - } + /// Removes `size` bytes at `offset` bytes from the start of the arena. `offset` must be page + /// aligned. + /// + /// # Arguments + /// * `offset` - Page aligned offset into the arena in bytes. + /// * `size` - Size of memory region in bytes. + pub fn remove(&mut self, offset: usize, size: usize) -> Result<()> { + self.try_add(offset, size, Protection::read(), None) } - /// Calls msync with MS_SYNC on the mapping at `offset` from the start of - /// the arena. - /// Returns a boolean indicating if there was a mapping present at `offset`. - /// If none was present, this method is a noop. - pub fn msync(&self, offset: usize) -> Result<bool> { - if let Some(mmap) = self.maps.get(&offset) { - mmap.msync()?; - Ok(true) - } else { - Ok(false) + /// Calls msync with MS_SYNC on a mapping of `size` bytes starting at `offset` from the start of + /// the arena. `offset` must be page aligned. + pub fn msync(&self, offset: usize, size: usize) -> Result<()> { + self.validate_range(offset, size)?; + + // Safe because we've validated that this memory range is owned by this `MemoryMappingArena`. + let ret = + unsafe { libc::msync(self.addr as *mut libc::c_void, self.size(), libc::MS_SYNC) }; + if ret == -1 { + return Err(Error::SystemCallFailed(errno::Error::last())); } + Ok(()) } /// Returns a pointer to the beginning of the memory region. Should only be @@ -836,32 +796,26 @@ impl MemoryMappingArena { if end_offset > self.size { return Err(Error::InvalidAddress); } - // Ensure offset..offset+size doesn't overlap with existing regions - // Find the offset + size of the first mapping before the desired offset - let (prev_offset, prev_size) = match self.maps.range(..offset).rev().next() { - Some((offset, mmap)) => (*offset, mmap.size()), - None => { - // Empty map - return Ok(()); - } - }; - if offset == prev_offset { - // Perfectly overlapping regions are allowed - if size != prev_size { - return Err(Error::Overlapping(offset, size)); - } - } else if offset < (prev_offset + prev_size) { - return Err(Error::Overlapping(offset, size)); - } - Ok(()) } } +impl From<MemoryMapping> for MemoryMappingArena { + fn from(mmap: MemoryMapping) -> Self { + let addr = mmap.as_ptr(); + let size = mmap.size(); + + // Forget the original mapping because the `MemoryMappingArena` will take care of calling + // `munmap` when it is dropped. + std::mem::forget(mmap); + MemoryMappingArena { addr, size } + } +} + impl Drop for MemoryMappingArena { fn drop(&mut self) { - // This is safe because we mmap the area at addr ourselves, and nobody - // else is holding a reference to it. + // This is safe because we own this memory range, and nobody else is holding a reference to + // it. unsafe { libc::munmap(self.addr as *mut libc::c_void, self.size); } @@ -977,22 +931,8 @@ mod tests { fn arena_remove() { let mut m = MemoryMappingArena::new(0x40000).unwrap(); assert!(m.add_anon(0, pagesize() * 4).is_ok()); - assert!(m.remove(0).unwrap(), true); - assert!(m.remove(0).unwrap(), false); - } - - #[test] - fn arena_add_overlap_error() { - let page = pagesize(); - let mut m = MemoryMappingArena::new(page * 4).unwrap(); - assert!(m.add_anon(0, page * 4).is_ok()); - let res = m.add_anon(page, page).unwrap_err(); - match res { - Error::Overlapping(a, o) => { - assert_eq!((a, o), (page, page)); - } - e => panic!("unexpected error: {}", e), - } + assert!(m.remove(0, pagesize()).is_ok()); + assert!(m.remove(0, pagesize() * 2).is_ok()); } #[test] @@ -1015,4 +955,62 @@ mod tests { e => panic!("unexpected error: {}", e), } } + + #[test] + fn arena_add_overlapping() { + let ps = pagesize(); + let mut m = + MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`"); + m.add_anon(ps * 4, ps * 4) + .expect("failed to add sub-mapping"); + + // Overlap in the front. + m.add_anon(ps * 2, ps * 3) + .expect("failed to add front overlapping sub-mapping"); + + // Overlap in the back. + m.add_anon(ps * 7, ps * 3) + .expect("failed to add back overlapping sub-mapping"); + + // Overlap the back of the first mapping, all of the middle mapping, and the front of the + // last mapping. + m.add_anon(ps * 3, ps * 6) + .expect("failed to add mapping that overlaps several mappings"); + } + + #[test] + fn arena_remove_overlapping() { + let ps = pagesize(); + let mut m = + MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`"); + m.add_anon(ps * 4, ps * 4) + .expect("failed to add sub-mapping"); + m.add_anon(ps * 2, ps * 2) + .expect("failed to add front overlapping sub-mapping"); + m.add_anon(ps * 8, ps * 2) + .expect("failed to add back overlapping sub-mapping"); + + // Remove the back of the first mapping and the front of the second. + m.remove(ps * 3, ps * 2) + .expect("failed to remove front overlapping mapping"); + + // Remove the back of the second mapping and the front of the third. + m.remove(ps * 7, ps * 2) + .expect("failed to remove back overlapping mapping"); + + // Remove a mapping that completely overlaps the middle mapping. + m.remove(ps * 5, ps * 2) + .expect("failed to remove fully overlapping mapping"); + } + + #[test] + fn arena_remove_unaligned() { + let ps = pagesize(); + let mut m = + MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`"); + + m.add_anon(0, ps).expect("failed to add mapping"); + m.remove(0, ps - 1) + .expect("failed to remove unaligned mapping"); + } } diff --git a/sys_util/src/shm.rs b/sys_util/src/shm.rs index d158230..ee5f5a3 100644 --- a/sys_util/src/shm.rs +++ b/sys_util/src/shm.rs @@ -5,7 +5,7 @@ use std::ffi::{CStr, CString}; use std::fs::{read_link, File}; use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use libc::{ self, c_char, c_int, c_long, c_uint, close, fcntl, ftruncate64, off64_t, syscall, EINVAL, @@ -136,13 +136,11 @@ impl SharedMemory { Ok(SharedMemory { fd: file, size: 0 }) } - /// Constructs a `SharedMemory` instance from a file descriptor that represents shared memory. + /// Constructs a `SharedMemory` instance from a `File` that represents shared memory. /// /// The size of the resulting shared memory will be determined using `File::seek`. If the given /// file's size can not be determined this way, this will return an error. - pub fn from_raw_fd<T: IntoRawFd>(fd: T) -> Result<SharedMemory> { - // Safe because the IntoRawFd trait indicates fd has unique ownership. - let mut file = unsafe { File::from_raw_fd(fd.into_raw_fd()) }; + pub fn from_file(mut file: File) -> Result<SharedMemory> { let file_size = file.seek(SeekFrom::End(0))?; Ok(SharedMemory { fd: file, diff --git a/sys_util/src/struct_util.rs b/sys_util/src/struct_util.rs index 551204e..aa2ca39 100644 --- a/sys_util/src/struct_util.rs +++ b/sys_util/src/struct_util.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std; use std::io::Read; use std::mem::size_of; diff --git a/sys_util/src/timerfd.rs b/sys_util/src/timerfd.rs index 8e5d91b..f2a3ff8 100644 --- a/sys_util/src/timerfd.rs +++ b/sys_util/src/timerfd.rs @@ -34,7 +34,7 @@ impl TimerFd { /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents /// the period for repeated expirations after the initial expiration. Otherwise /// the timer will expire just once. Cancels any existing duration and repeating interval. - pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> { + pub fn reset(&self, dur: Duration, interval: Option<Duration>) -> Result<()> { // Safe because we are zero-initializing a struct with only primitive member fields. let mut spec: libc::itimerspec = unsafe { mem::zeroed() }; spec.it_value.tv_sec = dur.as_secs() as libc::time_t; @@ -61,7 +61,7 @@ impl TimerFd { /// Waits until the timer expires. The return value represents the number of times the timer /// has expired since the last time `wait` was called. If the timer has not yet expired once /// this call will block until it does. - pub fn wait(&mut self) -> Result<u64> { + pub fn wait(&self) -> Result<u64> { let mut count = 0u64; // Safe because this will only modify |buf| and we check the return value. @@ -96,7 +96,7 @@ impl TimerFd { } /// Disarms the timer. - pub fn clear(&mut self) -> Result<()> { + pub fn clear(&self) -> Result<()> { // Safe because we are zero-initializing a struct with only primitive member fields. let spec: libc::itimerspec = unsafe { mem::zeroed() }; @@ -222,7 +222,7 @@ mod tests { #[test] fn one_shot() { - let mut tfd = TimerFd::new().expect("failed to create timerfd"); + let tfd = TimerFd::new().expect("failed to create timerfd"); assert_eq!(tfd.is_armed().unwrap(), false); let dur = Duration::from_millis(200); @@ -239,7 +239,7 @@ mod tests { #[test] fn repeating() { - let mut tfd = TimerFd::new().expect("failed to create timerfd"); + let tfd = TimerFd::new().expect("failed to create timerfd"); let dur = Duration::from_millis(200); let interval = Duration::from_millis(100); diff --git a/tests/boot.rs b/tests/boot.rs index b4e38e1..9c2da3c 100644 --- a/tests/boot.rs +++ b/tests/boot.rs @@ -13,8 +13,9 @@ use std::sync::Once; use libc::{cpu_set_t, sched_getaffinity}; +use arch::{set_default_serial_parameters, SerialHardware, SerialParameters, SerialType}; use crosvm::{linux, Config, Executable}; -use devices::{SerialParameters, SerialType}; +use sys_util::syslog; const CHROOT_KERNEL_PATH: &str = "/mnt/host/source/src/third_party/kernel/v4.19/"; const CONTAINER_VM_DEFCONFIG: &str = "arch/x86/configs/chromiumos-container-vm-x86_64_defconfig"; @@ -220,20 +221,26 @@ fn prepare_kernel() -> PathBuf { #[test] fn boot() { + syslog::init().unwrap(); + let kernel_path = prepare_kernel(); let mut c = Config::default(); c.sandbox = false; c.serial_parameters.insert( - 1, + (SerialHardware::Serial, 1), SerialParameters { type_: SerialType::Sink, + hardware: SerialHardware::Serial, path: None, + input: None, num: 1, console: false, + earlycon: false, stdin: false, }, ); + set_default_serial_parameters(&mut c.serial_parameters); c.executable_path = Some(Executable::Kernel(kernel_path)); let r = linux::run_config(c); diff --git a/tests/plugins.rs b/tests/plugins.rs index c45096f..1675a75 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -75,7 +75,7 @@ fn build_plugin(src: &str) -> RemovePath { let status = child.wait().expect("failed to wait for compiler"); assert!(status.success(), "failed to build plugin"); - RemovePath(PathBuf::from(out_bin)) + RemovePath(out_bin) } fn run_plugin(bin_path: &Path, with_sandbox: bool) { diff --git a/usb_util/src/error.rs b/usb_util/src/error.rs index 24d8036..409be98 100644 --- a/usb_util/src/error.rs +++ b/usb_util/src/error.rs @@ -4,7 +4,6 @@ use libc::c_ulong; use remain::sorted; -use std; use std::fmt::{self, Display}; use std::io; use std::num; diff --git a/vhost/src/net.rs b/vhost/src/net.rs index 7d49f17..9b59cf2 100644 --- a/vhost/src/net.rs +++ b/vhost/src/net.rs @@ -2,13 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use libc; use net_util::TapT; use std::fs::{File, OpenOptions}; use std::marker::PhantomData; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; -use virtio_sys; use sys_util::{ioctl_with_ref, GuestMemory}; diff --git a/vhost/src/vsock.rs b/vhost/src/vsock.rs index f453027..8bd27e7 100644 --- a/vhost/src/vsock.rs +++ b/vhost/src/vsock.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use libc; use std::fs::{File, OpenOptions}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs index b01f4af..a1d2964 100644 --- a/vm_control/src/lib.rs +++ b/vm_control/src/lib.rs @@ -123,6 +123,43 @@ pub struct BalloonStats { pub hugetlb_failures: Option<u64>, } +impl Display for BalloonStats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{{")?; + if let Some(swap_in) = self.swap_in { + write!(f, "\n swap_in: {}", swap_in)?; + } + if let Some(swap_out) = self.swap_out { + write!(f, "\n swap_out: {}", swap_out)?; + } + if let Some(major_faults) = self.major_faults { + write!(f, "\n major_faults: {}", major_faults)?; + } + if let Some(minor_faults) = self.minor_faults { + write!(f, "\n minor_faults: {}", minor_faults)?; + } + if let Some(free_memory) = self.free_memory { + write!(f, "\n free_memory: {}", free_memory)?; + } + if let Some(total_memory) = self.total_memory { + write!(f, "\n total_memory: {}", total_memory)?; + } + if let Some(available_memory) = self.available_memory { + write!(f, "\n available_memory: {}", available_memory)?; + } + if let Some(disk_caches) = self.disk_caches { + write!(f, "\n disk_caches: {}", disk_caches)?; + } + if let Some(hugetlb_allocations) = self.hugetlb_allocations { + write!(f, "\n hugetlb_allocations: {}", hugetlb_allocations)?; + } + if let Some(hugetlb_failures) = self.hugetlb_failures { + write!(f, "\n hugetlb_failures: {}", hugetlb_failures)?; + } + write!(f, "\n}}") + } +} + #[derive(MsgOnSocket, Debug)] pub enum BalloonControlResult { Stats { @@ -413,7 +450,12 @@ pub enum VmMsyncRequest { /// Flush the content of a memory mapping to its backing file. /// `slot` selects the arena (as returned by `Vm::add_mmap_arena`). /// `offset` is the offset of the mapping to sync within the arena. - MsyncArena { slot: u32, offset: usize }, + /// `size` is the size of the mapping to sync within the arena. + MsyncArena { + slot: u32, + offset: usize, + size: usize, + }, } #[derive(MsgOnSocket, Debug)] @@ -434,11 +476,10 @@ impl VmMsyncRequest { pub fn execute(&self, vm: &mut Vm) -> VmMsyncResponse { use self::VmMsyncRequest::*; match *self { - MsyncArena { slot, offset } => { + MsyncArena { slot, offset, size } => { if let Some(arena) = vm.get_mmap_arena(slot) { - match arena.msync(offset) { - Ok(true) => VmMsyncResponse::Ok, - Ok(false) => VmMsyncResponse::Err(SysError::new(EINVAL)), + match arena.msync(offset, size) { + Ok(()) => VmMsyncResponse::Ok, Err(e) => match e { MmapError::SystemCallFailed(errno) => VmMsyncResponse::Err(errno), _ => VmMsyncResponse::Err(SysError::new(EINVAL)), @@ -509,11 +550,23 @@ fn register_memory( }; let addr = match allocation { - Some((Alloc::PciBar { bus, dev, bar }, offset)) => { + Some(( + Alloc::PciBar { + bus, + dev, + func, + bar, + }, + offset, + )) => { match allocator .mmio_allocator(MmioType::High) - .get(&Alloc::PciBar { bus, dev, bar }) - { + .get(&Alloc::PciBar { + bus, + dev, + func, + bar, + }) { Some((start_addr, length, _)) => { let address = *start_addr + offset; let range = *start_addr..*start_addr + *length; @@ -574,10 +627,30 @@ impl VmRequest { *run_mode = Some(VmRunMode::Running); VmResponse::Ok } - VmRequest::BalloonCommand(ref command) => match balloon_host_socket.send(command) { - Ok(_) => VmResponse::Ok, - Err(_) => VmResponse::Err(SysError::last()), - }, + VmRequest::BalloonCommand(BalloonControlCommand::Adjust { num_bytes }) => { + match balloon_host_socket.send(&BalloonControlCommand::Adjust { num_bytes }) { + Ok(_) => VmResponse::Ok, + Err(_) => VmResponse::Err(SysError::last()), + } + } + VmRequest::BalloonCommand(BalloonControlCommand::Stats) => { + match balloon_host_socket.send(&BalloonControlCommand::Stats {}) { + Ok(_) => match balloon_host_socket.recv() { + Ok(BalloonControlResult::Stats { + stats, + balloon_actual, + }) => VmResponse::BalloonStats { + stats, + balloon_actual, + }, + Err(e) => { + error!("balloon socket recv failed: {}", e); + VmResponse::Err(SysError::last()) + } + }, + Err(_) => VmResponse::Err(SysError::last()), + } + } VmRequest::DiskCommand { disk_index, ref command, @@ -639,6 +712,11 @@ pub enum VmResponse { slot: u32, desc: GpuMemoryDesc, }, + /// Results of balloon control commands. + BalloonStats { + stats: BalloonStats, + balloon_actual: u64, + }, /// Results of usb control commands. UsbResponse(UsbControlResult), } @@ -660,6 +738,14 @@ impl Display for VmResponse { "gpu memory allocated and registered to page frame number {:#x} and memory slot {}", pfn, slot ), + BalloonStats { + stats, + balloon_actual, + } => write!( + f, + "balloon size: {}\nballoon stats: {}", + balloon_actual, stats + ), UsbResponse(result) => write!(f, "usb control request get result {:?}", result), } } diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs index 3600d16..14b21a7 100644 --- a/x86_64/src/acpi.rs +++ b/x86_64/src/acpi.rs @@ -40,8 +40,8 @@ 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); +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; diff --git a/x86_64/src/cpuid.rs b/x86_64/src/cpuid.rs index 46294b2..b32298c 100644 --- a/x86_64/src/cpuid.rs +++ b/x86_64/src/cpuid.rs @@ -6,9 +6,6 @@ use std::arch::x86_64::{__cpuid, __cpuid_count}; use std::fmt::{self, Display}; use std::result; -use kvm; -use sys_util; - #[derive(Debug, PartialEq)] pub enum Error { GetSupportedCpusFailed(sys_util::Error), diff --git a/x86_64/src/interrupts.rs b/x86_64/src/interrupts.rs index 5fba859..3be9940 100644 --- a/x86_64/src/interrupts.rs +++ b/x86_64/src/interrupts.rs @@ -7,9 +7,7 @@ use std::fmt::{self, Display}; use std::mem; use std::result; -use kvm; use kvm_sys::kvm_lapic_state; -use sys_util; #[derive(Debug)] pub enum Error { diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index a4ba444..a6a02bc 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -54,11 +54,14 @@ use std::mem; use std::sync::Arc; use crate::bootparam::boot_params; -use arch::{RunnableLinuxVm, VmComponents, VmImage}; +use arch::{ + get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters, + 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, + Ioapic, PciAddress, PciConfigIo, PciDevice, PciInterruptPin, Pic, IOAPIC_BASE_ADDRESS, + IOAPIC_MEM_LENGTH_BYTES, }; use io_jail::Minijail; use kvm::*; @@ -90,6 +93,7 @@ pub enum Error { CreateVm(sys_util::Error), E820Configuration, EnableSplitIrqchip(sys_util::Error), + GetSerialCmdline(GetSerialCmdlineError), KernelOffsetPastEnd, LoadBios(io::Error), LoadBzImage(bzimage::Error), @@ -139,6 +143,7 @@ impl Display for Error { 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), + GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", 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), @@ -172,8 +177,8 @@ pub struct X8664arch; const BOOT_STACK_POINTER: u64 = 0x8000; // Make sure it align to 256MB for MTRR convenient -const MEM_32BIT_GAP_SIZE: u64 = (768 << 20); -const FIRST_ADDR_PAST_32BITS: u64 = (1 << 32); +const MEM_32BIT_GAP_SIZE: u64 = 768 << 20; +const FIRST_ADDR_PAST_32BITS: u64 = 1 << 32; const END_ADDR_BEFORE_32BITS: u64 = FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE; const MMIO_SIZE: u64 = MEM_32BIT_GAP_SIZE - 0x8000000; const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200; @@ -207,7 +212,7 @@ fn configure_system( cmdline_addr: GuestAddress, cmdline_size: usize, num_cpus: u8, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, setup_data: Option<GuestAddress>, initrd: Option<(GuestAddress, usize)>, mut params: boot_params, @@ -331,7 +336,7 @@ impl arch::LinuxArch for X8664arch { mut components: VmComponents, split_irqchip: bool, ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap<u8, SerialParameters>, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option<Minijail>, create_devices: F, ) -> Result<RunnableLinuxVm> @@ -421,7 +426,7 @@ impl arch::LinuxArch for X8664arch { suspend_evt.try_clone().map_err(Error::CloneEventFd)?, )?; - let stdio_serial_num = Self::setup_serial_devices( + Self::setup_serial_devices( &mut vm, &mut io_bus, &mut gsi_relay, @@ -459,7 +464,11 @@ impl arch::LinuxArch for X8664arch { match components.vm_image { VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?, VmImage::Kernel(ref mut kernel_image) => { - let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); + let mut cmdline = Self::get_base_linux_cmdline(); + + get_serial_cmdline(&mut cmdline, serial_parameters, "io") + .map_err(Error::GetSerialCmdline)?; + for param in components.extra_kernel_params { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } @@ -579,7 +588,7 @@ impl X8664arch { vcpu_count: u32, cmdline: &CStr, initrd_file: Option<File>, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, android_fstab: Option<File>, kernel_end: u64, params: boot_params, @@ -719,12 +728,8 @@ impl X8664arch { } /// This returns a minimal kernel command for this architecture - fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline { + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize); - if let Some(stdio_serial_num) = stdio_serial_num { - let tty_string = get_serial_tty_string(stdio_serial_num); - cmdline.insert("console", &tty_string).unwrap(); - } cmdline.insert_str("pci=noacpi reboot=k panic=-1").unwrap(); cmdline @@ -848,13 +853,13 @@ impl X8664arch { vm: &mut Vm, io_bus: &mut devices::Bus, gsi_relay: &mut Option<GsiRelay>, - serial_parameters: &BTreeMap<u8, SerialParameters>, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option<Minijail>, - ) -> Result<Option<u8>> { + ) -> Result<()> { let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?; let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?; - let stdio_serial_num = arch::add_serial_devices( + arch::add_serial_devices( io_bus, &com_evt_1_3, &com_evt_2_4, @@ -873,7 +878,7 @@ impl X8664arch { .map_err(Error::RegisterIrqfd)?; } - Ok(stdio_serial_num) + Ok(()) } /// Configures the vcpu and should be called once per vcpu from the vcpu's thread. diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs index cac9e58..218f561 100644 --- a/x86_64/src/mptable.rs +++ b/x86_64/src/mptable.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::convert::TryFrom; use std::fmt::{self, Display}; use std::mem; use std::result; @@ -10,7 +11,7 @@ use std::slice; use libc::c_char; use data_model::VolatileMemory; -use devices::PciInterruptPin; +use devices::{PciAddress, PciInterruptPin}; use sys_util::{GuestAddress, GuestMemory}; use crate::mpspec::*; @@ -117,14 +118,16 @@ fn compute_mp_size(num_cpus: u8) -> usize { pub fn setup_mptable( mem: &GuestMemory, num_cpus: u8, - pci_irqs: Vec<(u32, PciInterruptPin)>, + pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, ) -> Result<()> { - const PCI_BUS_ID: u8 = 0; - const ISA_BUS_ID: u8 = 1; - // Used to keep track of the next base pointer into the MP table. let mut base_mp = GuestAddress(MPTABLE_START); + // Calculate ISA bus number in the system, report at least one PCI bus. + let isa_bus_id = match pci_irqs.iter().max_by_key(|v| v.0.bus) { + Some(pci_irq) => pci_irq.0.bus + 1, + _ => 1, + }; let mp_size = compute_mp_size(num_cpus); // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without @@ -194,11 +197,11 @@ pub fn setup_mptable( base_mp = base_mp.unchecked_add(size as u64); checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic)); } - { + for pci_bus_id in 0..isa_bus_id { let size = mem::size_of::<mpc_bus>(); let mut mpc_bus = mpc_bus::default(); mpc_bus.type_ = MP_BUS as u8; - mpc_bus.busid = PCI_BUS_ID; + mpc_bus.busid = pci_bus_id; mpc_bus.bustype = BUS_TYPE_PCI; mem.write_obj_at_addr(mpc_bus, base_mp) .map_err(|_| Error::WriteMpcBus)?; @@ -209,7 +212,7 @@ pub fn setup_mptable( let size = mem::size_of::<mpc_bus>(); let mut mpc_bus = mpc_bus::default(); mpc_bus.type_ = MP_BUS as u8; - mpc_bus.busid = ISA_BUS_ID; + mpc_bus.busid = isa_bus_id; mpc_bus.bustype = BUS_TYPE_ISA; mem.write_obj_at_addr(mpc_bus, base_mp) .map_err(|_| Error::WriteMpcBus)?; @@ -222,7 +225,7 @@ pub fn setup_mptable( mpc_intsrc.type_ = MP_INTSRC as u8; mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8; mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_intsrc.srcbus = ISA_BUS_ID; + mpc_intsrc.srcbus = isa_bus_id; mpc_intsrc.srcbusirq = 0; mpc_intsrc.dstapic = 0; mpc_intsrc.dstirq = 0; @@ -239,7 +242,7 @@ pub fn setup_mptable( mpc_intsrc.type_ = MP_INTSRC as u8; mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8; mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_intsrc.srcbus = ISA_BUS_ID; + mpc_intsrc.srcbus = isa_bus_id; mpc_intsrc.srcbusirq = i; mpc_intsrc.dstapic = ioapicid; mpc_intsrc.dstirq = i; @@ -257,7 +260,7 @@ pub fn setup_mptable( mpc_intsrc.type_ = MP_INTSRC as u8; mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8; mpc_intsrc.irqflag = (MP_IRQDIR_HIGH | MP_LEVEL_TRIGGER) as u16; - mpc_intsrc.srcbus = ISA_BUS_ID; + mpc_intsrc.srcbus = isa_bus_id; mpc_intsrc.srcbusirq = sci_irq; mpc_intsrc.dstapic = ioapicid; mpc_intsrc.dstirq = sci_irq; @@ -268,16 +271,16 @@ pub fn setup_mptable( } let pci_irq_base = super::X86_64_IRQ_BASE as u8; // Insert PCI interrupts after platform IRQs. - for (i, pci_irq) in pci_irqs.iter().enumerate() { + for (address, irq_num, irq_pin) in pci_irqs.iter() { let size = mem::size_of::<mpc_intsrc>(); let mut mpc_intsrc = mpc_intsrc::default(); mpc_intsrc.type_ = MP_INTSRC as u8; mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8; mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_intsrc.srcbus = PCI_BUS_ID; - mpc_intsrc.srcbusirq = (pci_irq.0 as u8 + 1) << 2 | pci_irq.1.to_mask() as u8; + mpc_intsrc.srcbus = address.bus; + mpc_intsrc.srcbusirq = address.dev << 2 | irq_pin.to_mask() as u8; mpc_intsrc.dstapic = ioapicid; - mpc_intsrc.dstirq = pci_irq_base + i as u8; + mpc_intsrc.dstirq = u8::try_from(*irq_num).map_err(|_| Error::WriteMpcIntsrc)?; mem.write_obj_at_addr(mpc_intsrc, base_mp) .map_err(|_| Error::WriteMpcIntsrc)?; base_mp = base_mp.unchecked_add(size as u64); @@ -290,7 +293,7 @@ pub fn setup_mptable( mpc_intsrc.type_ = MP_INTSRC as u8; mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8; mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_intsrc.srcbus = ISA_BUS_ID; + mpc_intsrc.srcbus = isa_bus_id; mpc_intsrc.srcbusirq = i as u8; mpc_intsrc.dstapic = ioapicid; mpc_intsrc.dstirq = i as u8; @@ -305,7 +308,7 @@ pub fn setup_mptable( mpc_lintsrc.type_ = MP_LINTSRC as u8; mpc_lintsrc.irqtype = mp_irq_source_types_mp_ExtINT as u8; mpc_lintsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_lintsrc.srcbusid = ISA_BUS_ID; + mpc_lintsrc.srcbusid = isa_bus_id; mpc_lintsrc.srcbusirq = 0; mpc_lintsrc.destapic = 0; mpc_lintsrc.destapiclint = 0; @@ -320,7 +323,7 @@ pub fn setup_mptable( mpc_lintsrc.type_ = MP_LINTSRC as u8; mpc_lintsrc.irqtype = mp_irq_source_types_mp_NMI as u8; mpc_lintsrc.irqflag = MP_IRQDIR_DEFAULT as u16; - mpc_lintsrc.srcbusid = ISA_BUS_ID; + mpc_lintsrc.srcbusid = isa_bus_id; mpc_lintsrc.srcbusirq = 0; mpc_lintsrc.destapic = 0xFF; // Per SeaBIOS mpc_lintsrc.destapiclint = 1; diff --git a/x86_64/src/regs.rs b/x86_64/src/regs.rs index 344878a..1f5c816 100644 --- a/x86_64/src/regs.rs +++ b/x86_64/src/regs.rs @@ -7,7 +7,6 @@ use std::fmt::{self, Display}; use std::{mem, result}; use assertions::const_assert; -use kvm; use kvm_sys::kvm_fpu; use kvm_sys::kvm_msr_entry; use kvm_sys::kvm_msrs; |