diff options
author | Alyssa Ross <hi@alyssa.is> | 2020-03-26 12:26:18 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2020-03-26 12:26:18 +0000 |
commit | dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a (patch) | |
tree | 5a51c0af87b6a8bb3dfe43d599cabf96523a7f6f | |
parent | 88b7821302043b7ad871fcc0c7748573d0f140e2 (diff) | |
parent | 22964eab8874d41cf0eadf03dfeb1ffb653283e5 (diff) | |
download | crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar.gz crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar.bz2 crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar.lz crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar.xz crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.tar.zst crosvm-dbd5f925b6b2f4cb1bf87563be4d24c8ed35689a.zip |
Merge remote-tracking branch 'origin/master'
53 files changed, 1802 insertions, 584 deletions
diff --git a/Cargo.lock b/Cargo.lock index bb75235..e9da7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,7 @@ dependencies = [ "kvm 0.1.0", "kvm_sys 0.1.0", "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "libcras 0.1.0", "linux_input_sys 0.1.0", "msg_on_socket_derive 0.1.0", "msg_socket 0.1.0", diff --git a/README.md b/README.md index 09e8c1c..5756dbf 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,61 @@ an exploit in the devices. The channel #crosvm on [freenode](https://webchat.freenode.net/#crosvm) is used for technical discussion related to crosvm development and integration. -## Building with Docker +## Getting started + +### Building for CrOS + +crosvm on Chromium OS is built with Portage, so it follows the same general +workflow as any `cros_workon` package. The full package name is +`chromeos-base/crosvm`. + +See the [Chromium OS developer guide] for more on how to build and deploy with +Portage. + +[Chromium OS developer guide]: https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md + +### Building with Docker See the [README](docker/README.md) from the `docker` subdirectory to learn how to build crosvm in enviroments outside of the Chrome OS chroot. +### Building for Linux + +>**NOTE:** Building for Linux natively is new and not fully supported. + +First, [set up depot_tools] and use `repo` to sync down the crosvm source +tree. This is a subset of the entire Chromium OS manifest with just enough repos +to build crosvm. + +```sh +mkdir crosvm +cd crosvm +repo init -g crosvm -u https://chromium.googlesource.com/chromiumos/manifest.git --repo-url=https://chromium.googlesource.com/external/repo.git +repo sync +``` + +A basic crosvm build links against `libcap` and `libfdt`. On a Debian-based system, +you can install `libcap-dev` and `libfdt-dev`. + +Handy Debian one-liner for all build and runtime deps, particularly if you're +running Crostini: +```sh +sudo apt install build-essential libcap-dev libfdt-dev pkg-config python +``` + +Known issues: +* Seccomp policy files have hardcoded absolute paths. You can either fix up + the paths locally, or set up an awesome hacky symlink: `sudo mkdir + /usr/share/policy && sudo ln -s /path/to/crosvm/seccomp/x86_64 + /usr/share/policy/crosvm`. We'll eventually build the precompiled + policies [into the crosvm binary](http://crbug.com/1052126). +* Devices can't be jailed if `/var/empty` doesn't exist. `sudo mkdir -p + /var/empty` to work around this for now. + +And that's it! You should be able to `cargo build/run/test`. + +[set up depot_tools]: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up + ## Usage To see the usage information for your version of crosvm, run `crosvm` or `crosvm diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 83aa406..629f29b 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -25,6 +25,7 @@ io_jail = { path = "../io_jail" } kvm = { path = "../kvm" } kvm_sys = { path = "../kvm_sys" } libc = "*" +libcras = "*" linux_input_sys = { path = "../linux_input_sys" } msg_on_socket_derive = { path = "../msg_socket/msg_on_socket_derive" } msg_socket = { path = "../msg_socket" } diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 1ecfc9c..9ed8417 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -30,8 +30,8 @@ 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::{ - Ac97Dev, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, - VfioPciDevice, + Ac97Backend, Ac97Dev, Ac97Parameters, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, + PciInterruptPin, PciRoot, VfioPciDevice, }; pub use self::pic::Pic; pub use self::pit::{Pit, PitError}; diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs index 792df24..5f59165 100644 --- a/devices/src/pci/ac97.rs +++ b/devices/src/pci/ac97.rs @@ -2,9 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::default::Default; +use std::error; +use std::fmt::{self, Display}; use std::os::unix::io::RawFd; +use std::str::FromStr; -use audio_streams::shm_streams::ShmStreamSource; +use audio_streams::{ + shm_streams::{NullShmStreamSource, ShmStreamSource}, + StreamEffect, +}; +use libcras::CrasClient; use resources::{Alloc, MmioType, SystemAllocator}; use sys_util::{error, EventFd, GuestMemory}; @@ -25,6 +33,53 @@ const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415; /// Internally the `Ac97BusMaster` and `Ac97Mixer` structs are used to emulated the bus master and /// mixer registers respectively. `Ac97BusMaster` handles moving smaples between guest memory and /// the audio backend. +#[derive(Debug, Clone)] +pub enum Ac97Backend { + NULL, + CRAS, +} + +impl Default for Ac97Backend { + fn default() -> Self { + Ac97Backend::NULL + } +} + +/// Errors that are possible from a `Ac97`. +#[derive(Debug)] +pub enum Ac97Error { + InvalidBackend, +} + +impl error::Error for Ac97Error {} + +impl Display for Ac97Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Ac97Error::InvalidBackend => write!(f, "Must be cras or null"), + } + } +} + +impl FromStr for Ac97Backend { + type Err = Ac97Error; + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + match s { + "cras" => Ok(Ac97Backend::CRAS), + "null" => Ok(Ac97Backend::NULL), + _ => Err(Ac97Error::InvalidBackend), + } + } +} + +/// Holds the parameters for a AC97 device +#[derive(Default, Debug, Clone)] +pub struct Ac97Parameters { + pub backend: Ac97Backend, + pub capture: bool, + pub capture_effects: Vec<StreamEffect>, +} + pub struct Ac97Dev { config_regs: PciConfiguration, pci_bus_dev: Option<(u8, u8)>, @@ -61,6 +116,37 @@ 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))?); + if params.capture { + server.enable_cras_capture(); + } + + let mut cras_audio = Ac97Dev::new(mem, server); + cras_audio.set_capture_effects(params.capture_effects); + Ok(cras_audio) + } + + fn create_null_audio_device(mem: GuestMemory) -> Result<Ac97Dev> { + let server = Box::new(NullShmStreamSource::new()); + let null_audio = Ac97Dev::new(mem, server); + Ok(null_audio) + } + + /// Creates an 'Ac97Dev' with suitable audio server inside based on Ac97Parameters + pub fn try_new(mem: GuestMemory, param: Ac97Parameters) -> Result<Ac97Dev> { + match param.backend { + Ac97Backend::CRAS => Ac97Dev::create_cras_audio_device(param, mem), + Ac97Backend::NULL => Ac97Dev::create_null_audio_device(mem), + } + } + + /// Provides the effect needed in capture stream creation + pub fn set_capture_effects(&mut self, effect: Vec<StreamEffect>) { + self.bus_master.set_capture_effects(effect); + } + fn read_mixer(&mut self, offset: u64, data: &mut [u8]) { match data.len() { // The mixer is only accessed with 16-bit words. diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index cb28c5a..5f4ca75 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -165,6 +165,9 @@ pub struct Ac97BusMaster { po_info: AudioThreadInfo, pi_info: AudioThreadInfo, + // Audio effect + capture_effects: Vec<StreamEffect>, + // Audio server used to create playback or capture streams. audio_server: Box<dyn ShmStreamSource>, @@ -184,12 +187,18 @@ impl Ac97BusMaster { po_info: AudioThreadInfo::new(), pi_info: AudioThreadInfo::new(), + capture_effects: Vec::new(), audio_server, irq_resample_thread: None, } } + /// Provides the effect needed in capture stream creation. + pub fn set_capture_effects(&mut self, effects: Vec<StreamEffect>) { + self.capture_effects = effects; + } + /// Returns any file descriptors that need to be kept open when entering a jail. pub fn keep_fds(&self) -> Option<Vec<RawFd>> { let mut fds = self.audio_server.keep_fds(); @@ -518,7 +527,7 @@ impl Ac97BusMaster { SampleFormat::S16LE, DEVICE_SAMPLE_RATE, buffer_frames, - StreamEffect::NoEffect, + self.capture_effects.as_slice(), self.mem.as_ref(), starting_offsets, ) diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs index a9e8749..b9ebc19 100644 --- a/devices/src/pci/mod.rs +++ b/devices/src/pci/mod.rs @@ -14,8 +14,8 @@ mod pci_device; mod pci_root; mod vfio_pci; -pub use self::ac97::Ac97Dev; -pub use self::msix::{MsixCap, MsixConfig}; +pub use self::ac97::{Ac97Backend, Ac97Dev, Ac97Parameters}; +pub use self::msix::{MsixCap, MsixConfig, MsixStatus}; pub use self::pci_configuration::{ PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSerialBusSubClass, diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs index 6c79b4a..b69114b 100644 --- a/devices/src/pci/msix.rs +++ b/devices/src/pci/msix.rs @@ -89,6 +89,12 @@ impl Display for MsixError { type MsixResult<T> = std::result::Result<T, MsixError>; +pub enum MsixStatus { + Changed, + EntryChanged(usize), + NothingToDo, +} + impl MsixConfig { pub fn new(msix_vectors: u16, vm_socket: Arc<VmIrqRequestSocket>) -> Self { assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE); @@ -123,6 +129,18 @@ impl MsixConfig { self.masked } + /// Check whether the Function Mask bit in MSIX table Message Control + /// word in set or not. + /// If true, the vector is masked. + /// If false, the vector is unmasked. + pub fn table_masked(&self, index: usize) -> bool { + if index >= self.table_entries.len() { + true + } else { + self.table_entries[index].masked() + } + } + /// Check whether the MSI-X Enable bit in Message Control word in set or not. /// if 1, the function is permitted to use MSI-X to request service. pub fn enabled(&self) -> bool { @@ -147,7 +165,7 @@ impl MsixConfig { /// Write to the MSI-X Capability Structure. /// Only the top 2 bits in Message Control Word are writable. - pub fn write_msix_capability(&mut self, offset: u64, data: &[u8]) { + pub fn write_msix_capability(&mut self, offset: u64, data: &[u8]) -> MsixStatus { if offset == 2 && data.len() == 2 { let reg = u16::from_le_bytes([data[0], data[1]]); let old_masked = self.masked; @@ -173,6 +191,9 @@ impl MsixConfig { self.inject_msix_and_clear_pba(index); } } + return MsixStatus::Changed; + } else if !old_masked && self.masked { + return MsixStatus::Changed; } } else { error!( @@ -180,6 +201,7 @@ impl MsixConfig { offset ); } + MsixStatus::NothingToDo } fn add_msi_route(&self, index: u16, gsi: u32) -> MsixResult<()> { @@ -304,7 +326,7 @@ impl MsixConfig { /// Vector Control: only bit 0 (Mask Bit) is not reserved: when this bit /// is set, the function is prohibited from sending a message using /// this MSI-X Table entry. - pub fn write_msix_table(&mut self, offset: u64, data: &[u8]) { + pub fn write_msix_table(&mut self, offset: u64, data: &[u8]) -> MsixStatus { let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize; let modulo_offset = offset % MSIX_TABLE_ENTRIES_MODULO; @@ -360,13 +382,17 @@ impl MsixConfig { // device. // Check if bit has been flipped - if !self.masked() - && old_entry.masked() - && !self.table_entries[index].masked() - && self.get_pba_bit(index as u16) == 1 - { - self.inject_msix_and_clear_pba(index); + if !self.masked() { + if old_entry.masked() && !self.table_entries[index].masked() { + if self.get_pba_bit(index as u16) == 1 { + self.inject_msix_and_clear_pba(index); + } + return MsixStatus::EntryChanged(index); + } else if !old_entry.masked() && self.table_entries[index].masked() { + return MsixStatus::EntryChanged(index); + } } + MsixStatus::NothingToDo } /// Read PBA Entries diff --git a/devices/src/pci/pci_device.rs b/devices/src/pci/pci_device.rs index 4c62e05..a1a3cca 100644 --- a/devices/src/pci/pci_device.rs +++ b/devices/src/pci/pci_device.rs @@ -22,6 +22,8 @@ pub enum Error { IoAllocationFailed(u64, SystemAllocatorFaliure), /// Registering an IO BAR failed. IoRegistrationFailed(u64, pci_configuration::Error), + /// Create cras client failed. + CreateCrasClientFailed(libcras::Error), } pub type Result<T> = std::result::Result<T, Error>; @@ -31,6 +33,7 @@ impl Display for Error { match self { CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e), + CreateCrasClientFailed(e) => write!(f, "failed to create CRAS Client: {}", e), IoAllocationFailed(size, e) => write!( f, "failed to allocate space for an IO BAR, size={}: {}", diff --git a/devices/src/pit.rs b/devices/src/pit.rs index 86ad4c5..68ed6e0 100644 --- a/devices/src/pit.rs +++ b/devices/src/pit.rs @@ -691,7 +691,7 @@ impl PitCounter { Some(t) => { let dur = self.clock.lock().now().duration_since(t); let dur_ns: u64 = dur.as_secs() * NANOS_PER_SEC + u64::from(dur.subsec_nanos()); - (dur_ns * FREQUENCY_HZ / NANOS_PER_SEC) + dur_ns * FREQUENCY_HZ / NANOS_PER_SEC } } } diff --git a/devices/src/serial.rs b/devices/src/serial.rs index 98f39dc..6629d7e 100644 --- a/devices/src/serial.rs +++ b/devices/src/serial.rs @@ -149,37 +149,35 @@ impl SerialParameters { ) -> 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()); - 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()) - } - } - Ok(Serial::new_in_out( - evt_fd, - Box::new(StdinWrapper), - Box::new(stdout()), - )) - } else { - Ok(Serial::new_out(evt_fd, Box::new(stdout()))) - } + Ok(Serial::new(evt_fd, input, Some(Box::new(stdout())))) } - SerialType::Sink => Ok(Serial::new_sink(evt_fd)), + SerialType::Sink => Ok(Serial::new(evt_fd, input, None)), SerialType::Syslog => { syslog::push_fds(keep_fds); - Ok(Serial::new_out( + Ok(Serial::new( evt_fd, - Box::new(syslog::Syslogger::new( + input, + Some(Box::new(syslog::Syslogger::new( syslog::Priority::Info, syslog::Facility::Daemon, - )), + ))), )) } SerialType::File => match &self.path { @@ -187,7 +185,7 @@ impl SerialParameters { Some(path) => { let file = File::create(path.as_path()).map_err(Error::FileError)?; keep_fds.push(file.as_raw_fd()); - Ok(Serial::new_out(evt_fd, Box::new(file))) + Ok(Serial::new(evt_fd, input, Some(Box::new(file)))) } }, SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)), diff --git a/devices/src/usb/host_backend/host_device.rs b/devices/src/usb/host_backend/host_device.rs index 3b85ea2..196fa50 100644 --- a/devices/src/usb/host_backend/host_device.rs +++ b/devices/src/usb/host_backend/host_device.rs @@ -305,22 +305,31 @@ impl HostDevice { config ); self.release_interfaces(); - let cur_config = self - .device - .lock() - .get_active_configuration() - .map_err(Error::GetActiveConfig)?; - usb_debug!("current config is: {}", cur_config); - if config != cur_config { - self.device - .lock() - .set_active_configuration(config) - .map_err(Error::SetActiveConfig)?; + if self.device.lock().get_num_configurations() > 1 { + let cur_config = match self.device.lock().get_active_configuration() { + Ok(c) => Some(c), + Err(e) => { + // The device may be in the default state, in which case + // GET_CONFIGURATION may fail. Assume the device needs to be + // reconfigured. + usb_debug!("Failed to get active configuration: {}", e); + error!("Failed to get active configuration: {}", e); + None + } + }; + if Some(config) != cur_config { + self.device + .lock() + .set_active_configuration(config) + .map_err(Error::SetActiveConfig)?; + } + } else { + usb_debug!("Only one configuration - not calling set_active_configuration"); } let config_descriptor = self .device .lock() - .get_active_config_descriptor() + .get_config_descriptor(config) .map_err(Error::GetActiveConfig)?; self.claim_interfaces(&config_descriptor); self.create_endpoints(&config_descriptor)?; @@ -337,10 +346,15 @@ impl HostDevice { .set_interface_alt_setting(interface, alt_setting) .map_err(Error::SetInterfaceAltSetting)?; self.alt_settings.insert(interface, alt_setting); + let config = self + .device + .lock() + .get_active_configuration() + .map_err(Error::GetActiveConfig)?; let config_descriptor = self .device .lock() - .get_active_config_descriptor() + .get_config_descriptor(config) .map_err(Error::GetActiveConfig)?; self.create_endpoints(&config_descriptor)?; Ok(TransferStatus::Completed) diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs new file mode 100644 index 0000000..38f5bf1 --- /dev/null +++ b/devices/src/virtio/console.rs @@ -0,0 +1,449 @@ +// 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::{self, Read}; +use std::os::unix::io::RawFd; +use std::sync::mpsc::{channel, Receiver, TryRecvError}; +use std::thread; + +use data_model::{DataInit, Le16, Le32}; +use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken}; + +use super::{ + copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_CONSOLE, VIRTIO_F_VERSION_1, +}; + +const QUEUE_SIZE: u16 = 256; + +// For now, just implement port 0 (receiveq and transmitq). +// If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed. +const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE]; + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C)] +struct virtio_console_config { + cols: Le16, + rows: Le16, + max_nr_ports: Le32, + emerg_wr: Le32, +} + +// Safe because it only has data and has no implicit padding. +unsafe impl DataInit for virtio_console_config {} + +struct Worker { + mem: GuestMemory, + interrupt: Interrupt, + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, +} + +fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> { + output.write_all(&data)?; + output.flush() +} + +impl Worker { + fn process_transmit_request( + mut reader: Reader, + output: &mut Box<dyn io::Write>, + ) -> io::Result<u32> { + let len = reader.available_bytes(); + let mut data = vec![0u8; len]; + reader.read_exact(&mut data)?; + write_output(output, &data)?; + Ok(0) + } + + fn process_transmit_queue( + &mut self, + transmit_queue: &mut Queue, + output: &mut Box<dyn io::Write>, + ) { + let mut needs_interrupt = false; + while let Some(avail_desc) = transmit_queue.pop(&self.mem) { + let desc_index = avail_desc.index; + + let reader = match Reader::new(&self.mem, avail_desc) { + Ok(r) => r, + Err(e) => { + error!("console: failed to create reader: {}", e); + transmit_queue.add_used(&self.mem, desc_index, 0); + needs_interrupt = true; + continue; + } + }; + + let len = match Self::process_transmit_request(reader, output) { + Ok(written) => written, + Err(e) => { + error!("console: process_transmit_request failed: {}", e); + 0 + } + }; + + transmit_queue.add_used(&self.mem, desc_index, len); + needs_interrupt = true; + } + + if needs_interrupt { + self.interrupt.signal_used_queue(transmit_queue.vector); + } + } + + // Start a thread that reads self.input and sends the input back via the returned channel. + // + // `in_avail_evt` will be triggered by the thread when new input is available. + fn spawn_input_thread(&mut self, in_avail_evt: &EventFd) -> Option<Receiver<u8>> { + let mut rx = match self.input.take() { + Some(input) => input, + None => return None, + }; + + let (send_channel, recv_channel) = channel(); + + let thread_in_avail_evt = match in_avail_evt.try_clone() { + Ok(evt) => evt, + Err(e) => { + error!("failed to clone in_avail_evt: {}", e); + return None; + } + }; + + // The input thread runs in detached mode and will exit when channel is disconnected because + // the console device has been dropped. + let res = thread::Builder::new() + .name(format!("console_input")) + .spawn(move || { + let mut rx_buf = [0u8; 1]; + loop { + match rx.read(&mut rx_buf) { + Ok(0) => break, // Assume the stream of input has ended. + Ok(_) => { + if send_channel.send(rx_buf[0]).is_err() { + // The receiver has disconnected. + break; + } + thread_in_avail_evt.write(1).unwrap(); + } + Err(e) => { + // Being interrupted is not an error, but everything else is. + if e.kind() != io::ErrorKind::Interrupted { + error!( + "failed to read for bytes to queue into console device: {}", + e + ); + break; + } + } + } + } + }); + if let Err(e) = res { + error!("failed to spawn input thread: {}", e); + return None; + } + Some(recv_channel) + } + + // Check for input from `in_channel_opt` and transfer it to the receive queue, if any. + fn handle_input( + &mut self, + in_channel_opt: &mut Option<Receiver<u8>>, + receive_queue: &mut Queue, + ) { + let in_channel = match in_channel_opt.as_ref() { + Some(v) => v, + None => return, + }; + + while let Some(desc) = receive_queue.peek(&self.mem) { + let desc_index = desc.index; + let mut writer = match Writer::new(&self.mem, desc) { + Ok(w) => w, + Err(e) => { + error!("console: failed to create Writer: {}", e); + break; + } + }; + + let mut disconnected = false; + while writer.available_bytes() > 0 { + match in_channel.try_recv() { + Ok(byte) => { + writer.write_obj(byte).unwrap(); + } + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => { + disconnected = true; + break; + } + } + } + + let bytes_written = writer.bytes_written() as u32; + + if bytes_written > 0 { + receive_queue.pop_peeked(&self.mem); + receive_queue.add_used(&self.mem, desc_index, bytes_written); + self.interrupt.signal_used_queue(receive_queue.vector); + } + + if disconnected { + // Set in_channel to None so that future handle_input calls exit early. + in_channel_opt.take(); + return; + } + + if bytes_written == 0 { + break; + } + } + } + + fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<EventFd>, kill_evt: EventFd) { + #[derive(PollToken)] + enum Token { + ReceiveQueueAvailable, + TransmitQueueAvailable, + InputAvailable, + InterruptResample, + Kill, + } + + // Device -> driver + let (mut receive_queue, receive_evt) = (queues.remove(0), queue_evts.remove(0)); + + // Driver -> device + let (mut transmit_queue, transmit_evt) = (queues.remove(0), queue_evts.remove(0)); + + let in_avail_evt = match EventFd::new() { + Ok(evt) => evt, + Err(e) => { + error!("failed creating EventFd: {}", e); + return; + } + }; + + // Spawn a separate thread to poll self.input. + // A thread is used because io::Read only provides a blocking interface, and there is no + // generic way to add an io::Read instance to a poll context (it may not be backed by a file + // descriptor). Moving the blocking read call to a separate thread and sending data back to + // the main worker thread with an event for notification bridges this gap. + let mut in_channel = self.spawn_input_thread(&in_avail_evt); + + let poll_ctx: PollContext<Token> = match PollContext::build_with(&[ + (&transmit_evt, Token::TransmitQueueAvailable), + (&receive_evt, Token::ReceiveQueueAvailable), + (&in_avail_evt, Token::InputAvailable), + (self.interrupt.get_resample_evt(), Token::InterruptResample), + (&kill_evt, Token::Kill), + ]) { + Ok(pc) => pc, + Err(e) => { + error!("failed creating PollContext: {}", e); + return; + } + }; + + let mut output: Box<dyn io::Write> = match self.output.take() { + Some(o) => o, + None => Box::new(io::sink()), + }; + + 'poll: loop { + let events = match poll_ctx.wait() { + Ok(v) => v, + Err(e) => { + error!("failed polling for events: {}", e); + break; + } + }; + + for event in events.iter_readable() { + match event.token() { + Token::TransmitQueueAvailable => { + if let Err(e) = transmit_evt.read() { + error!("failed reading transmit queue EventFd: {}", e); + break 'poll; + } + self.process_transmit_queue(&mut transmit_queue, &mut output); + } + Token::ReceiveQueueAvailable => { + if let Err(e) = receive_evt.read() { + error!("failed reading receive queue EventFd: {}", e); + break 'poll; + } + self.handle_input(&mut in_channel, &mut receive_queue); + } + Token::InputAvailable => { + if let Err(e) = in_avail_evt.read() { + error!("failed reading in_avail_evt: {}", e); + break 'poll; + } + self.handle_input(&mut in_channel, &mut receive_queue); + } + Token::InterruptResample => { + self.interrupt.interrupt_resample(); + } + Token::Kill => break 'poll, + } + } + } + } +} + +/// Virtio console device. +pub struct Console { + kill_evt: Option<EventFd>, + worker_thread: Option<thread::JoinHandle<Worker>>, + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, + keep_fds: Vec<RawFd>, +} + +impl Console { + fn new( + input: Option<Box<dyn io::Read + Send>>, + output: Option<Box<dyn io::Write + Send>>, + keep_fds: Vec<RawFd>, + ) -> Console { + Console { + kill_evt: None, + worker_thread: None, + input, + output, + 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 { + fn drop(&mut self) { + if let Some(kill_evt) = self.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + if let Some(worker_thread) = self.worker_thread.take() { + let _ = worker_thread.join(); + } + } +} + +impl VirtioDevice for Console { + fn keep_fds(&self) -> Vec<RawFd> { + self.keep_fds.clone() + } + + fn features(&self) -> u64 { + 1 << VIRTIO_F_VERSION_1 + } + + fn device_type(&self) -> u32 { + TYPE_CONSOLE + } + + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + let config = virtio_console_config { + max_nr_ports: 1.into(), + ..Default::default() + }; + copy_config(data, 0, config.as_slice(), offset); + } + + fn activate( + &mut self, + mem: GuestMemory, + interrupt: Interrupt, + queues: Vec<Queue>, + queue_evts: Vec<EventFd>, + ) { + if queues.len() < 2 || queue_evts.len() < 2 { + return; + } + + let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed creating kill EventFd pair: {}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let input = self.input.take(); + let output = self.output.take(); + + let worker_result = thread::Builder::new() + .name("virtio_console".to_string()) + .spawn(move || { + let mut worker = Worker { + mem, + interrupt, + input, + output, + }; + worker.run(queues, queue_evts, kill_evt); + worker + }); + + match worker_result { + Err(e) => { + error!("failed to spawn virtio_console worker: {}", e); + return; + } + Ok(join_handle) => { + self.worker_thread = Some(join_handle); + } + } + } + + fn reset(&mut self) -> bool { + if let Some(kill_evt) = self.kill_evt.take() { + if kill_evt.write(1).is_err() { + error!("{}: failed to notify the kill event", self.debug_label()); + return false; + } + } + + if let Some(worker_thread) = self.worker_thread.take() { + match worker_thread.join() { + Err(_) => { + error!("{}: failed to get back resources", self.debug_label()); + return false; + } + Ok(worker) => { + self.input = worker.input; + self.output = worker.output; + return true; + } + } + } + false + } +} diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs index 723b0c6..990f147 100644 --- a/devices/src/virtio/descriptor_utils.rs +++ b/devices/src/virtio/descriptor_utils.rs @@ -216,8 +216,8 @@ pub struct Reader<'a> { buffer: DescriptorChainConsumer<'a>, } -// An iterator over `DataInit` objects on readable descriptors in the descriptor chain. -struct ReaderIterator<'a, T: DataInit> { +/// An iterator over `DataInit` objects on readable descriptors in the descriptor chain. +pub struct ReaderIterator<'a, T: DataInit> { reader: &'a mut Reader<'a>, phantom: PhantomData<T>, } @@ -283,10 +283,17 @@ impl<'a> Reader<'a> { /// them as a collection. Returns an error if the size of the remaining data is indivisible by /// the size of an object of type `T`. pub fn collect<C: FromIterator<io::Result<T>>, T: DataInit>(&'a mut self) -> C { - C::from_iter(ReaderIterator { + C::from_iter(self.iter()) + } + + /// Creates an iterator for sequentially reading `DataInit` objects from the `Reader`. Unlike + /// `collect`, this doesn't consume all the remaining data in the `Reader` and doesn't require + /// the objects to be stored in a separate collection. + pub fn iter<T: DataInit>(&'a mut self) -> ReaderIterator<'a, T> { + ReaderIterator { reader: self, phantom: PhantomData, - }) + } } /// Reads data from the descriptor chain buffer into a file descriptor. diff --git a/devices/src/virtio/fs/filesystem.rs b/devices/src/virtio/fs/filesystem.rs index 232ff99..eb9726c 100644 --- a/devices/src/virtio/fs/filesystem.rs +++ b/devices/src/virtio/fs/filesystem.rs @@ -1139,4 +1139,34 @@ pub trait FileSystem { fn lseek(&self) -> io::Result<()> { Err(io::Error::from_raw_os_error(libc::ENOSYS)) } + + /// Copy a range of data from one file to another + /// + /// Performs an optimized copy between two file descriptors without the additional cost of + /// transferring data through the kernel module to user space (glibc) and then back into + /// the file system again. + /// + /// In case this method is not implemented, glibc falls back to reading data from the source and + /// writing to the destination. + /// + /// If this method fails with an `ENOSYS` error, then the kernel will treat that as a permanent + /// failure. The kernel will return `EOPNOTSUPP` for all future calls to `copy_file_range` + /// without forwarding them to the file system. + /// + /// All values accepted by the `copy_file_range(2)` system call are valid values for `flags` and + /// must be handled by the file system. + fn copy_file_range( + &self, + ctx: Context, + inode_src: Self::Inode, + handle_src: Self::Handle, + offset_src: u64, + inode_dst: Self::Inode, + handle_dst: Self::Handle, + offset_dst: u64, + length: u64, + flags: u64, + ) -> io::Result<usize> { + Err(io::Error::from_raw_os_error(libc::ENOSYS)) + } } diff --git a/devices/src/virtio/fs/fuse.rs b/devices/src/virtio/fs/fuse.rs index 612202b..5c531cf 100644 --- a/devices/src/virtio/fs/fuse.rs +++ b/devices/src/virtio/fs/fuse.rs @@ -12,8 +12,11 @@ use libc; /// Version number of this interface. pub const KERNEL_VERSION: u32 = 7; +/// Oldest supported minor version of the fuse interface. +pub const OLDEST_SUPPORTED_KERNEL_MINOR_VERSION: u32 = 27; + /// Minor version number of this interface. -pub const KERNEL_MINOR_VERSION: u32 = 27; +pub const KERNEL_MINOR_VERSION: u32 = 31; /// The ID of the inode corresponding to the root directory of the file system. pub const ROOT_ID: u64 = 1; @@ -56,6 +59,12 @@ const FOPEN_KEEP_CACHE: u32 = 2; /// The file is not seekable. const FOPEN_NONSEEKABLE: u32 = 4; +/// Allow caching the directory entries. +const FOPEN_CACHE_DIR: u32 = 8; + +/// This file is stream-like (i.e., no file position). +const FOPEN_STREAM: u32 = 16; + bitflags! { /// Options controlling the behavior of files opened by the server in response /// to an open or create request. @@ -63,6 +72,8 @@ bitflags! { const DIRECT_IO = FOPEN_DIRECT_IO; const KEEP_CACHE = FOPEN_KEEP_CACHE; const NONSEEKABLE = FOPEN_NONSEEKABLE; + const CACHE_DIR = FOPEN_CACHE_DIR; + const STREAM = FOPEN_STREAM; } } @@ -131,6 +142,24 @@ const HANDLE_KILLPRIV: u32 = 524288; /// FileSystem supports posix acls. const POSIX_ACL: u32 = 1048576; +/// Reading the device after an abort returns `ECONNABORTED`. +const ABORT_ERROR: u32 = 2097152; + +/// The reply to the `init` message contains the max number of request pages. +const MAX_PAGES: u32 = 4194304; + +/// Cache `readlink` responses. +const CACHE_SYMLINKS: u32 = 8388608; + +/// Kernel supports zero-message opens for directories. +const NO_OPENDIR_SUPPORT: u32 = 16777216; + +/// Kernel supports explicit cache invalidation. +const EXPLICIT_INVAL_DATA: u32 = 33554432; + +/// The `map_alignment` field of the `InitOut` struct is valid. +const MAP_ALIGNMENT: u32 = 67108864; + bitflags! { /// A bitfield passed in as a parameter to and returned from the `init` method of the /// `FileSystem` trait. @@ -297,6 +326,37 @@ bitflags! { /// /// This feature is disabled by default. const POSIX_ACL = POSIX_ACL; + + /// Indicates that the kernel may cache responses to `readlink` calls. + const CACHE_SYMLINKS = CACHE_SYMLINKS; + + /// Indicates support for zero-message opens for directories. If this flag is set in the + /// `capable` parameter of the `init` trait method, then the file system may return `ENOSYS` + /// from the opendir() handler to indicate success. Further attempts to open directories + /// will be handled in the kernel. (If this flag is not set, returning ENOSYS will be + /// treated as an error and signaled to the caller). + /// + /// Setting (or not setting) the field in the `FsOptions` returned from the `init` method + /// has no effect. + const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT; + + /// Indicates support for invalidating cached pages only on explicit request. + /// + /// If this flag is set in the `capable` parameter of the `init` trait method, then the FUSE + /// kernel module supports invalidating cached pages only on explicit request by the + /// filesystem. + /// + /// By setting this flag in the return value of the `init` trait method, the filesystem is + /// responsible for invalidating cached pages through explicit requests to the kernel. + /// + /// Note that setting this flag does not prevent the cached pages from being flushed by OS + /// itself and/or through user actions. + /// + /// Note that if both EXPLICIT_INVAL_DATA and AUTO_INVAL_DATA are set in the `capable` + /// parameter of the `init` trait method then AUTO_INVAL_DATA takes precedence. + /// + /// This feature is disabled by default. + const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA; } } @@ -318,6 +378,9 @@ pub const WRITE_CACHE: u32 = 1; /// `lock_owner` field is valid. pub const WRITE_LOCKOWNER: u32 = 2; +/// Kill the suid and sgid bits. +pub const WRITE_KILL_PRIV: u32 = 3; + // Read flags. pub const READ_LOCKOWNER: u32 = 2; @@ -338,6 +401,9 @@ const IOCTL_32BIT: u32 = 8; /// Is a directory const IOCTL_DIR: u32 = 16; +/// 32-bit compat ioctl on 64-bit machine with 64-bit time_t. +const IOCTL_COMPAT_X32: u32 = 32; + /// Maximum of in_iovecs + out_iovecs pub const IOCTL_MAX_IOV: usize = 256; @@ -357,6 +423,9 @@ bitflags! { /// Is a directory const DIR = IOCTL_DIR; + + /// 32-bit compat ioctl on 64-bit machine with 64-bit time_t. + const COMPAT_X32 = IOCTL_COMPAT_X32; } } @@ -511,6 +580,9 @@ pub enum Opcode { Readdirplus = 44, Rename2 = 45, Lseek = 46, + CopyFileRange = 47, + SetUpMapping = 48, + RemoveMapping = 49, } #[repr(u32)] @@ -830,7 +902,9 @@ pub struct InitOut { pub congestion_threshold: u16, pub max_write: u32, pub time_gran: u32, - pub unused: [u32; 9], + pub max_pages: u16, + pub map_alignment: u16, + pub unused: [u32; 8], } unsafe impl DataInit for InitOut {} @@ -1049,3 +1123,16 @@ pub struct LseekOut { pub offset: u64, } unsafe impl DataInit for LseekOut {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct CopyFileRangeIn { + pub fh_src: u64, + pub off_src: u64, + pub nodeid_dst: u64, + pub fh_dst: u64, + pub off_dst: u64, + pub len: u64, + pub flags: u64, +} +unsafe impl DataInit for CopyFileRangeIn {} diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs index bdc1c6f..419d466 100644 --- a/devices/src/virtio/fs/passthrough.rs +++ b/devices/src/virtio/fs/passthrough.rs @@ -395,7 +395,7 @@ impl PassthroughFs { libc::openat( self.proc.as_raw_fd(), pathname.as_ptr(), - (flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW), + (flags | libc::O_CLOEXEC) & !(libc::O_NOFOLLOW | libc::O_DIRECT), ) }; if fd < 0 { @@ -592,7 +592,13 @@ impl PassthroughFs { OpenOptions::DIRECT_IO, flags & (libc::O_DIRECTORY as u32) == 0, ), - CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE, + CachePolicy::Always => { + opts |= if flags & (libc::O_DIRECTORY as u32) == 0 { + OpenOptions::KEEP_CACHE + } else { + OpenOptions::CACHE_DIR + } + } _ => {} }; @@ -965,7 +971,8 @@ impl FileSystem for PassthroughFs { libc::openat( data.file.as_raw_fd(), name.as_ptr(), - flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW, + (flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW) + & !libc::O_DIRECT, mode & !(umask & 0o777), ) }; @@ -1689,4 +1696,58 @@ impl FileSystem for PassthroughFs { Err(io::Error::from_raw_os_error(libc::ENOTTY)) } } + + fn copy_file_range( + &self, + ctx: Context, + inode_src: Inode, + handle_src: Handle, + offset_src: u64, + inode_dst: Inode, + handle_dst: Handle, + offset_dst: u64, + length: u64, + flags: u64, + ) -> io::Result<usize> { + // We need to change credentials during a write so that the kernel will remove setuid or + // setgid bits from the file if it was written to by someone other than the owner. + let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; + let src_data = self + .handles + .read() + .unwrap() + .get(&handle_src) + .filter(|hd| hd.inode == inode_src) + .map(Arc::clone) + .ok_or_else(ebadf)?; + let dst_data = self + .handles + .read() + .unwrap() + .get(&handle_dst) + .filter(|hd| hd.inode == inode_dst) + .map(Arc::clone) + .ok_or_else(ebadf)?; + + let src = src_data.file.lock().as_raw_fd(); + let dst = dst_data.file.lock().as_raw_fd(); + + let res = unsafe { + libc::syscall( + libc::SYS_copy_file_range, + src, + &offset_src, + dst, + offset_dst, + length, + flags, + ) + }; + + if res >= 0 { + Ok(res as usize) + } else { + Err(io::Error::last_os_error()) + } + } } diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs index 32b5008..c9025f2 100644 --- a/devices/src/virtio/fs/server.rs +++ b/devices/src/virtio/fs/server.rs @@ -118,7 +118,8 @@ impl<F: FileSystem + Sync> Server<F> { Some(Opcode::Readdirplus) => self.readdirplus(in_header, r, w), Some(Opcode::Rename2) => self.rename2(in_header, r, w), Some(Opcode::Lseek) => self.lseek(in_header, r, w), - None => reply_error( + Some(Opcode::CopyFileRange) => self.copy_file_range(in_header, r, w), + Some(Opcode::SetUpMapping) | Some(Opcode::RemoveMapping) | None => reply_error( io::Error::from_raw_os_error(libc::ENOSYS), in_header.unique, w, @@ -809,7 +810,7 @@ impl<F: FileSystem + Sync> Server<F> { return reply_ok(Some(out), None, in_header.unique, w); } - if minor < KERNEL_MINOR_VERSION { + if minor < OLDEST_SUPPORTED_KERNEL_MINOR_VERSION { error!( "Unsupported fuse protocol minor version: {}.{}", major, minor @@ -1208,6 +1209,40 @@ impl<F: FileSystem + Sync> Server<F> { Ok(0) } } + + fn copy_file_range(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> { + let CopyFileRangeIn { + fh_src, + off_src, + nodeid_dst, + fh_dst, + off_dst, + len, + flags, + } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self.fs.copy_file_range( + Context::from(in_header), + in_header.nodeid.into(), + fh_src.into(), + off_src, + nodeid_dst.into(), + fh_dst.into(), + off_dst, + len, + flags, + ) { + Ok(count) => { + let out = WriteOut { + size: count as u32, + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } } fn retry_ioctl( diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 8cc211f..93d004f 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -277,30 +277,24 @@ trait Backend { GpuResponse::ErrUnspec } - fn allocation_metadata( - &mut self, - _request_id: u32, - _request: Vec<u8>, - mut _response: Vec<u8>, - ) -> GpuResponse { - GpuResponse::ErrUnspec - } - fn resource_create_v2( &mut self, _resource_id: u32, - _guest_memory_type: u32, - _guest_caching_type: u32, + _ctx_id: u32, + _flags: u32, _size: u64, - _pci_addr: u64, - _mem: &GuestMemory, + _memory_id: u64, _vecs: Vec<(GuestAddress, usize)>, - _args: Vec<u8>, + _mem: &GuestMemory, ) -> GpuResponse { GpuResponse::ErrUnspec } - fn resource_v2_unref(&mut self, _resource_id: u32) -> GpuResponse { + fn resource_map(&mut self, _resource_id: u32, _pci_addr: u64) -> GpuResponse { + GpuResponse::ErrUnspec + } + + fn resource_unmap(&mut self, _resource_id: u32) -> GpuResponse { GpuResponse::ErrUnspec } } @@ -474,19 +468,19 @@ impl Frontend { let available_bytes = reader.available_bytes(); if available_bytes != 0 { let entry_count = info.nr_entries.to_native() as usize; - let mut iovecs = Vec::with_capacity(entry_count); + let mut vecs = Vec::with_capacity(entry_count); for _ in 0..entry_count { match reader.read_obj::<virtio_gpu_mem_entry>() { Ok(entry) => { let addr = GuestAddress(entry.addr.to_native()); let len = entry.length.to_native() as usize; - iovecs.push((addr, len)) + vecs.push((addr, len)) } Err(_) => return GpuResponse::ErrUnspec, } } self.backend - .attach_backing(info.resource_id.to_native(), mem, iovecs) + .attach_backing(info.resource_id.to_native(), mem, vecs) } else { error!("missing data for command {:?}", cmd); GpuResponse::ErrUnspec @@ -610,78 +604,49 @@ impl Frontend { GpuResponse::OkNoData } } - GpuCommand::AllocationMetadata(info) => { - if reader.available_bytes() != 0 { - let id = info.request_id.to_native(); - let request_size = info.request_size.to_native(); - let response_size = info.response_size.to_native(); - if request_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - || response_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - { - return GpuResponse::ErrUnspec; - } - - let mut request_buf = vec![0; request_size as usize]; - let response_buf = vec![0; response_size as usize]; - if reader.read_exact(&mut request_buf[..]).is_ok() { - self.backend - .allocation_metadata(id, request_buf, response_buf) - } else { - GpuResponse::ErrInvalidParameter - } - } else { - GpuResponse::ErrUnspec - } - } GpuCommand::ResourceCreateV2(info) => { - if reader.available_bytes() != 0 { - let resource_id = info.resource_id.to_native(); - let guest_memory_type = info.guest_memory_type.to_native(); - let size = info.size.to_native(); - let guest_caching_type = info.guest_caching_type.to_native(); - let pci_addr = info.pci_addr.to_native(); - let entry_count = info.nr_entries.to_native(); - let args_size = info.args_size.to_native(); - if args_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE - || entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES - { - return GpuResponse::ErrUnspec; - } - - let mut iovecs = Vec::with_capacity(entry_count as usize); - let mut args = vec![0; args_size as usize]; + let resource_id = info.resource_id.to_native(); + let ctx_id = info.hdr.ctx_id.to_native(); + let flags = info.flags.to_native(); + let size = info.size.to_native(); + let memory_id = info.memory_id.to_native(); + let entry_count = info.nr_entries.to_native(); + if entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES + || (reader.available_bytes() == 0 && entry_count > 0) + { + return GpuResponse::ErrUnspec; + } - for _ in 0..entry_count { - match reader.read_obj::<virtio_gpu_mem_entry>() { - Ok(entry) => { - let addr = GuestAddress(entry.addr.to_native()); - let len = entry.length.to_native() as usize; - iovecs.push((addr, len)) - } - Err(_) => return GpuResponse::ErrUnspec, + let mut vecs = Vec::with_capacity(entry_count as usize); + for _ in 0..entry_count { + match reader.read_obj::<virtio_gpu_mem_entry>() { + Ok(entry) => { + let addr = GuestAddress(entry.addr.to_native()); + let len = entry.length.to_native() as usize; + vecs.push((addr, len)) } + Err(_) => return GpuResponse::ErrUnspec, } - - match reader.read_exact(&mut args[..]) { - Ok(_) => self.backend.resource_create_v2( - resource_id, - guest_memory_type, - guest_caching_type, - size, - pci_addr, - mem, - iovecs, - args, - ), - Err(_) => GpuResponse::ErrUnspec, - } - } else { - GpuResponse::ErrUnspec } + + self.backend.resource_create_v2( + resource_id, + ctx_id, + flags, + size, + memory_id, + vecs, + mem, + ) + } + GpuCommand::ResourceMap(info) => { + let resource_id = info.resource_id.to_native(); + let offset = info.offset.to_native(); + self.backend.resource_map(resource_id, offset) } - GpuCommand::ResourceV2Unref(info) => { + GpuCommand::ResourceUnmap(info) => { let resource_id = info.resource_id.to_native(); - self.backend.resource_v2_unref(resource_id) + self.backend.resource_unmap(resource_id) } } } diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs index b4610b2..c98e289 100644 --- a/devices/src/virtio/gpu/protocol.rs +++ b/devices/src/virtio/gpu/protocol.rs @@ -19,9 +19,10 @@ use data_model::{DataInit, Le32, Le64}; pub const VIRTIO_GPU_F_VIRGL: u32 = 0; pub const VIRTIO_GPU_F_EDID: u32 = 1; /* The following capabilities are not upstreamed. */ -pub const VIRTIO_GPU_F_MEMORY: u32 = 2; -pub const VIRTIO_GPU_F_SHARED_GUEST: u32 = 3; -pub const VIRTIO_GPU_F_HOST_COHERENT: u32 = 4; +pub const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2; +pub const VIRTIO_GPU_F_RESOURCE_V2: u32 = 3; +pub const VIRTIO_GPU_F_HOST_VISIBLE: u32 = 4; +pub const VIRTIO_GPU_F_VULKAN: u32 = 5; pub const VIRTIO_GPU_UNDEFINED: u32 = 0x0; @@ -49,8 +50,8 @@ pub const VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: u32 = 0x206; pub const VIRTIO_GPU_CMD_SUBMIT_3D: u32 = 0x207; /* The following hypercalls are not upstreamed. */ pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2: u32 = 0x208; -pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF: u32 = 0x209; -pub const VIRTIO_GPU_CMD_ALLOCATION_METADATA: u32 = 0x20a; +pub const VIRTIO_GPU_CMD_RESOURCE_MAP: u32 = 0x209; +pub const VIRTIO_GPU_CMD_RESOURCE_UNMAP: u32 = 0x20a; /* cursor commands */ pub const VIRTIO_GPU_CMD_UPDATE_CURSOR: u32 = 0x300; @@ -63,7 +64,6 @@ pub const VIRTIO_GPU_RESP_OK_CAPSET_INFO: u32 = 0x1102; pub const VIRTIO_GPU_RESP_OK_CAPSET: u32 = 0x1103; pub const VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO: u32 = 0x1104; pub const VIRTIO_GPU_RESP_OK_EDID: u32 = 0x1105; -pub const VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA: u32 = 0x1106; /* error responses */ pub const VIRTIO_GPU_RESP_ERR_UNSPEC: u32 = 0x1200; @@ -73,20 +73,22 @@ pub const VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID: u32 = 0x1203; pub const VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID: u32 = 0x1204; pub const VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER: u32 = 0x1205; -/* guest memory types (not upstreamed) */ -pub const VIRTIO_GPU_MEMORY_UNDEFINED: u32 = 0; -pub const VIRTIO_GPU_MEMORY_TRANSFER: u32 = 1; -pub const VIRTIO_GPU_MEMORY_SHARED_GUEST: u32 = 2; -pub const VIRTIO_GPU_MEMORY_HOST_COHERENT: u32 = 3; +/* Resource flags (not upstreamed) */ +pub const VIRTIO_GPU_RESOURCE_GUEST_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_GUEST_MASK: u32 = 0x000f; +pub const VIRTIO_GPU_RESOURCE_GUEST_SYSTEM: u32 = 0x0001; -/* guest caching types (not upstreamed) */ -pub const VIRTIO_GPU_UNDEFINED_CACHING: u32 = 0; -pub const VIRTIO_GPU_CACHED: u32 = 1; -pub const VIRTIO_GPU_WRITE_COMBINE: u32 = 2; -pub const VIRTIO_GPU_UNCACHED: u32 = 3; +pub const VIRTIO_GPU_RESOURCE_HOST_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_HOST_MASK: u32 = 0x00f0; +pub const VIRTIO_GPU_RESOURCE_HOST: u32 = 0x0010; +pub const VIRTIO_GPU_RESOURCE_HOST_FROM_GUEST: u32 = 0x0020; + +pub const VIRTIO_GPU_RESOURCE_USE_NONE: u32 = 0x0000; +pub const VIRTIO_GPU_RESOURCE_USE_MASK: u32 = 0x0f00; +pub const VIRTIO_GPU_RESOURCE_USE_MAPPABLE: u32 = 0x0100; +pub const VIRTIO_GPU_RESOURCE_USE_SHAREABLE: u32 = 0x0200; +pub const VIRTIO_GPU_RESOURCE_USE_CROSS_DEVICE: u32 = 0x0400; -/* Limits on virtio-gpu stream (not upstreamed) */ -pub const VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE: u32 = 4096; /* This matches the limit in udmabuf.c */ pub const VIRTIO_GPU_MAX_IOVEC_ENTRIES: u32 = 1024; @@ -112,8 +114,8 @@ pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str { VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => "VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D", VIRTIO_GPU_CMD_SUBMIT_3D => "VIRTIO_GPU_CMD_SUBMIT_3D", VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2", - VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF", - VIRTIO_GPU_CMD_ALLOCATION_METADATA => "VIRTIO_GPU_CMD_ALLOCATION_METADATA", + VIRTIO_GPU_CMD_RESOURCE_MAP => "VIRTIO_GPU_RESOURCE_MAP", + VIRTIO_GPU_CMD_RESOURCE_UNMAP => "VIRTIO_GPU_RESOURCE_UNMAP", VIRTIO_GPU_CMD_UPDATE_CURSOR => "VIRTIO_GPU_CMD_UPDATE_CURSOR", VIRTIO_GPU_CMD_MOVE_CURSOR => "VIRTIO_GPU_CMD_MOVE_CURSOR", VIRTIO_GPU_RESP_OK_NODATA => "VIRTIO_GPU_RESP_OK_NODATA", @@ -497,52 +499,38 @@ unsafe impl DataInit for virtio_gpu_config {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_allocation_metadata { +pub struct virtio_gpu_resource_create_v2 { pub hdr: virtio_gpu_ctrl_hdr, - pub request_id: Le32, + pub resource_id: Le32, + pub flags: Le32, + pub size: Le64, + pub memory_id: Le64, + pub nr_entries: Le32, pub padding: Le32, - pub request_size: Le32, - pub response_size: Le32, } -unsafe impl DataInit for virtio_gpu_allocation_metadata {} - -/* VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA */ -#[derive(Copy, Clone, Debug, Default)] -#[repr(C)] -pub struct virtio_gpu_resp_allocation_metadata { - pub hdr: virtio_gpu_ctrl_hdr, - pub request_id: Le32, - pub response_size: Le32, -} - -unsafe impl DataInit for virtio_gpu_resp_allocation_metadata {} +unsafe impl DataInit for virtio_gpu_resource_create_v2 {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_resource_create_v2 { +pub struct virtio_gpu_resource_map { pub hdr: virtio_gpu_ctrl_hdr, pub resource_id: Le32, - pub guest_memory_type: Le32, - pub guest_caching_type: Le32, - pub padding: Le32, - pub size: Le64, - pub pci_addr: Le64, - pub args_size: Le32, - pub nr_entries: Le32, + pub map_flags: Le32, + pub offset: Le64, } -unsafe impl DataInit for virtio_gpu_resource_create_v2 {} +unsafe impl DataInit for virtio_gpu_resource_map {} #[derive(Copy, Clone, Debug, Default)] #[repr(C)] -pub struct virtio_gpu_resource_v2_unref { +pub struct virtio_gpu_resource_unmap { pub hdr: virtio_gpu_ctrl_hdr, pub resource_id: Le32, pub padding: Le32, } -unsafe impl DataInit for virtio_gpu_resource_v2_unref {} +unsafe impl DataInit for virtio_gpu_resource_unmap {} /* simple formats for fbcon/X use */ pub const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1; @@ -576,8 +564,8 @@ pub enum GpuCommand { TransferFromHost3d(virtio_gpu_transfer_host_3d), CmdSubmit3d(virtio_gpu_cmd_submit), ResourceCreateV2(virtio_gpu_resource_create_v2), - ResourceV2Unref(virtio_gpu_resource_v2_unref), - AllocationMetadata(virtio_gpu_allocation_metadata), + ResourceMap(virtio_gpu_resource_map), + ResourceUnmap(virtio_gpu_resource_unmap), UpdateCursor(virtio_gpu_update_cursor), MoveCursor(virtio_gpu_update_cursor), } @@ -645,8 +633,8 @@ impl fmt::Debug for GpuCommand { TransferFromHost3d(_info) => f.debug_struct("TransferFromHost3d").finish(), CmdSubmit3d(_info) => f.debug_struct("CmdSubmit3d").finish(), ResourceCreateV2(_info) => f.debug_struct("ResourceCreateV2").finish(), - ResourceV2Unref(_info) => f.debug_struct("ResourceV2Unref").finish(), - AllocationMetadata(_info) => f.debug_struct("AllocationMetadata").finish(), + ResourceMap(_info) => f.debug_struct("ResourceMap").finish(), + ResourceUnmap(_info) => f.debug_struct("ResourceUnmap").finish(), UpdateCursor(_info) => f.debug_struct("UpdateCursor").finish(), MoveCursor(_info) => f.debug_struct("MoveCursor").finish(), } @@ -678,8 +666,8 @@ impl GpuCommand { VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => TransferFromHost3d(cmd.read_obj()?), VIRTIO_GPU_CMD_SUBMIT_3D => CmdSubmit3d(cmd.read_obj()?), VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => ResourceCreateV2(cmd.read_obj()?), - VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => ResourceV2Unref(cmd.read_obj()?), - VIRTIO_GPU_CMD_ALLOCATION_METADATA => AllocationMetadata(cmd.read_obj()?), + VIRTIO_GPU_CMD_RESOURCE_MAP => ResourceMap(cmd.read_obj()?), + VIRTIO_GPU_CMD_RESOURCE_UNMAP => ResourceUnmap(cmd.read_obj()?), VIRTIO_GPU_CMD_UPDATE_CURSOR => UpdateCursor(cmd.read_obj()?), VIRTIO_GPU_CMD_MOVE_CURSOR => MoveCursor(cmd.read_obj()?), _ => return Err(GpuCommandDecodeError::InvalidType(hdr.type_.into())), @@ -709,8 +697,8 @@ impl GpuCommand { TransferFromHost3d(info) => &info.hdr, CmdSubmit3d(info) => &info.hdr, ResourceCreateV2(info) => &info.hdr, - ResourceV2Unref(info) => &info.hdr, - AllocationMetadata(info) => &info.hdr, + ResourceMap(info) => &info.hdr, + ResourceUnmap(info) => &info.hdr, UpdateCursor(info) => &info.hdr, MoveCursor(info) => &info.hdr, } @@ -723,12 +711,6 @@ pub struct GpuResponsePlaneInfo { pub offset: u32, } -#[derive(Default, Debug, PartialEq)] -pub struct AllocationMetadataResponse { - pub request_id: u32, - pub response: Vec<u8>, -} - /// A response to a `GpuCommand`. These correspond to `VIRTIO_GPU_RESP_*`. #[derive(Debug, PartialEq)] pub enum GpuResponse { @@ -744,9 +726,6 @@ pub enum GpuResponse { format_modifier: u64, plane_info: Vec<GpuResponsePlaneInfo>, }, - OkAllocationMetadata { - res_info: AllocationMetadataResponse, - }, ErrUnspec, ErrOutOfMemory, ErrInvalidScanoutId, @@ -880,17 +859,6 @@ impl GpuResponse { size_of_val(&hdr) } } - GpuResponse::OkAllocationMetadata { ref res_info } => { - let resp_info = virtio_gpu_resp_allocation_metadata { - hdr, - request_id: Le32::from(res_info.request_id), - response_size: Le32::from(res_info.response.len() as u32), - }; - - resp.write_obj(resp_info)?; - resp.write_all(&res_info.response)?; - size_of_val(&resp_info) + res_info.response.len() - } _ => { resp.write_obj(hdr)?; size_of_val(&hdr) @@ -907,7 +875,6 @@ impl GpuResponse { GpuResponse::OkCapsetInfo { .. } => VIRTIO_GPU_RESP_OK_CAPSET_INFO, GpuResponse::OkCapset(_) => VIRTIO_GPU_RESP_OK_CAPSET, GpuResponse::OkResourcePlaneInfo { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO, - GpuResponse::OkAllocationMetadata { .. } => VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA, GpuResponse::ErrUnspec => VIRTIO_GPU_RESP_ERR_UNSPEC, GpuResponse::ErrOutOfMemory => VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, GpuResponse::ErrInvalidScanoutId => VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID, @@ -925,7 +892,6 @@ impl GpuResponse { GpuResponse::OkCapsetInfo { .. } => true, GpuResponse::OkCapset(_) => true, GpuResponse::OkResourcePlaneInfo { .. } => true, - GpuResponse::OkAllocationMetadata { .. } => true, _ => false, } } diff --git a/devices/src/virtio/gpu/virtio_3d_backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs index 7ae044c..3ede8a6 100644 --- a/devices/src/virtio/gpu/virtio_3d_backend.rs +++ b/devices/src/virtio/gpu/virtio_3d_backend.rs @@ -26,13 +26,13 @@ use gpu_renderer::{ }; use super::protocol::{ - AllocationMetadataResponse, GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, - VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_MEMORY_HOST_COHERENT, + GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, VIRTIO_GPU_CAPSET_VIRGL, + VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_RESOURCE_USE_MAPPABLE, VIRTIO_GPU_RESOURCE_USE_MASK, }; pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource}; use crate::virtio::gpu::{ - Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY, - VIRTIO_GPU_F_VIRGL, + Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_VISIBLE, + VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_RESOURCE_V2, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_F_VULKAN, }; use crate::virtio::resource_bridge::{PlaneInfo, ResourceInfo, ResourceResponse}; @@ -44,6 +44,8 @@ struct Virtio3DResource { gpu_resource: GpuRendererResource, display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>, kvm_slot: Option<u32>, + size: u64, + flags: u32, } impl Virtio3DResource { @@ -54,27 +56,38 @@ impl Virtio3DResource { gpu_resource, display_import: None, kvm_slot: None, + flags: 0, + // The size of the host resource isn't really zero, but it's undefined by + // virtio_gpu_resource_create_3d + size: 0, } } pub fn v2_new( width: u32, height: u32, - kvm_slot: u32, gpu_resource: GpuRendererResource, + flags: u32, + size: u64, ) -> Virtio3DResource { Virtio3DResource { width, height, gpu_resource, display_import: None, - kvm_slot: Some(kvm_slot), + kvm_slot: None, + flags, + size, } } fn as_mut(&mut self) -> &mut dyn VirtioResource { self } + + fn use_flags(&self) -> u32 { + self.flags & VIRTIO_GPU_RESOURCE_USE_MASK + } } impl VirtioResource for Virtio3DResource { @@ -225,8 +238,10 @@ impl Backend for Virtio3DBackend { fn features() -> u64 { 1 << VIRTIO_GPU_F_VIRGL | 1 << VIRTIO_F_VERSION_1 - | 1 << VIRTIO_GPU_F_MEMORY - | 1 << VIRTIO_GPU_F_HOST_COHERENT + | 1 << VIRTIO_GPU_F_RESOURCE_UUID + | 1 << VIRTIO_GPU_F_RESOURCE_V2 + | 1 << VIRTIO_GPU_F_HOST_VISIBLE + | 1 << VIRTIO_GPU_F_VULKAN } /// Returns the underlying Backend. @@ -757,51 +772,26 @@ impl Backend for Virtio3DBackend { } } - fn allocation_metadata( - &mut self, - request_id: u32, - request: Vec<u8>, - mut response: Vec<u8>, - ) -> GpuResponse { - let res = self.renderer.allocation_metadata(&request, &mut response); - - match res { - Ok(_) => { - let res_info = AllocationMetadataResponse { - request_id, - response, - }; - - GpuResponse::OkAllocationMetadata { res_info } - } - Err(_) => { - error!("failed to get metadata"); - GpuResponse::ErrUnspec - } - } - } - fn resource_create_v2( &mut self, resource_id: u32, - guest_memory_type: u32, - guest_caching_type: u32, + ctx_id: u32, + flags: u32, size: u64, - pci_addr: u64, - mem: &GuestMemory, + memory_id: u64, vecs: Vec<(GuestAddress, usize)>, - args: Vec<u8>, + mem: &GuestMemory, ) -> GpuResponse { match self.resources.entry(resource_id) { Entry::Vacant(entry) => { let resource = match self.renderer.resource_create_v2( resource_id, - guest_memory_type, - guest_caching_type, + ctx_id, + flags, size, - mem, + memory_id, &vecs, - &args, + mem, ) { Ok(resource) => resource, Err(e) => { @@ -810,102 +800,119 @@ impl Backend for Virtio3DBackend { } }; - match guest_memory_type { - VIRTIO_GPU_MEMORY_HOST_COHERENT => { - let dma_buf_fd = match resource.export() { - Ok(export) => export.1, - Err(e) => { - error!("failed to export plane fd: {}", e); - return GpuResponse::ErrUnspec; - } - }; - - let request = VmMemoryRequest::RegisterMemoryAtAddress( - self.pci_bar, - MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()), - size as usize, - pci_addr, - ); - - match self.gpu_device_socket.send(&request) { - Ok(_resq) => match self.gpu_device_socket.recv() { - Ok(response) => match response { - VmMemoryResponse::RegisterMemory { pfn: _, slot } => { - entry.insert(Virtio3DResource::v2_new( - self.base.display_width, - self.base.display_height, - slot, - resource, - )); - GpuResponse::OkNoData - } - VmMemoryResponse::Err(e) => { - error!("received an error: {}", e); - GpuResponse::ErrUnspec - } - _ => { - error!("recieved an unexpected response"); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to receive data: {}", e); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to send request: {}", e); - GpuResponse::ErrUnspec - } - } - } - _ => { - entry.insert(Virtio3DResource::new( - self.base.display_width, - self.base.display_height, - resource, - )); + entry.insert(Virtio3DResource::v2_new( + self.base.display_width, + self.base.display_height, + resource, + flags, + size, + )); - GpuResponse::OkNoData - } - } + GpuResponse::OkNoData } Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, } } - fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse { - match self.resources.remove(&resource_id) { - Some(entry) => match entry.kvm_slot { - Some(kvm_slot) => { - let request = VmMemoryRequest::UnregisterMemory(kvm_slot); - match self.gpu_device_socket.send(&request) { - Ok(_resq) => match self.gpu_device_socket.recv() { - Ok(response) => match response { - VmMemoryResponse::Ok => GpuResponse::OkNoData, - VmMemoryResponse::Err(e) => { - error!("received an error: {}", e); - GpuResponse::ErrUnspec - } - _ => { - error!("recieved an unexpected response"); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to receive data: {}", e); - GpuResponse::ErrUnspec - } - }, - Err(e) => { - error!("failed to send request: {}", e); - GpuResponse::ErrUnspec - } - } - } - None => GpuResponse::OkNoData, - }, - None => GpuResponse::ErrInvalidResourceId, + fn resource_map(&mut self, resource_id: u32, offset: u64) -> GpuResponse { + let resource = match self.resources.get_mut(&resource_id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; + + if resource.use_flags() & VIRTIO_GPU_RESOURCE_USE_MAPPABLE == 0 { + error!("resource not mappable"); + return GpuResponse::ErrUnspec; + } + + let dma_buf_fd = match resource.gpu_resource.export() { + Ok(export) => export.1, + Err(e) => { + error!("failed to export plane fd: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + let request = VmMemoryRequest::RegisterFdAtPciBarOffset( + self.pci_bar, + MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()), + resource.size as usize, + offset, + ); + + match self.gpu_device_socket.send(&request) { + Ok(_) => (), + Err(e) => { + error!("failed to send request: {}", e); + return GpuResponse::ErrUnspec; + } + } + + let response = match self.gpu_device_socket.recv() { + Ok(response) => response, + Err(e) => { + error!("failed to receive data: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + match response { + VmMemoryResponse::RegisterMemory { pfn: _, slot } => { + resource.kvm_slot = Some(slot); + GpuResponse::OkNoData + } + VmMemoryResponse::Err(e) => { + error!("received an error: {}", e); + GpuResponse::ErrUnspec + } + _ => { + error!("recieved an unexpected response"); + GpuResponse::ErrUnspec + } + } + } + + fn resource_unmap(&mut self, resource_id: u32) -> GpuResponse { + let resource = match self.resources.get_mut(&resource_id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; + + let kvm_slot = match resource.kvm_slot { + Some(kvm_slot) => kvm_slot, + None => return GpuResponse::ErrUnspec, + }; + + let request = VmMemoryRequest::UnregisterMemory(kvm_slot); + match self.gpu_device_socket.send(&request) { + Ok(_) => (), + Err(e) => { + error!("failed to send request: {}", e); + return GpuResponse::ErrUnspec; + } + } + + let response = match self.gpu_device_socket.recv() { + Ok(response) => response, + Err(e) => { + error!("failed to receive data: {}", e); + return GpuResponse::ErrUnspec; + } + }; + + match response { + VmMemoryResponse::Ok => { + resource.kvm_slot = None; + GpuResponse::OkNoData + } + VmMemoryResponse::Err(e) => { + error!("received an error: {}", e); + GpuResponse::ErrUnspec + } + _ => { + error!("recieved an unexpected response"); + GpuResponse::ErrUnspec + } } } } diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs index aa02e15..2a49da8 100644 --- a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs +++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs @@ -10,7 +10,6 @@ use std::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; -use std::fs::File; use std::mem::transmute; use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void}; use std::panic; @@ -27,6 +26,7 @@ use vm_control::VmMemoryControlRequestSocket; use super::protocol::GpuResponse; pub use super::virtio_backend::{VirtioBackend, VirtioResource}; use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_VIRGL}; +use crate::virtio::resource_bridge::ResourceResponse; // C definitions related to gfxstream // In gfxstream, only write_fence is used @@ -340,8 +340,8 @@ impl Backend for VirtioGfxStreamBackend { } /// If supported, export the resource with the given id to a file. - fn export_resource(&mut self, _id: u32) -> Option<File> { - None + fn export_resource(&mut self, _id: u32) -> ResourceResponse { + ResourceResponse::Invalid } /// Creates a fence with the given id that can be used to determine when the previous command diff --git a/devices/src/virtio/interrupt.rs b/devices/src/virtio/interrupt.rs index f808d84..91a3942 100644 --- a/devices/src/virtio/interrupt.rs +++ b/devices/src/virtio/interrupt.rs @@ -13,7 +13,7 @@ pub struct Interrupt { interrupt_status: Arc<AtomicUsize>, interrupt_evt: EventFd, interrupt_resample_evt: EventFd, - msix_config: Option<Arc<Mutex<MsixConfig>>>, + pub msix_config: Option<Arc<Mutex<MsixConfig>>>, config_msix_vector: u16, } diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index 7716fe0..4d5d2cb 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -6,6 +6,7 @@ mod balloon; mod block; +mod console; mod descriptor_utils; mod input; mod interrupt; @@ -29,6 +30,7 @@ pub mod vhost; pub use self::balloon::*; pub use self::block::*; +pub use self::console::*; pub use self::descriptor_utils::Error as DescriptorError; pub use self::descriptor_utils::*; #[cfg(feature = "gpu")] diff --git a/devices/src/virtio/vhost/control_socket.rs b/devices/src/virtio/vhost/control_socket.rs new file mode 100644 index 0000000..a1ccfaf --- /dev/null +++ b/devices/src/virtio/vhost/control_socket.rs @@ -0,0 +1,36 @@ +// 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 msg_socket::{MsgOnSocket, MsgSocket}; +use sys_util::Error as SysError; + +#[derive(MsgOnSocket, Debug)] +pub enum VhostDevRequest { + /// Mask or unmask all the MSI entries for a Virtio Vhost device. + MsixChanged, + /// Mask or unmask a MSI entry for a Virtio Vhost device. + MsixEntryChanged(usize), +} + +#[derive(MsgOnSocket, Debug)] +pub enum VhostDevResponse { + Ok, + Err(SysError), +} + +pub type VhostDevRequestSocket = MsgSocket<VhostDevRequest, VhostDevResponse>; +pub type VhostDevResponseSocket = MsgSocket<VhostDevResponse, VhostDevRequest>; + +/// Create control socket pair. This pair is used to communicate with the +/// virtio device process. +/// Mainly between the virtio and activate thread. +pub fn create_control_sockets() -> ( + Option<VhostDevRequestSocket>, + Option<VhostDevResponseSocket>, +) { + match msg_socket::pair::<VhostDevRequest, VhostDevResponse>() { + Ok((request, response)) => (Some(request), Some(response)), + _ => (None, None), + } +} diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs index 66c62d0..86ed81e 100644 --- a/devices/src/virtio/vhost/mod.rs +++ b/devices/src/virtio/vhost/mod.rs @@ -12,10 +12,12 @@ use remain::sorted; use sys_util::Error as SysError; use vhost::Error as VhostError; +mod control_socket; mod net; mod vsock; mod worker; +pub use self::control_socket::*; pub use self::net::Net; pub use self::vsock::Vsock; diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs index ff72970..542423a 100644 --- a/devices/src/virtio/vhost/net.rs +++ b/devices/src/virtio/vhost/net.rs @@ -14,9 +14,12 @@ use sys_util::{error, warn, EventFd, GuestMemory}; use vhost::NetT as VhostNetT; use virtio_sys::virtio_net; +use super::control_socket::*; use super::worker::Worker; use super::{Error, Result}; +use crate::pci::MsixStatus; use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_NET}; +use msg_socket::{MsgReceiver, MsgSender}; const QUEUE_SIZE: u16 = 256; const NUM_QUEUES: usize = 2; @@ -31,6 +34,8 @@ pub struct Net<T: TapT, U: VhostNetT<T>> { vhost_interrupt: Option<Vec<EventFd>>, avail_features: u64, acked_features: u64, + request_socket: Option<VhostDevRequestSocket>, + response_socket: Option<VhostDevResponseSocket>, } impl<T, U> Net<T, U> @@ -85,6 +90,8 @@ where vhost_interrupt.push(EventFd::new().map_err(Error::VhostIrqCreate)?); } + let (request_socket, response_socket) = create_control_sockets(); + Ok(Net { workers_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?), kill_evt, @@ -94,6 +101,8 @@ where vhost_interrupt: Some(vhost_interrupt), avail_features, acked_features: 0u64, + request_socket, + response_socket, }) } } @@ -143,6 +152,14 @@ where } keep_fds.push(self.kill_evt.as_raw_fd()); + if let Some(request_socket) = &self.request_socket { + keep_fds.push(request_socket.as_raw_fd()); + } + + if let Some(response_socket) = &self.response_socket { + keep_fds.push(response_socket.as_raw_fd()); + } + keep_fds } @@ -189,6 +206,11 @@ where if let Some(vhost_interrupt) = self.vhost_interrupt.take() { if let Some(kill_evt) = self.workers_kill_evt.take() { let acked_features = self.acked_features; + let socket = if self.response_socket.is_some() { + self.response_socket.take() + } else { + None + }; let worker_result = thread::Builder::new() .name("vhost_net".to_string()) .spawn(move || { @@ -199,6 +221,7 @@ where interrupt, acked_features, kill_evt, + socket, ); let activate_vqs = |handle: &U| -> Result<()> { for idx in 0..NUM_QUEUES { @@ -251,6 +274,48 @@ where } } + fn control_notify(&self, behavior: MsixStatus) { + if self.worker_thread.is_none() || self.request_socket.is_none() { + return; + } + if let Some(socket) = &self.request_socket { + match behavior { + MsixStatus::EntryChanged(index) => { + if let Err(e) = socket.send(&VhostDevRequest::MsixEntryChanged(index)) { + error!( + "{} failed to send VhostMsixEntryChanged request for entry {}: {:?}", + self.debug_label(), + index, + e + ); + return; + } + if let Err(e) = socket.recv() { + error!("{} failed to receive VhostMsixEntryChanged response for entry {}: {:?}", self.debug_label(), index, e); + } + } + MsixStatus::Changed => { + if let Err(e) = socket.send(&VhostDevRequest::MsixChanged) { + error!( + "{} failed to send VhostMsixChanged request: {:?}", + self.debug_label(), + e + ); + return; + } + if let Err(e) = socket.recv() { + error!( + "{} failed to receive VhostMsixChanged response {:?}", + self.debug_label(), + e + ); + } + } + _ => {} + } + } + } + fn reset(&mut self) -> bool { // Only kill the child if it claimed its eventfd. if self.workers_kill_evt.is_none() && self.kill_evt.write(1).is_err() { @@ -269,6 +334,7 @@ where self.tap = Some(tap); self.vhost_interrupt = Some(worker.vhost_interrupt); self.workers_kill_evt = Some(worker.kill_evt); + self.response_socket = worker.response_socket; return true; } } diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs index 03826ff..390c857 100644 --- a/devices/src/virtio/vhost/vsock.rs +++ b/devices/src/virtio/vhost/vsock.rs @@ -169,6 +169,7 @@ impl VirtioDevice for Vsock { interrupt, acked_features, kill_evt, + None, ); let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> { handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?; diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs index 1eff01f..ca02a63 100644 --- a/devices/src/virtio/vhost/worker.rs +++ b/devices/src/virtio/vhost/worker.rs @@ -4,16 +4,16 @@ use std::os::raw::c_ulonglong; -use sys_util::{EventFd, PollContext, PollToken}; +use sys_util::{error, Error as SysError, EventFd, PollContext, PollToken}; use vhost::Vhost; +use super::control_socket::{VhostDevRequest, VhostDevResponse, VhostDevResponseSocket}; use super::{Error, Result}; use crate::virtio::{Interrupt, Queue}; +use libc::EIO; +use msg_socket::{MsgReceiver, MsgSender}; -/// Worker that takes care of running the vhost device. This mainly involves forwarding interrupts -/// from the vhost driver to the guest VM because crosvm only supports the virtio-mmio transport, -/// which requires a bit to be set in the interrupt status register before triggering the interrupt -/// and the vhost driver doesn't do this for us. +/// Worker that takes care of running the vhost device. pub struct Worker<T: Vhost> { interrupt: Interrupt, queues: Vec<Queue>, @@ -21,6 +21,7 @@ pub struct Worker<T: Vhost> { pub vhost_interrupt: Vec<EventFd>, acked_features: u64, pub kill_evt: EventFd, + pub response_socket: Option<VhostDevResponseSocket>, } impl<T: Vhost> Worker<T> { @@ -31,6 +32,7 @@ impl<T: Vhost> Worker<T> { interrupt: Interrupt, acked_features: u64, kill_evt: EventFd, + response_socket: Option<VhostDevResponseSocket>, ) -> Worker<T> { Worker { interrupt, @@ -39,6 +41,7 @@ impl<T: Vhost> Worker<T> { vhost_interrupt, acked_features, kill_evt, + response_socket, } } @@ -87,9 +90,7 @@ impl<T: Vhost> Worker<T> { self.vhost_handle .set_vring_base(queue_index, 0) .map_err(Error::VhostSetVringBase)?; - self.vhost_handle - .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) - .map_err(Error::VhostSetVringCall)?; + self.set_vring_call_for_entry(queue_index, queue.vector as usize)?; self.vhost_handle .set_vring_kick(queue_index, &queue_evts[queue_index]) .map_err(Error::VhostSetVringKick)?; @@ -102,6 +103,7 @@ impl<T: Vhost> Worker<T> { VhostIrqi { index: usize }, InterruptResample, Kill, + ControlNotify, } let poll_ctx: PollContext<Token> = PollContext::build_with(&[ @@ -115,6 +117,11 @@ impl<T: Vhost> Worker<T> { .add(vhost_int, Token::VhostIrqi { index }) .map_err(Error::CreatePollContext)?; } + if let Some(socket) = &self.response_socket { + poll_ctx + .add(socket, Token::ControlNotify) + .map_err(Error::CreatePollContext)?; + } 'poll: loop { let events = poll_ctx.wait().map_err(Error::PollError)?; @@ -134,10 +141,122 @@ impl<T: Vhost> Worker<T> { let _ = self.kill_evt.read(); break 'poll; } + Token::ControlNotify => { + if let Some(socket) = &self.response_socket { + match socket.recv() { + Ok(VhostDevRequest::MsixEntryChanged(index)) => { + let mut qindex = 0; + for (queue_index, queue) in self.queues.iter().enumerate() { + if queue.vector == index as u16 { + qindex = queue_index; + break; + } + } + let response = + match self.set_vring_call_for_entry(qindex, index) { + Ok(()) => VhostDevResponse::Ok, + Err(e) => { + error!( + "Set vring call failed for masked entry {}: {:?}", + index, e + ); + VhostDevResponse::Err(SysError::new(EIO)) + } + }; + if let Err(e) = socket.send(&response) { + error!("Vhost failed to send VhostMsixEntryMasked Response for entry {}: {:?}", index, e); + } + } + Ok(VhostDevRequest::MsixChanged) => { + let response = match self.set_vring_calls() { + Ok(()) => VhostDevResponse::Ok, + Err(e) => { + error!("Set vring calls failed: {:?}", e); + VhostDevResponse::Err(SysError::new(EIO)) + } + }; + if let Err(e) = socket.send(&response) { + error!( + "Vhost failed to send VhostMsixMasked Response: {:?}", + e + ); + } + } + Err(e) => { + error!("Vhost failed to receive Control request: {:?}", e); + } + } + } + } } } } cleanup_vqs(&self.vhost_handle)?; Ok(()) } + + fn set_vring_call_for_entry(&self, queue_index: usize, vector: usize) -> Result<()> { + // No response_socket means it doesn't have any control related + // with the msix. Due to this, cannot use the direct irq fd but + // should fall back to indirect irq fd. + if self.response_socket.is_some() { + if let Some(msix_config) = &self.interrupt.msix_config { + let msix_config = msix_config.lock(); + let msix_masked = msix_config.masked(); + if msix_masked { + return Ok(()); + } + if !msix_config.table_masked(vector) { + if let Some(irqfd) = msix_config.get_irqfd(vector) { + self.vhost_handle + .set_vring_call(queue_index, irqfd) + .map_err(Error::VhostSetVringCall)?; + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + return Ok(()); + } + } + } + + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + Ok(()) + } + + fn set_vring_calls(&self) -> Result<()> { + if let Some(msix_config) = &self.interrupt.msix_config { + let msix_config = msix_config.lock(); + if msix_config.masked() { + for (queue_index, _) in self.queues.iter().enumerate() { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } else { + for (queue_index, queue) in self.queues.iter().enumerate() { + let vector = queue.vector as usize; + if !msix_config.table_masked(vector) { + if let Some(irqfd) = msix_config.get_irqfd(vector) { + self.vhost_handle + .set_vring_call(queue_index, irqfd) + .map_err(Error::VhostSetVringCall)?; + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } else { + self.vhost_handle + .set_vring_call(queue_index, &self.vhost_interrupt[queue_index]) + .map_err(Error::VhostSetVringCall)?; + } + } + } + } + Ok(()) + } } diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs index 806a98f..6eb5548 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::{PciBarConfiguration, PciCapability}; +use crate::pci::{MsixStatus, PciBarConfiguration, PciCapability}; /// Trait for virtio devices to be driven by a virtio transport. /// @@ -86,4 +86,6 @@ pub trait VirtioDevice: Send { /// Invoked when the device is sandboxed. fn on_device_sandboxed(&mut self) {} + + fn control_notify(&self, _behavior: MsixStatus) {} } diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs index c6d6786..e63abe9 100644 --- a/devices/src/virtio/virtio_pci_device.rs +++ b/devices/src/virtio/virtio_pci_device.rs @@ -535,7 +535,8 @@ impl PciDevice for VirtioPciDevice { fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx { if msix_cap_reg_idx == reg_idx { - self.msix_config.lock().write_msix_capability(offset, data); + let behavior = self.msix_config.lock().write_msix_capability(offset, data); + self.device.control_notify(behavior); } } @@ -626,9 +627,11 @@ impl PciDevice for VirtioPciDevice { // Handled with ioeventfds. } o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => { - self.msix_config + let behavior = self + .msix_config .lock() .write_msix_table(o - MSIX_TABLE_BAR_OFFSET, data); + self.device.control_notify(behavior); } o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => { self.msix_config diff --git a/docker/checkout_commits.env b/docker/checkout_commits.env index 92586bc..60aecc1 100644 --- a/docker/checkout_commits.env +++ b/docker/checkout_commits.env @@ -2,5 +2,5 @@ MESON_COMMIT=a1a8772034aef90e8d58230d8bcfce54ab27bf6a LIBEPOXY_COMMIT=af38a466caf9c2ae49b8acda4ff842ae44d57f78 TPM2_COMMIT=a9bc45bb7fafc65ea8a787894434d409f533b1f1 PLATFORM2_COMMIT=9239a43f2dc2e98e57e9d77aac72fa3ce8169e5f -ADHD_COMMIT=d99fa2312e03594c1de236e7ac74b332b0ddc329 +ADHD_COMMIT=db796cecdea7013b8679f90dfae34915edc9246f DRM_COMMIT=00320d7d68ddc7d815d073bb7c92d9a1f9bb8c31 diff --git a/gpu_display/src/keycode_converter/mod.rs b/gpu_display/src/keycode_converter/mod.rs index 40fdb95..236e65d 100644 --- a/gpu_display/src/keycode_converter/mod.rs +++ b/gpu_display/src/keycode_converter/mod.rs @@ -23,7 +23,7 @@ impl KeycodeTranslator { /// Create a new KeycodeTranslator that translates from the `from_type` type to Linux keycodes. pub fn new(from_type: KeycodeTypes) -> KeycodeTranslator { let mut kcm: HashMap<u32, MapEntry> = HashMap::new(); - for entry in KEYCODE_MAP.into_iter() { + for entry in KEYCODE_MAP.iter() { kcm.insert( match from_type { KeycodeTypes::XkbScancode => entry.xkb, diff --git a/gpu_renderer/src/generated/virglrenderer.rs b/gpu_renderer/src/generated/virglrenderer.rs index 01bf219..020d69f 100644 --- a/gpu_renderer/src/generated/virglrenderer.rs +++ b/gpu_renderer/src/generated/virglrenderer.rs @@ -307,24 +307,21 @@ extern "C" { execute_size: u32, ) -> ::std::os::raw::c_int; } -extern "C" { - pub fn virgl_renderer_allocation_metadata( - request: *const ::std::os::raw::c_void, - response: *mut ::std::os::raw::c_void, - request_size: u32, - response_size: u32, - ) -> ::std::os::raw::c_int; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct virgl_renderer_resource_create_v2_args { + pub version: u32, + pub res_handle: u32, + pub ctx_id: u32, + pub flags: u32, + pub size: u64, + pub memory_id: u64, + pub iovecs: *mut iovec, + pub num_iovs: u32, } extern "C" { pub fn virgl_renderer_resource_create_v2( - resource_id: u32, - guest_memory_type: u32, - guest_caching_type: u32, - size: u64, - iovec: *const iovec, - num_iovs: u32, - args: *const ::std::os::raw::c_void, - args_size: u32, + args: *mut virgl_renderer_resource_create_v2_args, ) -> ::std::os::raw::c_int; } pub type __builtin_va_list = [__va_list_tag; 1usize]; diff --git a/gpu_renderer/src/lib.rs b/gpu_renderer/src/lib.rs index be8c17a..5e5ab95 100644 --- a/gpu_renderer/src/lib.rs +++ b/gpu_renderer/src/lib.rs @@ -6,8 +6,11 @@ mod command_buffer; mod generated; +mod vsnprintf; use std::cell::RefCell; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use std::ffi::CString; use std::fmt::{self, Display}; use std::fs::File; use std::marker::PhantomData; @@ -22,7 +25,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use libc::close; use data_model::{VolatileMemory, VolatileSlice}; -use sys_util::{GuestAddress, GuestMemory}; +use sys_util::{debug, GuestAddress, GuestMemory}; use crate::generated::p_defines::{ PIPE_BIND_RENDER_TARGET, PIPE_BIND_SAMPLER_VIEW, PIPE_TEXTURE_1D, PIPE_TEXTURE_2D, @@ -31,8 +34,10 @@ use crate::generated::p_format::PIPE_FORMAT_B8G8R8X8_UNORM; use crate::generated::virglrenderer::*; pub use crate::command_buffer::CommandBufferBuilder; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use crate::vsnprintf::vsnprintf; -/// Arguments used in `Renderer::create_resource`.. +/// Arguments used in `Renderer::create_resource`. pub type ResourceCreateArgs = virgl_renderer_resource_create_args; /// Some of the information returned from `Resource::export_query`. pub type Query = virgl_renderer_export_query; @@ -245,6 +250,11 @@ impl Renderer { fence_state: Rc::clone(&fence_state), })); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + virgl_set_debug_callback(Some(Renderer::debug_callback)) + }; + // Safe because a valid cookie and set of callbacks is used and the result is checked for // error. let ret = unsafe { @@ -396,71 +406,52 @@ impl Renderer { } #[allow(unused_variables)] - pub fn allocation_metadata(&self, request: &[u8], response: &mut Vec<u8>) -> Result<()> { - #[cfg(feature = "virtio-gpu-next")] - { - let ret = unsafe { - virgl_renderer_allocation_metadata( - request.as_ptr() as *const c_void, - response.as_mut_ptr() as *mut c_void, - request.len() as u32, - response.len() as u32, - ) - }; - ret_to_res(ret) - } - #[cfg(not(feature = "virtio-gpu-next"))] - Err(Error::Unsupported) - } - - #[allow(unused_variables)] pub fn resource_create_v2( &self, resource_id: u32, - guest_memory_type: u32, - guest_caching_type: u32, + ctx_id: u32, + flags: u32, size: u64, + memory_id: u64, + vecs: &[(GuestAddress, usize)], mem: &GuestMemory, - iovecs: &[(GuestAddress, usize)], - args: &[u8], ) -> Result<Resource> { #[cfg(feature = "virtio-gpu-next")] { - if iovecs + if vecs .iter() .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) { return Err(Error::InvalidIovec); } - let mut vecs = Vec::new(); - for &(addr, len) in iovecs { + let mut iovecs = Vec::new(); + for &(addr, len) in vecs { // Unwrap will not panic because we already checked the slices. let slice = mem.get_slice(addr.offset(), len as u64).unwrap(); - vecs.push(VirglVec { + iovecs.push(VirglVec { base: slice.as_ptr() as *mut c_void, len, }); } - let ret = unsafe { - virgl_renderer_resource_create_v2( - resource_id, - guest_memory_type, - guest_caching_type, - size, - vecs.as_ptr() as *const iovec, - vecs.len() as u32, - args.as_ptr() as *const c_void, - args.len() as u32, - ) + let mut resource_create_args = virgl_renderer_resource_create_v2_args { + version: 1, + res_handle: resource_id, + ctx_id, + flags, + size, + memory_id, + iovecs: iovecs.as_mut_ptr() as *mut iovec, + num_iovs: iovecs.len() as u32, }; + let ret = unsafe { virgl_renderer_resource_create_v2(&mut resource_create_args) }; ret_to_res(ret)?; Ok(Resource { id: resource_id, - backing_iovecs: vecs, + backing_iovecs: iovecs, backing_mem: None, no_sync_send: PhantomData, }) @@ -468,6 +459,28 @@ impl Renderer { #[cfg(not(feature = "virtio-gpu-next"))] Err(Error::Unsupported) } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + extern "C" fn debug_callback( + fmt: *const ::std::os::raw::c_char, + ap: *mut generated::virglrenderer::__va_list_tag, + ) { + let len: u32 = 256; + let mut c_str = CString::new(vec![' ' as u8; len as usize]).unwrap(); + unsafe { + let mut varargs = vsnprintf::__va_list_tag { + gp_offset: (*ap).gp_offset, + fp_offset: (*ap).fp_offset, + overflow_arg_area: (*ap).overflow_arg_area, + reg_save_area: (*ap).reg_save_area, + }; + + let raw = c_str.into_raw(); + vsnprintf(raw, len.into(), fmt, &mut varargs); + c_str = CString::from_raw(raw); + } + debug!("{}", c_str.to_string_lossy()); + } } /// A context in which resources can be attached/detached and commands can be submitted. diff --git a/gpu_renderer/src/vsnprintf.rs b/gpu_renderer/src/vsnprintf.rs new file mode 100644 index 0000000..ec121ad --- /dev/null +++ b/gpu_renderer/src/vsnprintf.rs @@ -0,0 +1,33 @@ +// 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. + +#![allow(dead_code, non_snake_case, non_camel_case_types)] + +/* + * automatically generated by rust-bindgen + * $ bindgen /usr/include/stdio.h \ + * --no-layout-tests \ + * --whitelist-function vsnprintf \ + * -o vsnprintf.rs + */ + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +extern "C" { + pub fn vsnprintf( + __s: *mut ::std::os::raw::c_char, + __maxlen: ::std::os::raw::c_ulong, + __format: *const ::std::os::raw::c_char, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __va_list_tag { + pub gp_offset: ::std::os::raw::c_uint, + pub fp_offset: ::std::os::raw::c_uint, + pub overflow_arg_area: *mut ::std::os::raw::c_void, + pub reg_save_area: *mut ::std::os::raw::c_void, +} diff --git a/p9/src/server.rs b/p9/src/server.rs index b8a026f..e1ffd16 100644 --- a/p9/src/server.rs +++ b/p9/src/server.rs @@ -44,7 +44,7 @@ const MAPPED_FLAGS: [(u32, i32); 10] = [ (P9_NONBLOCK, libc::O_NONBLOCK), (P9_DSYNC, libc::O_DSYNC), (P9_FASYNC, 0), // Unsupported - (P9_DIRECT, libc::O_DIRECT), + (P9_DIRECT, 0), // Unsupported (P9_LARGEFILE, libc::O_LARGEFILE), (P9_DIRECTORY, libc::O_DIRECTORY), (P9_NOFOLLOW, libc::O_NOFOLLOW), diff --git a/seccomp/aarch64/fs_device.policy b/seccomp/aarch64/fs_device.policy index 9fd4c8b..ec9d155 100644 --- a/seccomp/aarch64/fs_device.policy +++ b/seccomp/aarch64/fs_device.policy @@ -4,6 +4,7 @@ @include /usr/share/policy/crosvm/common_device.policy +copy_file_range: 1 fallocate: 1 fchmodat: 1 fchownat: 1 diff --git a/seccomp/arm/fs_device.policy b/seccomp/arm/fs_device.policy index eb9df16..4078f41 100644 --- a/seccomp/arm/fs_device.policy +++ b/seccomp/arm/fs_device.policy @@ -4,6 +4,7 @@ @include /usr/share/policy/crosvm/common_device.policy +copy_file_range: 1 fallocate: 1 fchmodat: 1 fchownat: 1 diff --git a/seccomp/x86_64/common_device.frequency b/seccomp/x86_64/common_device.frequency new file mode 100644 index 0000000..618c44d --- /dev/null +++ b/seccomp/x86_64/common_device.frequency @@ -0,0 +1,45 @@ +# 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. + +capget: 30 +capset: 30 +chdir: 30 +chroot: 15 +clone: 30 +close: 1185 +dup: 50 +dup2: 160 +epoll_ctl: 25 +epoll_wait: 90 +eventfd2: 75 +exit: 15 +exit_group: 15 +fchdir: 30 +fstat: 90 +futex: 20 +getdents: 55 +ioctl: 350 +mmap: 95 +mount: 45 +mprotect: 45 +openat: 515 +pipe: 15 +pivot_root: 15 +prctl: 570 +prlimit64: 15 +read: 82415 +recvmsg: 85 +restart_syscall: 15 +rt_sigaction: 20 +rt_sigreturn: 15 +seccomp: 25 +sendmsg: 390 +setsockopt: 30 +socket: 20 +socketpair: 30 +stat: 30 +umount2: 15 +unshare: 30 +wait4: 20 +write: 56100 diff --git a/seccomp/x86_64/common_device.policy b/seccomp/x86_64/common_device.policy index 8464c4b..453719d 100644 --- a/seccomp/x86_64/common_device.policy +++ b/seccomp/x86_64/common_device.policy @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +@frequency ./common_device.frequency brk: 1 clone: arg0 & CLONE_THREAD close: 1 diff --git a/seccomp/x86_64/fs_device.policy b/seccomp/x86_64/fs_device.policy index ddb2a51..eb5a1c4 100644 --- a/seccomp/x86_64/fs_device.policy +++ b/seccomp/x86_64/fs_device.policy @@ -4,6 +4,7 @@ @include /usr/share/policy/crosvm/common_device.policy +copy_file_range: 1 fallocate: 1 fchmodat: 1 fchownat: 1 diff --git a/src/argument.rs b/src/argument.rs index 9ef9d4d..0d0e142 100644 --- a/src/argument.rs +++ b/src/argument.rs @@ -25,7 +25,7 @@ //! let v: u32 = value.unwrap().parse().map_err(|_| { //! Error::InvalidValue { //! value: value.unwrap().to_owned(), -//! expected: "this value for `cpus` needs to be integer", +//! expected: String::from("this value for `cpus` needs to be integer"), //! } //! })?; //! } @@ -56,10 +56,7 @@ pub enum Error { /// The argument was required. ExpectedArgument(String), /// The argument's given value is invalid. - InvalidValue { - value: String, - expected: &'static str, - }, + InvalidValue { value: String, expected: String }, /// The argument was already given and none more are expected. TooManyArguments(String), /// The argument expects a value. @@ -453,7 +450,7 @@ mod tests { "cpus" => { let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this value for `cpus` needs to be integer", + expected: String::from("this value for `cpus` needs to be integer"), })?; assert_eq!(c, 3); } @@ -527,7 +524,7 @@ mod tests { _ => { return Err(Error::InvalidValue { value: v.to_string(), - expected: "2D or 3D", + expected: String::from("2D or 3D"), }) } } diff --git a/src/crosvm.rs b/src/crosvm.rs index b3a9233..81344c3 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -20,7 +20,7 @@ use arch::Pstore; use devices::virtio::fs::passthrough; #[cfg(feature = "gpu")] use devices::virtio::gpu::GpuParameters; -use devices::SerialParameters; +use devices::{Ac97Parameters, SerialParameters}; use libc::{getegid, geteuid}; static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm"; @@ -189,11 +189,9 @@ pub struct Config { #[cfg(feature = "gpu")] pub gpu_parameters: Option<GpuParameters>, pub software_tpm: bool, - pub cras_audio: bool, - pub cras_capture: bool, - pub null_audio: bool, pub display_window_keyboard: bool, pub display_window_mouse: bool, + pub ac97_parameters: Vec<Ac97Parameters>, pub serial_parameters: BTreeMap<u8, SerialParameters>, pub syslog_tag: Option<String>, pub virtio_single_touch: Option<TouchDeviceOption>, @@ -240,9 +238,7 @@ impl Default for Config { sandbox: !cfg!(feature = "default-no-sandbox"), seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR), seccomp_log_failures: false, - cras_audio: false, - cras_capture: false, - null_audio: false, + ac97_parameters: Vec::new(), serial_parameters: BTreeMap::new(), syslog_tag: None, virtio_single_touch: None, diff --git a/src/linux.rs b/src/linux.rs index 2de1aaa..130644d 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -27,17 +27,15 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use libc::{self, c_int, gid_t, uid_t}; -use audio_streams::shm_streams::NullShmStreamSource; #[cfg(feature = "gpu")] use devices::virtio::EventDevice; use devices::virtio::{self, VirtioDevice}; use devices::{ - self, HostBackendDeviceProvider, PciDevice, VfioContainer, VfioDevice, VfioPciDevice, - VirtioPciDevice, XhciController, + self, Ac97Backend, Ac97Dev, HostBackendDeviceProvider, PciDevice, VfioContainer, VfioDevice, + VfioPciDevice, VirtioPciDevice, XhciController, }; use io_jail::{self, Minijail}; use kvm::*; -use libcras::CrasClient; use msg_socket::{MsgError, MsgReceiver, MsgSender, MsgSocket}; use net_util::{Error as NetError, MacAddress, Tap}; use rand_ish::SimpleRng; @@ -83,7 +81,7 @@ pub enum Error { BuildVm(<Arch as LinuxArch>::Error), ChownTpmStorage(sys_util::Error), CloneEventFd(sys_util::Error), - CreateCrasClient(libcras::Error), + CreateAc97(devices::PciDeviceError), CreateDiskError(disk::Error), CreateEventFd(sys_util::Error), CreatePollContext(sys_util::Error), @@ -168,7 +166,7 @@ impl Display for Error { BuildVm(e) => write!(f, "The architecture failed to build the vm: {}", e), ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e), CloneEventFd(e) => write!(f, "failed to clone eventfd: {}", e), - CreateCrasClient(e) => write!(f, "failed to create cras client: {}", e), + CreateAc97(e) => write!(f, "failed to create ac97 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), @@ -1150,27 +1148,14 @@ fn create_devices( pci_devices.push((dev, stub.jail)); } - if cfg.cras_audio { - let mut server = Box::new(CrasClient::new().map_err(Error::CreateCrasClient)?); - if cfg.cras_capture { - server.enable_cras_capture(); - } - let cras_audio = devices::Ac97Dev::new(mem.clone(), server); - - pci_devices.push(( - Box::new(cras_audio), - simple_jail(&cfg, "cras_audio_device")?, - )); - } - - if cfg.null_audio { - let server = Box::new(NullShmStreamSource::new()); - let null_audio = devices::Ac97Dev::new(mem.clone(), server); + for ac97_param in &cfg.ac97_parameters { + let dev = Ac97Dev::try_new(mem.clone(), ac97_param.clone()).map_err(Error::CreateAc97)?; + let policy = match ac97_param.backend { + Ac97Backend::CRAS => "cras_audio_device", + Ac97Backend::NULL => "null_audio_device", + }; - pci_devices.push(( - Box::new(null_audio), - simple_jail(&cfg, "null_audio_device")?, - )); + pci_devices.push((Box::new(dev), simple_jail(&cfg, &policy)?)); } // Create xhci controller. let usb_controller = Box::new(XhciController::new(mem.clone(), usb_provider)); @@ -1751,7 +1736,7 @@ fn run_control( .map_err(Error::PollContextAdd)?; if let Some(gsi_relay) = &linux.gsi_relay { - for (gsi, evt) in gsi_relay.irqfd.into_iter().enumerate() { + for (gsi, evt) in gsi_relay.irqfd.iter().enumerate() { if let Some(evt) = evt { poll_ctx .add(evt, Token::IrqFd { gsi }) diff --git a/src/main.rs b/src/main.rs index 1804028..d385db3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ pub mod panic_hook; +use std::default::Default; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader}; @@ -17,13 +18,14 @@ use std::thread::sleep; use std::time::Duration; use arch::Pstore; +use audio_streams::StreamEffect; use crosvm::{ argument::{self, print_help, set_arguments, Argument}, linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption, }; #[cfg(feature = "gpu")] use devices::virtio::gpu::{GpuMode, GpuParameters}; -use devices::{SerialParameters, SerialType}; +use devices::{Ac97Backend, Ac97Parameters, SerialParameters, SerialType}; use disk::QcowFile; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; use sys_util::{ @@ -78,21 +80,21 @@ fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> { if range.len() == 0 || range.len() > 2 { return Err(argument::Error::InvalidValue { value: part.to_owned(), - expected: "invalid list syntax", + expected: String::from("invalid list syntax"), }); } let first_cpu: usize = range[0] .parse() .map_err(|_| argument::Error::InvalidValue { value: part.to_owned(), - expected: "CPU index must be a non-negative integer", + expected: String::from("CPU index must be a non-negative integer"), })?; let last_cpu: usize = if range.len() == 2 { range[1] .parse() .map_err(|_| argument::Error::InvalidValue { value: part.to_owned(), - expected: "CPU index must be a non-negative integer", + expected: String::from("CPU index must be a non-negative integer"), })? } else { first_cpu @@ -101,7 +103,7 @@ fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> { if last_cpu < first_cpu { return Err(argument::Error::InvalidValue { value: part.to_owned(), - expected: "CPU ranges must be from low to high", + expected: String::from("CPU ranges must be from low to high"), }); } @@ -151,7 +153,9 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { _ => { return Err(argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'backend' should be one of (2d|3d|gfxstream)", + expected: String::from( + "gpu parameter 'backend' should be one of (2d|3d|gfxstream)", + ), }); } }, @@ -165,7 +169,7 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { _ => { return Err(argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'egl' should be a boolean", + expected: String::from("gpu parameter 'egl' should be a boolean"), }); } }, @@ -179,7 +183,7 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { _ => { return Err(argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'gles' should be a boolean", + expected: String::from("gpu parameter 'gles' should be a boolean"), }); } }, @@ -193,7 +197,7 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { _ => { return Err(argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'glx' should be a boolean", + expected: String::from("gpu parameter 'glx' should be a boolean"), }); } }, @@ -207,7 +211,9 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { _ => { return Err(argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'surfaceless' should be a boolean", + expected: String::from( + "gpu parameter 'surfaceless' should be a boolean", + ), }); } }, @@ -216,7 +222,9 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { v.parse::<u32>() .map_err(|_| argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'width' must be a valid integer", + expected: String::from( + "gpu parameter 'width' must be a valid integer", + ), })?; } "height" => { @@ -224,7 +232,9 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { v.parse::<u32>() .map_err(|_| argument::Error::InvalidValue { value: v.to_string(), - expected: "gpu parameter 'height' must be a valid integer", + expected: String::from( + "gpu parameter 'height' must be a valid integer", + ), })?; } "" => {} @@ -241,6 +251,53 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> { Ok(gpu_params) } +fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> { + let mut ac97_params: Ac97Parameters = Default::default(); + + let opts = s + .split(",") + .map(|frag| frag.split("=")) + .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or(""))); + + for (k, v) in opts { + match k { + "backend" => { + ac97_params.backend = + v.parse::<Ac97Backend>() + .map_err(|e| argument::Error::InvalidValue { + value: v.to_string(), + expected: e.to_string(), + })?; + } + "capture" => { + ac97_params.capture = v.parse::<bool>().map_err(|e| { + argument::Error::Syntax(format!("invalid capture option: {}", e)) + })?; + } + "capture_effects" => { + ac97_params.capture_effects = v + .split("|") + .map(|val| { + val.parse::<StreamEffect>() + .map_err(|e| argument::Error::InvalidValue { + value: val.to_string(), + expected: e.to_string(), + }) + }) + .collect::<argument::Result<Vec<_>>>()?; + } + _ => { + return Err(argument::Error::UnknownArgument(format!( + "unknown ac97 parameter {}", + k + ))); + } + } + } + + Ok(ac97_params) +} + fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { let mut serial_setting = SerialParameters { type_: SerialType::Sink, @@ -269,7 +326,7 @@ fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { if num < 1 || num > 4 { return Err(argument::Error::InvalidValue { value: num.to_string(), - expected: "Serial port num must be between 1 - 4", + expected: String::from("Serial port num must be between 1 - 4"), }); } serial_setting.num = num; @@ -305,7 +362,9 @@ fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { if components.is_empty() || components.len() > 3 || components[0].is_empty() { return Err(argument::Error::InvalidValue { value: value.to_owned(), - expected: "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]", + expected: String::from( + "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]", + ), }); } @@ -313,13 +372,13 @@ fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { if src.is_relative() { return Err(argument::Error::InvalidValue { value: components[0].to_owned(), - expected: "the source path for `plugin-mount` must be absolute", + expected: String::from("the source path for `plugin-mount` must be absolute"), }); } if !src.exists() { return Err(argument::Error::InvalidValue { value: components[0].to_owned(), - expected: "the source path for `plugin-mount` does not exist", + expected: String::from("the source path for `plugin-mount` does not exist"), }); } @@ -330,7 +389,7 @@ fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { if dst.is_relative() { return Err(argument::Error::InvalidValue { value: components[1].to_owned(), - expected: "the destination path for `plugin-mount` must be absolute", + expected: String::from("the destination path for `plugin-mount` must be absolute"), }); } @@ -338,7 +397,7 @@ fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> { None => false, Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue { value: components[2].to_owned(), - expected: "the <writable> component for `plugin-mount` is not valid bool", + expected: String::from("the <writable> component for `plugin-mount` is not valid bool"), })?, }; @@ -350,8 +409,9 @@ fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> { if components.is_empty() || components.len() > 3 || components[0].is_empty() { return Err(argument::Error::InvalidValue { value: value.to_owned(), - expected: + expected: String::from( "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]", + ), }); } @@ -359,14 +419,14 @@ fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> { .parse() .map_err(|_| argument::Error::InvalidValue { value: components[0].to_owned(), - expected: "the <inner> component for `plugin-gid-map` is not valid gid", + expected: String::from("the <inner> component for `plugin-gid-map` is not valid gid"), })?; let outer: libc::gid_t = match components.get(1) { None | Some(&"") => inner, Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue { value: components[1].to_owned(), - expected: "the <outer> component for `plugin-gid-map` is not valid gid", + expected: String::from("the <outer> component for `plugin-gid-map` is not valid gid"), })?, }; @@ -374,7 +434,9 @@ fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> { None => 1, Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue { value: components[2].to_owned(), - expected: "the <count> component for `plugin-gid-map` is not valid number", + expected: String::from( + "the <count> component for `plugin-gid-map` is not valid number", + ), })?, }; @@ -398,7 +460,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if !kernel_path.exists() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this kernel path does not exist", + expected: String::from("this kernel path does not exist"), }); } cfg.executable_path = Some(Executable::Kernel(kernel_path)); @@ -415,7 +477,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if !android_fstab.exists() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this android fstab path does not exist", + expected: String::from("this android fstab path does not exist"), }); } cfg.android_fstab = Some(android_fstab); @@ -437,7 +499,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this value for `cpus` needs to be integer", + expected: String::from("this value for `cpus` needs to be integer"), })?, ) } @@ -462,18 +524,13 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this value for `mem` needs to be integer", + expected: String::from("this value for `mem` needs to be integer"), })?, ) } - "cras-audio" => { - cfg.cras_audio = true; - } - "cras-capture" => { - cfg.cras_capture = true; - } - "null-audio" => { - cfg.null_audio = true; + "ac97" => { + let ac97_params = parse_ac97_options(value.unwrap())?; + cfg.ac97_parameters.push(ac97_params); } "serial" => { let serial_params = parse_serial_options(value.unwrap())?; @@ -526,13 +583,13 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .next() .ok_or_else(|| argument::Error::InvalidValue { value: param.to_owned(), - expected: "missing disk path", + expected: String::from("missing disk path"), })?, ); if !disk_path.exists() { return Err(argument::Error::InvalidValue { value: param.to_owned(), - expected: "this disk path does not exist", + expected: String::from("this disk path does not exist"), }); } if name.ends_with("root") { @@ -559,18 +616,18 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let mut o = opt.splitn(2, '='); let kind = o.next().ok_or_else(|| argument::Error::InvalidValue { value: opt.to_owned(), - expected: "disk options must not be empty", + expected: String::from("disk options must not be empty"), })?; let value = o.next().ok_or_else(|| argument::Error::InvalidValue { value: opt.to_owned(), - expected: "disk options must be of the form `kind=value`", + expected: String::from("disk options must be of the form `kind=value`"), })?; match kind { "sparse" => { let sparse = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`sparse` must be a boolean", + expected: String::from("`sparse` must be a boolean"), })?; disk.sparse = sparse; } @@ -578,14 +635,14 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let block_size = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`block_size` must be an integer", + expected: String::from("`block_size` must be an integer"), })?; disk.block_size = block_size; } _ => { return Err(argument::Error::InvalidValue { value: kind.to_owned(), - expected: "unrecognized disk option", + expected: String::from("unrecognized disk option"), }); } } @@ -598,7 +655,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if !disk_path.exists() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this disk path does not exist", + expected: String::from("this disk path does not exist"), }); } @@ -621,7 +678,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if components.len() != 2 { return Err(argument::Error::InvalidValue { value: value.to_owned(), - expected: "pstore must have exactly 2 components: path=<path>,size=<size>", + expected: String::from( + "pstore must have exactly 2 components: path=<path>,size=<size>", + ), }); } cfg.pstore = Some(Pstore { @@ -629,7 +688,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if components[0].len() <= 5 || !components[0].starts_with("path=") { return Err(argument::Error::InvalidValue { value: components[0].to_owned(), - expected: "pstore path must follow with `path=`", + expected: String::from("pstore path must follow with `path=`"), }); }; PathBuf::from(&components[0][5..]) @@ -638,14 +697,14 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if components[1].len() <= 5 || !components[1].starts_with("size=") { return Err(argument::Error::InvalidValue { value: components[1].to_owned(), - expected: "pstore size must follow with `size=`", + expected: String::from("pstore size must follow with `size=`"), }); }; components[1][5..] .parse() .map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "pstore size must be an integer", + expected: String::from("pstore size must be an integer"), })? }, }); @@ -663,7 +722,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "`host_ip` needs to be in the form \"x.x.x.x\"", + expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""), })?, ) } @@ -680,7 +739,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "`netmask` needs to be in the form \"x.x.x.x\"", + expected: String::from("`netmask` needs to be in the form \"x.x.x.x\""), })?, ) } @@ -697,7 +756,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"", + expected: String::from( + "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"", + ), })?, ) } @@ -709,7 +770,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .next() .ok_or_else(|| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "missing socket path", + expected: String::from("missing socket path"), })?, ); let mut name = ""; @@ -720,7 +781,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: _ => { return Err(argument::Error::InvalidValue { value: c.to_owned(), - expected: "option must be of the form `kind=value`", + expected: String::from("option must be of the form `kind=value`"), }) } }; @@ -729,7 +790,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: _ => { return Err(argument::Error::InvalidValue { value: kind.to_owned(), - expected: "unrecognized option", + expected: String::from("unrecognized option"), }) } } @@ -771,7 +832,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if socket_path.exists() { return Err(argument::Error::InvalidValue { value: socket_path.to_string_lossy().into_owned(), - expected: "this socket path already exists", + expected: String::from("this socket path already exists"), }); } cfg.socket_path = Some(socket_path); @@ -791,7 +852,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this value for `cid` must be an unsigned integer", + expected: String::from("this value for `cid` must be an unsigned integer"), })?, ); } @@ -816,21 +877,21 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .next() .ok_or_else(|| argument::Error::InvalidValue { value: param.to_owned(), - expected: "missing source path for `shared-dir`", + expected: String::from("missing source path for `shared-dir`"), })?, ); let tag = components .next() .ok_or_else(|| argument::Error::InvalidValue { value: param.to_owned(), - expected: "missing tag for `shared-dir`", + expected: String::from("missing tag for `shared-dir`"), })? .to_owned(); if !src.is_dir() { return Err(argument::Error::InvalidValue { value: param.to_owned(), - expected: "source path for `shared-dir` must be a directory", + expected: String::from("source path for `shared-dir` must be a directory"), }); } @@ -843,11 +904,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let mut o = opt.splitn(2, '='); let kind = o.next().ok_or_else(|| argument::Error::InvalidValue { value: opt.to_owned(), - expected: "`shared-dir` options must not be empty", + expected: String::from("`shared-dir` options must not be empty"), })?; let value = o.next().ok_or_else(|| argument::Error::InvalidValue { value: opt.to_owned(), - expected: "`shared-dir` options must be of the form `kind=value`", + expected: String::from("`shared-dir` options must be of the form `kind=value`"), })?; match kind { @@ -855,7 +916,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: shared_dir.kind = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`type` must be one of `fs` or `9p`", + expected: String::from("`type` must be one of `fs` or `9p`"), })? } "uidmap" => shared_dir.uid_map = value.into(), @@ -863,7 +924,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "timeout" => { let seconds = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`timeout` must be an integer", + expected: String::from("`timeout` must be an integer"), })?; let dur = Duration::from_secs(seconds); @@ -873,7 +934,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "cache" => { let policy = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`cache` must be one of `never`, `always`, or `auto`", + expected: String::from( + "`cache` must be one of `never`, `always`, or `auto`", + ), })?; shared_dir.cfg.cache_policy = policy; } @@ -881,14 +944,14 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: let writeback = value.parse().map_err(|_| argument::Error::InvalidValue { value: value.to_owned(), - expected: "`writeback` must be a boolean", + expected: String::from("`writeback` must be a boolean"), })?; shared_dir.cfg.writeback = writeback; } _ => { return Err(argument::Error::InvalidValue { value: kind.to_owned(), - expected: "unrecognized option for `shared-dir`", + expected: String::from("unrecognized option for `shared-dir`"), }) } } @@ -930,7 +993,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if plugin.is_relative() { return Err(argument::Error::InvalidValue { value: plugin.to_string_lossy().into_owned(), - expected: "the plugin path must be an absolute path", + expected: String::from("the plugin path must be an absolute path"), }); } cfg.executable_path = Some(Executable::Plugin(plugin)); @@ -945,7 +1008,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "plugin-mount-file" => { let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "unable to open `plugin-mount-file` file", + expected: String::from("unable to open `plugin-mount-file` file"), })?; let reader = BufReader::new(file); for l in reader.lines() { @@ -964,7 +1027,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "plugin-gid-map-file" => { let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "unable to open `plugin-gid-map-file` file", + expected: String::from("unable to open `plugin-gid-map-file` file"), })?; let reader = BufReader::new(file); for l in reader.lines() { @@ -984,7 +1047,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: .parse() .map_err(|_| argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this value for `tap-fd` must be an unsigned integer", + expected: String::from( + "this value for `tap-fd` must be an unsigned integer", + ), })?, ); } @@ -1053,7 +1118,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if !dev_path.exists() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "this input device path does not exist", + expected: String::from("this input device path does not exist"), }); } cfg.virtio_input_evdevs.push(dev_path); @@ -1078,13 +1143,13 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if !vfio_path.exists() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "the vfio path does not exist", + expected: String::from("the vfio path does not exist"), }); } if !vfio_path.is_dir() { return Err(argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "the vfio path should be directory", + expected: String::from("the vfio path should be directory"), }); } @@ -1223,24 +1288,28 @@ Path to pstore buffer backend file follewed by size. ), Argument::value("netmask", "NETMASK", "Netmask for VM subnet."), Argument::value("mac", "MAC", "MAC address for VM."), - Argument::flag( - "cras-audio", - "\ -Add an audio device to the VM that plays samples through CRAS server. -", - ), - Argument::flag( - "cras-capture", - "\ -Enable capturing audio from CRAS server to the cras-audio device. -", - ), - Argument::flag( - "null-audio", - "\ -Add an audio device to the VM that plays samples to /dev/null. -", + Argument::value( + "ac97", + "[backend=BACKEND,capture=true,capture_effect=EFFECT]", + r#"Comma separated key=value pairs for setting up Ac97 devices. Can be +given more than once. + +Possible key values: + + backend=(null,cras) + Where to route the audio device. "null" for /dev/null, and "cras" + for CRAS server. + (default: null) + + capture + Enable audio capture. + + capture_effects + Separated effects to be enabled for recording. The only supported + effect value now is EchoCancellation or aec. +"#, ), + Argument::value( "serial", "type=TYPE,[num=NUM,path=PATH,console,stdin]", @@ -1683,7 +1752,7 @@ fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> { size = Some(value.unwrap().parse::<u64>().map_err(|_| { argument::Error::InvalidValue { value: value.unwrap().to_owned(), - expected: "SIZE should be a nonnegative integer", + expected: String::from("SIZE should be a nonnegative integer"), } })?); } @@ -2100,6 +2169,34 @@ mod tests { } #[test] + fn parse_ac97_vaild() { + parse_ac97_options("backend=cras").expect("parse should have succeded"); + } + + #[test] + fn parse_ac97_null_vaild() { + parse_ac97_options("backend=null").expect("parse should have succeded"); + } + + #[test] + fn parse_ac97_dup_effect_vaild() { + parse_ac97_options("backend=cras,capture=true,capture_effects=aec|aec") + .expect("parse should have succeded"); + } + + #[test] + fn parse_ac97_effect_invaild() { + parse_ac97_options("backend=cras,capture=true,capture_effects=abc") + .expect_err("parse should have failed"); + } + + #[test] + fn parse_ac97_effect_vaild() { + parse_ac97_options("backend=cras,capture=true,capture_effects=aec") + .expect("parse should have succeded"); + } + + #[test] fn parse_serial_vaild() { parse_serial_options("type=syslog,num=1,console=true,stdin=true") .expect("parse should have succeded"); diff --git a/usb_util/src/device.rs b/usb_util/src/device.rs index aad77db..3ac1403 100644 --- a/usb_util/src/device.rs +++ b/usb_util/src/device.rs @@ -261,12 +261,8 @@ impl Device { } /// Get active config descriptor of this device. - pub fn get_active_config_descriptor(&self) -> Result<ConfigDescriptorTree> { - let active_config = self.get_active_configuration()?; - match self - .device_descriptor_tree - .get_config_descriptor(active_config) - { + pub fn get_config_descriptor(&self, config: u8) -> Result<ConfigDescriptorTree> { + match self.device_descriptor_tree.get_config_descriptor(config) { Some(config_descriptor) => Ok(config_descriptor.clone()), None => Err(Error::NoSuchDescriptor), } @@ -297,6 +293,11 @@ impl Device { Ok(active_config) } + /// Get the total number of configurations for this device. + pub fn get_num_configurations(&self) -> u8 { + self.device_descriptor_tree.bNumConfigurations + } + /// Clear the halt/stall condition for an endpoint. pub fn clear_halt(&self, ep_addr: u8) -> Result<()> { let endpoint: c_uint = ep_addr.into(); diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs index 8a7b9ec..0216a90 100644 --- a/vm_control/src/lib.rs +++ b/vm_control/src/lib.rs @@ -192,7 +192,7 @@ pub enum VmMemoryRequest { RegisterMemory(MaybeOwnedFd, usize), /// Similiar to `VmMemoryRequest::RegisterMemory`, but doesn't allocate new address space. /// Useful for cases where the address space is already allocated (PCI regions). - RegisterMemoryAtAddress(Alloc, MaybeOwnedFd, usize, u64), + RegisterFdAtPciBarOffset(Alloc, MaybeOwnedFd, usize, u64), /// Unregister the given memory slot that was previously registereed with `RegisterMemory`. UnregisterMemory(u32), /// Allocate GPU buffer of a given size/format and register the memory into guest address space. @@ -230,8 +230,8 @@ impl VmMemoryRequest { Err(e) => VmMemoryResponse::Err(e), } } - RegisterMemoryAtAddress(alloc, ref fd, size, guest_addr) => { - match register_memory(vm, sys_allocator, fd, size, Some((alloc, guest_addr))) { + RegisterFdAtPciBarOffset(alloc, ref fd, size, offset) => { + match register_memory(vm, sys_allocator, fd, size, Some((alloc, offset))) { Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot }, Err(e) => VmMemoryResponse::Err(e), } @@ -433,12 +433,13 @@ fn register_memory( }; let addr = match allocation { - Some((Alloc::PciBar { bus, dev, bar }, address)) => { + Some((Alloc::PciBar { bus, dev, bar }, offset)) => { match allocator .mmio_allocator(MmioType::High) .get(&Alloc::PciBar { bus, dev, bar }) { Some((start_addr, length, _)) => { + let address = *start_addr + offset; let range = *start_addr..*start_addr + *length; let end = address + (size as u64); match (range.contains(&address), range.contains(&end)) { diff --git a/x86_64/src/gdt.rs b/x86_64/src/gdt.rs index 7eb1ff7..a37ddd4 100644 --- a/x86_64/src/gdt.rs +++ b/x86_64/src/gdt.rs @@ -8,17 +8,17 @@ use kvm_sys::kvm_segment; /// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h. pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 { - ((((base as u64) & 0xff000000u64) << (56 - 24)) + (((base as u64) & 0xff000000u64) << (56 - 24)) | (((flags as u64) & 0x0000f0ffu64) << 40) | (((limit as u64) & 0x000f0000u64) << (48 - 16)) | (((base as u64) & 0x00ffffffu64) << 16) - | ((limit as u64) & 0x0000ffffu64)) + | ((limit as u64) & 0x0000ffffu64) } fn get_base(entry: u64) -> u64 { - ((((entry) & 0xFF00000000000000) >> 32) + (((entry) & 0xFF00000000000000) >> 32) | (((entry) & 0x000000FF00000000) >> 16) - | (((entry) & 0x00000000FFFF0000) >> 16)) + | (((entry) & 0x00000000FFFF0000) >> 16) } fn get_limit(entry: u64) -> u32 { diff --git a/x86_64/src/interrupts.rs b/x86_64/src/interrupts.rs index a2b0f1c..5fba859 100644 --- a/x86_64/src/interrupts.rs +++ b/x86_64/src/interrupts.rs @@ -57,7 +57,7 @@ fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) { } fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 { - (((reg) & !0x700) | ((mode) << 8)) + ((reg) & !0x700) | ((mode) << 8) } /// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI. diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index a912edd..a4ba444 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -189,7 +189,15 @@ const CMDLINE_OFFSET: u64 = 0x20000; const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET; const X86_64_SERIAL_1_3_IRQ: u32 = 4; const X86_64_SERIAL_2_4_IRQ: u32 = 3; -const X86_64_IRQ_BASE: u32 = 5; +// X86_64_SCI_IRQ is used to fill the ACPI FACP table. +// The sci_irq number is better to be a legacy +// IRQ number which is less than 16(actually most of the +// platforms have fixed IRQ number 9). So we can +// reserve the IRQ number 5 for SCI and let the +// the other devices starts from next. +pub const X86_64_SCI_IRQ: u32 = 5; +// So the IRQ_BASE start from SCI_IRQ + 1 +pub const X86_64_IRQ_BASE: u32 = X86_64_SCI_IRQ + 1; const ACPI_HI_RSDP_WINDOW_BASE: u64 = 0x000E0000; fn configure_system( @@ -203,7 +211,6 @@ fn configure_system( setup_data: Option<GuestAddress>, initrd: Option<(GuestAddress, usize)>, mut params: boot_params, - sci_irq: u32, ) -> Result<()> { const EBDA_START: u64 = 0x0009fc00; const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; @@ -267,7 +274,7 @@ fn configure_system( .write_obj_at_addr(params, zero_page_addr) .map_err(|_| Error::ZeroPageSetup)?; - let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, sci_irq); + let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, X86_64_SCI_IRQ); params.acpi_rsdp_addr = rsdp_addr.0; Ok(()) @@ -404,8 +411,6 @@ impl arch::LinuxArch for X8664arch { // Event used to notify crosvm that guest OS is trying to suspend. let suspend_evt = EventFd::new().map_err(Error::CreateEventFd)?; - // allocate sci_irq to fill the ACPI FACP table - let sci_irq = resources.allocate_irq().ok_or(Error::AllocateIrq)?; let mut io_bus = Self::setup_io_bus( &mut vm, @@ -492,7 +497,6 @@ impl arch::LinuxArch for X8664arch { components.android_fstab, kernel_end, params, - sci_irq, )?; } } @@ -579,7 +583,6 @@ impl X8664arch { android_fstab: Option<File>, kernel_end: u64, params: boot_params, - sci_irq: u32, ) -> Result<()> { kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline) .map_err(Error::LoadCmdline)?; @@ -641,7 +644,6 @@ impl X8664arch { setup_data, initrd, params, - sci_irq, )?; Ok(()) } @@ -723,7 +725,7 @@ impl X8664arch { let tty_string = get_serial_tty_string(stdio_serial_num); cmdline.insert("console", &tty_string).unwrap(); } - cmdline.insert_str("acpi=off reboot=k panic=-1").unwrap(); + cmdline.insert_str("pci=noacpi reboot=k panic=-1").unwrap(); cmdline } diff --git a/x86_64/src/mpspec.rs b/x86_64/src/mpspec.rs index ab7af51..5340d9e 100644 --- a/x86_64/src/mpspec.rs +++ b/x86_64/src/mpspec.rs @@ -38,6 +38,7 @@ pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1; pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0; pub const MP_IRQDIR_HIGH: ::std::os::raw::c_uint = 1; pub const MP_IRQDIR_LOW: ::std::os::raw::c_uint = 3; +pub const MP_LEVEL_TRIGGER: ::std::os::raw::c_uint = 0xc; pub const MP_APIC_ALL: ::std::os::raw::c_uint = 255; pub const MPC_OEM_SIGNATURE: &'static [u8; 5usize] = b"_OEM\x00"; #[repr(C)] diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs index 9aded3f..cac9e58 100644 --- a/x86_64/src/mptable.rs +++ b/x86_64/src/mptable.rs @@ -231,8 +231,9 @@ pub fn setup_mptable( base_mp = base_mp.unchecked_add(size as u64); checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc)); } + let sci_irq = super::X86_64_SCI_IRQ as u8; // Per kvm_setup_default_irq_routing() in kernel - for i in 0..5 { + for i in 0..sci_irq { let size = mem::size_of::<mpc_intsrc>(); let mut mpc_intsrc = mpc_intsrc::default(); mpc_intsrc.type_ = MP_INTSRC as u8; @@ -247,6 +248,25 @@ pub fn setup_mptable( base_mp = base_mp.unchecked_add(size as u64); checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc)); } + // Insert SCI interrupt before PCI interrupts. Set the SCI interrupt + // to be the default trigger/polarity of PCI bus, which is level/low. + // This setting can be changed in future if necessary. + { + 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_HIGH | MP_LEVEL_TRIGGER) as u16; + mpc_intsrc.srcbus = ISA_BUS_ID; + mpc_intsrc.srcbusirq = sci_irq; + mpc_intsrc.dstapic = ioapicid; + mpc_intsrc.dstirq = sci_irq; + mem.write_obj_at_addr(mpc_intsrc, base_mp) + .map_err(|_| Error::WriteMpcIntsrc)?; + base_mp = base_mp.unchecked_add(size as u64); + checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc)); + } + 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() { let size = mem::size_of::<mpc_intsrc>(); @@ -257,14 +277,14 @@ pub fn setup_mptable( 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.dstapic = ioapicid; - mpc_intsrc.dstirq = 5 + i as u8; + mpc_intsrc.dstirq = pci_irq_base + i as u8; mem.write_obj_at_addr(mpc_intsrc, base_mp) .map_err(|_| Error::WriteMpcIntsrc)?; base_mp = base_mp.unchecked_add(size as u64); checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc)); } // Finally insert ISA interrupts. - for i in 5 + pci_irqs.len()..16 { + for i in pci_irq_base + pci_irqs.len() as u8..16 { let size = mem::size_of::<mpc_intsrc>(); let mut mpc_intsrc = mpc_intsrc::default(); mpc_intsrc.type_ = MP_INTSRC as u8; |