diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | arch/src/lib.rs | 1 | ||||
-rw-r--r-- | devices/Cargo.toml | 1 | ||||
-rw-r--r-- | devices/src/lib.rs | 13 | ||||
-rw-r--r-- | devices/src/pci/mod.rs | 3 | ||||
-rw-r--r-- | devices/src/usb/host_backend/context.rs | 7 | ||||
-rw-r--r-- | devices/src/usb/host_backend/mod.rs | 2 | ||||
-rw-r--r-- | devices/src/usb/mod.rs | 2 | ||||
-rw-r--r-- | devices/src/usb/xhci/mod.rs | 12 | ||||
-rw-r--r-- | devices/src/usb/xhci/xhci.rs | 387 | ||||
-rw-r--r-- | devices/src/usb/xhci/xhci_controller.rs | 277 | ||||
-rw-r--r-- | seccomp/arm/xhci.policy | 40 | ||||
-rw-r--r-- | seccomp/x86_64/xhci.policy | 38 | ||||
-rw-r--r-- | src/linux.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 212 | ||||
-rw-r--r-- | vm_control/src/lib.rs | 15 |
16 files changed, 999 insertions, 29 deletions
diff --git a/Cargo.toml b/Cargo.toml index 253604d..ed06b35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,9 @@ default-no-sandbox = [] wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"] gpu = ["devices/gpu"] usb-emulation = ["usb_util"] -sandboxed-libusb = ["usb_util/sandboxed-libusb"] tpm = ["devices/tpm"] gpu-forward = ["render_node_forward"] +sandboxed-libusb = ["devices/sandboxed-libusb", "usb_util/sandboxed-libusb", "vm_control/sandboxed-libusb"] [dependencies] arch = { path = "arch" } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 38bc2c7..f089972 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -190,6 +190,7 @@ pub fn generate_pci_root( pid_labels.insert(proxy.pid() as u32, proxy.debug_label()); Arc::new(Mutex::new(proxy)) } else { + device.on_sandboxed(); Arc::new(Mutex::new(device)) }; root.add_device(arced_dev.clone()); diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 352135b..13ccb22 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -7,6 +7,7 @@ authors = ["The Chromium OS Authors"] wl-dmabuf = [] gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"] tpm = ["tpm2"] +sandboxed-libusb = [] [dependencies] audio_streams = "*" diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 442939e..dae8d3f 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -23,14 +23,11 @@ extern crate sync; extern crate sys_util; #[cfg(feature = "tpm")] extern crate tpm2; +extern crate usb_util; extern crate vhost; extern crate virtio_sys; extern crate vm_control; -#[allow(dead_code)] -#[macro_use] -mod register_space; - mod bus; mod cmos; mod i8042; @@ -39,10 +36,10 @@ mod pci; mod pit; pub mod pl030; mod proxy; +#[macro_use] +mod register_space; mod serial; -#[allow(dead_code)] -mod usb; -#[allow(dead_code)] +pub mod usb; mod utils; pub mod virtio; @@ -59,4 +56,6 @@ pub use self::pl030::Pl030; pub use self::proxy::Error as ProxyError; pub use self::proxy::ProxyDevice; pub use self::serial::Serial; +pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; +pub use self::usb::xhci::xhci_controller::XhciController; pub use self::virtio::VirtioPciDevice; diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs index 3aab059..791161a 100644 --- a/devices/src/pci/mod.rs +++ b/devices/src/pci/mod.rs @@ -15,7 +15,8 @@ mod pci_root; pub use self::ac97::Ac97Dev; pub use self::pci_configuration::{ PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID, - PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSubclass, + PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSerialBusSubClass, + PciSubclass, }; pub use self::pci_device::Error as PciDeviceError; pub use self::pci_device::PciDevice; diff --git a/devices/src/usb/host_backend/context.rs b/devices/src/usb/host_backend/context.rs index 3fd8d3d..260aad1 100644 --- a/devices/src/usb/host_backend/context.rs +++ b/devices/src/usb/host_backend/context.rs @@ -129,11 +129,14 @@ struct PollfdChangeHandler { impl LibUsbPollfdChangeHandler for PollfdChangeHandler { fn add_poll_fd(&self, fd: RawFd, events: c_short) { - self.event_loop.add_event( + match self.event_loop.add_event( &MaybeOwnedFd::Borrowed(fd), WatchingEvents::new(events as u32), self.event_handler.clone(), - ); + ) { + Err(e) => error!("cannot add event to event loop: {}", e), + Ok(_) => {} + } } fn remove_poll_fd(&self, fd: RawFd) { diff --git a/devices/src/usb/host_backend/mod.rs b/devices/src/usb/host_backend/mod.rs index 8951a08..ea0f44c 100644 --- a/devices/src/usb/host_backend/mod.rs +++ b/devices/src/usb/host_backend/mod.rs @@ -3,7 +3,7 @@ // found in the LICENSE file. pub mod context; -mod error; +pub mod error; pub mod host_backend_device_provider; pub mod host_device; mod hotplug; diff --git a/devices/src/usb/mod.rs b/devices/src/usb/mod.rs index 78d93bd..738c693 100644 --- a/devices/src/usb/mod.rs +++ b/devices/src/usb/mod.rs @@ -6,5 +6,5 @@ extern crate usb_util; #[macro_use] mod log; -mod host_backend; +pub mod host_backend; pub mod xhci; diff --git a/devices/src/usb/xhci/mod.rs b/devices/src/usb/xhci/mod.rs index 3aabdc6..c652e90 100644 --- a/devices/src/usb/xhci/mod.rs +++ b/devices/src/usb/xhci/mod.rs @@ -10,12 +10,18 @@ mod intr_resample_handler; mod ring_buffer; mod ring_buffer_controller; mod ring_buffer_stop_cb; -pub mod scatter_gather_buffer; mod transfer_ring_controller; -pub mod usb_hub; +mod xhci; +#[allow(dead_code)] mod xhci_abi; +#[allow(dead_code)] mod xhci_abi_schema; +#[allow(dead_code)] +mod xhci_regs; + +pub mod scatter_gather_buffer; +pub mod usb_hub; pub mod xhci_backend_device; pub mod xhci_backend_device_provider; -mod xhci_regs; +pub mod xhci_controller; pub mod xhci_transfer; diff --git a/devices/src/usb/xhci/xhci.rs b/devices/src/usb/xhci/xhci.rs new file mode 100644 index 0000000..b69397e --- /dev/null +++ b/devices/src/usb/xhci/xhci.rs @@ -0,0 +1,387 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use super::command_ring_controller::{CommandRingController, CommandRingControllerError}; +use super::device_slot::{DeviceSlots, Error as DeviceSlotError}; +use super::interrupter::{Error as InterrupterError, Interrupter}; +use super::intr_resample_handler::IntrResampleHandler; +use super::ring_buffer_stop_cb::RingBufferStopCallback; +use super::usb_hub::UsbHub; +use super::xhci_backend_device_provider::XhciBackendDeviceProvider; +use super::xhci_regs::*; +use std::fmt::{self, Display}; +use std::sync::Arc; +use sync::Mutex; +use sys_util::{EventFd, GuestAddress, GuestMemory}; +use usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; +use utils::{Error as UtilsError, EventLoop, FailHandle}; + +#[derive(Debug)] +pub enum Error { + StartEventLoop(UtilsError), + GetDeviceSlot(u8), + StartResampleHandler, + SendInterrupt(InterrupterError), + EnableInterrupter(InterrupterError), + SetModeration(InterrupterError), + SetupEventRing(InterrupterError), + SetEventHandlerBusy(InterrupterError), + StartProvider, + RingDoorbell(DeviceSlotError), + CreateCommandRingController(CommandRingControllerError), +} + +type Result<T> = std::result::Result<T, Error>; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + StartEventLoop(e) => write!(f, "failed to start event loop: {}", e), + GetDeviceSlot(i) => write!(f, "failed to get device slot: {}", i), + StartResampleHandler => write!(f, "failed to start resample handler"), + SendInterrupt(e) => write!(f, "failed to send interrupter: {}", e), + EnableInterrupter(e) => write!(f, "failed to enable interrupter: {}", e), + SetModeration(e) => write!(f, "failed to set interrupter moderation: {}", e), + SetupEventRing(e) => write!(f, "failed to setup event ring: {}", e), + SetEventHandlerBusy(e) => write!(f, "failed to set event handler busy: {}", e), + StartProvider => write!(f, "failed to start backend provider"), + RingDoorbell(e) => write!(f, "failed to ring doorbell: {}", e), + CreateCommandRingController(e) => { + write!(f, "failed to create command ring controller: {}", e) + } + } + } +} + +/// xHCI controller implementation. +pub struct Xhci { + fail_handle: Arc<FailHandle>, + regs: XhciRegs, + interrupter: Arc<Mutex<Interrupter>>, + command_ring_controller: Arc<CommandRingController>, + device_slots: DeviceSlots, + // resample handler and device provider only lives on EventLoop to handle corresponding events. + // By design, event loop only hold weak reference. We need to keep a strong reference here to + // keep it alive. + #[allow(dead_code)] + intr_resample_handler: Arc<IntrResampleHandler>, + #[allow(dead_code)] + device_provider: HostBackendDeviceProvider, +} + +impl Xhci { + /// Create a new xHCI controller. + pub fn new( + fail_handle: Arc<FailHandle>, + mem: GuestMemory, + device_provider: HostBackendDeviceProvider, + irq_evt: EventFd, + irq_resample_evt: EventFd, + regs: XhciRegs, + ) -> Result<Arc<Self>> { + let (event_loop, _join_handle) = + EventLoop::start(Some(fail_handle.clone())).map_err(Error::StartEventLoop)?; + let interrupter = Arc::new(Mutex::new(Interrupter::new(mem.clone(), irq_evt, ®s))); + let event_loop = Arc::new(event_loop); + let intr_resample_handler = + IntrResampleHandler::start(&event_loop, interrupter.clone(), irq_resample_evt) + .ok_or(Error::StartResampleHandler)?; + let hub = Arc::new(UsbHub::new(®s, interrupter.clone())); + + let mut device_provider = device_provider; + device_provider + .start(fail_handle.clone(), event_loop.clone(), hub.clone()) + .map_err(|_| Error::StartProvider)?; + + let device_slots = DeviceSlots::new( + fail_handle.clone(), + regs.dcbaap.clone(), + hub.clone(), + interrupter.clone(), + event_loop.clone(), + mem.clone(), + ); + let command_ring_controller = CommandRingController::new( + mem.clone(), + event_loop.clone(), + device_slots.clone(), + interrupter.clone(), + ) + .map_err(Error::CreateCommandRingController)?; + let xhci = Arc::new(Xhci { + fail_handle, + regs, + intr_resample_handler, + interrupter, + command_ring_controller, + device_slots, + device_provider, + }); + Self::init_reg_callbacks(&xhci); + Ok(xhci) + } + + fn init_reg_callbacks(xhci: &Arc<Xhci>) { + // All the callbacks will hold a weak reference to avoid memory leak. Thos weak upgrade + // should never fail. + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.usbcmd.set_write_cb(move |val: u32| { + // All the weak reference upgrade should never fail. xhci hold reference to the + // registers, callback won't be invoked if xhci is gone. + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.usbcmd_callback(val); + xhci.handle_register_callback_result(r, 0) + }); + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.crcr.set_write_cb(move |val: u64| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.crcr_callback(val); + xhci.handle_register_callback_result(r, 0) + }); + + for i in 0..xhci.regs.portsc.len() { + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.portsc[i].set_write_cb(move |val: u32| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.portsc_callback(i as u32, val); + xhci.handle_register_callback_result(r, 0) + }); + } + + for i in 0..xhci.regs.doorbells.len() { + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.doorbells[i].set_write_cb(move |val: u32| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.doorbell_callback(i as u32, val); + xhci.handle_register_callback_result(r, ()); + val + }); + } + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.iman.set_write_cb(move |val: u32| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.iman_callback(val); + xhci.handle_register_callback_result(r, ()); + val + }); + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.imod.set_write_cb(move |val: u32| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.imod_callback(val); + xhci.handle_register_callback_result(r, ()); + val + }); + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.erstsz.set_write_cb(move |val: u32| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.erstsz_callback(val); + xhci.handle_register_callback_result(r, ()); + val + }); + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.erstba.set_write_cb(move |val: u64| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.erstba_callback(val); + xhci.handle_register_callback_result(r, ()); + val + }); + + let xhci_weak = Arc::downgrade(xhci); + xhci.regs.erdp.set_write_cb(move |val: u64| { + let xhci = xhci_weak.upgrade().unwrap(); + let r = xhci.erdp_callback(val); + xhci.handle_register_callback_result(r, ()); + val + }); + } + + fn handle_register_callback_result<T>(&self, r: Result<T>, t: T) -> T { + match r { + Ok(v) => v, + Err(e) => { + error!("xhci controller failed: {}", e); + self.fail_handle.fail(); + t + } + } + } + + // Callback for usbcmd register write. + fn usbcmd_callback(&self, value: u32) -> Result<u32> { + if (value & USB_CMD_RESET) > 0 { + usb_debug!("xhci_controller: reset controller"); + self.reset()?; + return Ok(value & (!USB_CMD_RESET)); + } + + if (value & USB_CMD_RUNSTOP) > 0 { + usb_debug!("xhci_controller: clear halt bits"); + self.regs.usbsts.clear_bits(USB_STS_HALTED); + } else { + usb_debug!("xhci_controller: halt device"); + self.halt()?; + self.regs.crcr.clear_bits(CRCR_COMMAND_RING_RUNNING); + } + + // Enable interrupter if needed. + let enabled = (value & USB_CMD_INTERRUPTER_ENABLE) > 0 + && (self.regs.iman.get_value() & IMAN_INTERRUPT_ENABLE) > 0; + usb_debug!("xhci_controller: interrupter enable?: {}", enabled); + self.interrupter + .lock() + .set_enabled(enabled) + .map_err(Error::EnableInterrupter)?; + Ok(value) + } + + // Callback for crcr register write. + fn crcr_callback(&self, value: u64) -> Result<u64> { + usb_debug!("xhci_controller: write to crcr {:x}", value); + let value = if (self.regs.crcr.get_value() & CRCR_COMMAND_RING_RUNNING) == 0 { + self.command_ring_controller + .set_dequeue_pointer(GuestAddress(value & CRCR_COMMAND_RING_POINTER)); + self.command_ring_controller + .set_consumer_cycle_state((value & CRCR_RING_CYCLE_STATE) > 0); + value + } else { + error!("Write to crcr while command ring is running"); + self.regs.crcr.get_value() + }; + Ok(value) + } + + // Callback for portsc register write. + fn portsc_callback(&self, index: u32, value: u32) -> Result<u32> { + let mut value = value; + usb_debug!( + "xhci_controller: write to portsc index {} value {:x}", + index, + value + ); + // xHCI spec 4.19.5. Note: we might want to change this logic if we support USB 3.0. + if (value & PORTSC_PORT_RESET) > 0 || (value & PORTSC_WARM_PORT_RESET) > 0 { + // Libusb onlys support blocking call to reset and "usually incurs a noticeable + // delay.". We are faking a reset now. + value &= !PORTSC_PORT_LINK_STATE_MASK; + value &= !PORTSC_PORT_RESET; + value |= PORTSC_PORT_ENABLED; + value |= PORTSC_PORT_RESET_CHANGE; + self.interrupter + .lock() + .send_port_status_change_trb((index + 1) as u8) + .map_err(Error::SendInterrupt)?; + } + Ok(value) + } + + // Callback for doorbell register write. + fn doorbell_callback(&self, index: u32, value: u32) -> Result<()> { + usb_debug!( + "xhci_controller: write to doorbell index {} value {:x}", + index, + value + ); + let target = (value & DOORBELL_TARGET) as u8; + let stream_id: u16 = (value >> DOORBELL_STREAM_ID_OFFSET) as u16; + if (self.regs.usbcmd.get_value() & USB_CMD_RUNSTOP) > 0 { + // First doorbell is for command ring. + if index == 0 { + if target != 0 || stream_id != 0 { + return Ok(()); + } + usb_debug!("doorbell to command ring"); + self.regs.crcr.set_bits(CRCR_COMMAND_RING_RUNNING); + self.command_ring_controller.start(); + } else { + usb_debug!("doorbell to device slot"); + self.device_slots + .slot(index as u8) + .ok_or(Error::GetDeviceSlot(index as u8))? + .ring_doorbell(target, stream_id) + .map_err(Error::RingDoorbell)?; + } + } + Ok(()) + } + + // Callback for iman register write. + fn iman_callback(&self, value: u32) -> Result<()> { + usb_debug!("xhci_controller: write to iman {:x}", value); + let enabled = ((value & IMAN_INTERRUPT_ENABLE) > 0) + && ((self.regs.usbcmd.get_value() & USB_CMD_INTERRUPTER_ENABLE) > 0); + self.interrupter + .lock() + .set_enabled(enabled) + .map_err(Error::EnableInterrupter) + } + + // Callback for imod register write. + fn imod_callback(&self, value: u32) -> Result<()> { + usb_debug!("xhci_controller: write to imod {:x}", value); + self.interrupter + .lock() + .set_moderation( + (value & IMOD_INTERRUPT_MODERATION_INTERVAL) as u16, + (value >> IMOD_INTERRUPT_MODERATION_COUNTER_OFFSET) as u16, + ) + .map_err(Error::SetModeration) + } + + // Callback for erstsz register write. + fn erstsz_callback(&self, value: u32) -> Result<()> { + usb_debug!("xhci_controller: write to erstz {:x}", value); + self.interrupter + .lock() + .set_event_ring_seg_table_size((value & ERSTSZ_SEGMENT_TABLE_SIZE) as u16) + .map_err(Error::SetupEventRing) + } + + // Callback for erstba register write. + fn erstba_callback(&self, value: u64) -> Result<()> { + usb_debug!("xhci_controller: write to erstba {:x}", value); + self.interrupter + .lock() + .set_event_ring_seg_table_base_addr(GuestAddress( + value & ERSTBA_SEGMENT_TABLE_BASE_ADDRESS, + )) + .map_err(Error::SetupEventRing) + } + + // Callback for erdp register write. + fn erdp_callback(&self, value: u64) -> Result<()> { + usb_debug!("xhci_controller: write to erdp {:x}", value); + let mut interrupter = self.interrupter.lock(); + interrupter + .set_event_ring_dequeue_pointer(GuestAddress(value & ERDP_EVENT_RING_DEQUEUE_POINTER)) + .map_err(Error::SetupEventRing)?; + interrupter + .set_event_handler_busy((value & ERDP_EVENT_HANDLER_BUSY) > 0) + .map_err(Error::SetEventHandlerBusy) + } + + fn reset(&self) -> Result<()> { + self.regs.usbsts.set_bits(USB_STS_CONTROLLER_NOT_READY); + let usbsts = self.regs.usbsts.clone(); + self.device_slots.stop_all_and_reset(move || { + usbsts.clear_bits(USB_STS_CONTROLLER_NOT_READY); + }); + Ok(()) + } + + fn halt(&self) -> Result<()> { + let usbsts = self.regs.usbsts.clone(); + self.device_slots + .stop_all(RingBufferStopCallback::new(move || { + usbsts.set_bits(USB_STS_HALTED); + })); + Ok(()) + } +} diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs new file mode 100644 index 0000000..f920704 --- /dev/null +++ b/devices/src/usb/xhci/xhci_controller.rs @@ -0,0 +1,277 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use pci::{ + PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, + PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass, +}; +use register_space::{Register, RegisterSpace}; +use resources::SystemAllocator; +use std::mem; +use std::os::unix::io::RawFd; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use sys_util::{EventFd, GuestMemory}; +use usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider; +use usb::xhci::xhci::Xhci; +use usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider; +use usb::xhci::xhci_regs::{init_xhci_mmio_space_and_regs, XhciRegs}; +use utils::FailHandle; + +const XHCI_BAR0_SIZE: u64 = 0x10000; + +#[derive(Clone, Copy)] +enum UsbControllerProgrammingInterface { + Usb3HostController = 0x30, +} + +impl PciProgrammingInterface for UsbControllerProgrammingInterface { + fn get_register_value(&self) -> u8 { + *self as u8 + } +} + +/// Use this handle to fail xhci controller. +pub struct XhciFailHandle { + usbcmd: Register<u32>, + usbsts: Register<u32>, + xhci_failed: AtomicBool, +} + +impl XhciFailHandle { + pub fn new(regs: &XhciRegs) -> XhciFailHandle { + XhciFailHandle { + usbcmd: regs.usbcmd.clone(), + usbsts: regs.usbsts.clone(), + xhci_failed: AtomicBool::new(false), + } + } +} + +impl FailHandle for XhciFailHandle { + /// Fail this controller. Will set related registers and flip failed bool. + fn fail(&self) { + // set run/stop to stop. + const USBCMD_STOPPED: u32 = 0; + // Set host system error bit. + const USBSTS_HSE: u32 = 1 << 2; + self.usbcmd.set_value(USBCMD_STOPPED); + self.usbsts.set_value(USBSTS_HSE); + + self.xhci_failed.store(true, Ordering::SeqCst); + error!("xhci controller stopped working"); + } + + /// Returns true if xhci is already failed. + fn failed(&self) -> bool { + self.xhci_failed.load(Ordering::SeqCst) + } +} + +// Xhci controller should be created with backend device provider. Then irq should be assigned +// before initialized. We are not making `failed` as a state here to optimize performance. Cause we +// need to set failed in other threads. +enum XhciControllerState { + Unknown, + Created { + device_provider: HostBackendDeviceProvider, + }, + IrqAssigned { + device_provider: HostBackendDeviceProvider, + irq_evt: EventFd, + irq_resample_evt: EventFd, + }, + Initialized { + mmio: RegisterSpace, + // Xhci init could fail. + #[allow(dead_code)] + xhci: Option<Arc<Xhci>>, + fail_handle: Arc<FailHandle>, + }, +} + +/// xHCI PCI interface implementation. +pub struct XhciController { + config_regs: PciConfiguration, + mem: GuestMemory, + bar0: u64, // bar0 in config_regs will be changed by guest. Not sure why. + state: XhciControllerState, +} + +impl XhciController { + /// Create new xhci controller. + pub fn new(mem: GuestMemory, usb_provider: HostBackendDeviceProvider) -> Self { + let config_regs = PciConfiguration::new( + 0x01b73, // fresco logic, (google = 0x1ae0) + 0x1000, // fresco logic pdk. This chip has broken msi. See kernel xhci-pci.c + PciClassCode::SerialBusController, + &PciSerialBusSubClass::USB, + Some(&UsbControllerProgrammingInterface::Usb3HostController), + PciHeaderType::Device, + 0, + 0, + ); + XhciController { + config_regs, + mem, + bar0: 0, + state: XhciControllerState::Created { + device_provider: usb_provider, + }, + } + } + + /// Init xhci controller when it's forked. + pub fn init_when_forked(&mut self) { + match mem::replace(&mut self.state, XhciControllerState::Unknown) { + XhciControllerState::IrqAssigned { + device_provider, + irq_evt, + irq_resample_evt, + } => { + let (mmio, regs) = init_xhci_mmio_space_and_regs(); + let fail_handle: Arc<FailHandle> = Arc::new(XhciFailHandle::new(®s)); + let xhci = match Xhci::new( + fail_handle.clone(), + self.mem.clone(), + device_provider, + irq_evt, + irq_resample_evt, + regs, + ) { + Ok(xhci) => Some(xhci), + Err(_) => { + error!("fail to init xhci"); + fail_handle.fail(); + return; + } + }; + + self.state = XhciControllerState::Initialized { + mmio, + xhci, + fail_handle, + } + } + _ => { + error!("xhci controller is in a wrong state"); + return; + } + } + } +} + +impl PciDevice for XhciController { + fn debug_label(&self) -> String { + "xhci controller".to_owned() + } + + fn keep_fds(&self) -> Vec<RawFd> { + match self.state { + XhciControllerState::Created { + ref device_provider, + } => device_provider.keep_fds(), + _ => { + error!("xhci controller is in a wrong state"); + vec![] + } + } + } + + fn assign_irq( + &mut self, + irq_evt: EventFd, + irq_resample_evt: EventFd, + irq_num: u32, + irq_pin: PciInterruptPin, + ) { + match mem::replace(&mut self.state, XhciControllerState::Unknown) { + XhciControllerState::Created { device_provider } => { + self.config_regs.set_irq(irq_num as u8, irq_pin); + self.state = XhciControllerState::IrqAssigned { + device_provider, + irq_evt, + irq_resample_evt, + } + } + _ => { + error!("xhci controller is in a wrong state"); + return; + } + } + } + + fn allocate_io_bars( + &mut self, + resources: &mut SystemAllocator, + ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> { + // xHCI spec 5.2.1. + let bar0_addr = resources + .allocate_mmio_addresses(XHCI_BAR0_SIZE) + .ok_or(PciDeviceError::IoAllocationFailed(XHCI_BAR0_SIZE))?; + let bar0_config = PciBarConfiguration::default() + .set_register_index(0) + .set_address(bar0_addr) + .set_size(XHCI_BAR0_SIZE); + self.config_regs + .add_pci_bar(&bar0_config) + .map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr, e))?; + self.bar0 = bar0_addr; + Ok(vec![(bar0_addr, XHCI_BAR0_SIZE)]) + } + + fn config_registers(&self) -> &PciConfiguration { + &self.config_regs + } + + fn config_registers_mut(&mut self) -> &mut PciConfiguration { + &mut self.config_regs + } + + fn read_bar(&mut self, addr: u64, data: &mut [u8]) { + let bar0 = self.bar0; + if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE { + return; + } + match self.state { + XhciControllerState::Initialized { + ref mmio, + xhci: _, + fail_handle: _, + } => { + // Read bar would still work even if it's already failed. + mmio.read(addr - bar0, data); + } + _ => { + error!("xhci controller is in a wrong state"); + return; + } + } + } + + fn write_bar(&mut self, addr: u64, data: &[u8]) { + let bar0 = self.bar0; + if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE { + return; + } + match self.state { + XhciControllerState::Initialized { + ref mmio, + xhci: _, + ref fail_handle, + } => { + if !fail_handle.failed() { + mmio.write(addr - bar0, data); + } + } + _ => { + error!("xhci controller is in a wrong state"); + return; + } + } + } + fn on_device_sandboxed(&mut self) { + self.init_when_forked(); + } +} diff --git a/seccomp/arm/xhci.policy b/seccomp/arm/xhci.policy new file mode 100644 index 0000000..0c69bc0 --- /dev/null +++ b/seccomp/arm/xhci.policy @@ -0,0 +1,40 @@ +# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +openat: 1 +@include /usr/share/policy/crosvm/common_device.policy + +stat64: 1 +fcntl64: 1 +lstat64: 1 +readlinkat: 1 +getdents64: 1 +getrandom: 1 +name_to_handle_at: 1 +access: 1 +gettid: 1 +clock_gettime: 1 +timerfd_create: 1 +getsockname: 1 +pipe: 1 +setsockopt: 1 +bind: 1 +fcntl: 1 +socket: arg0 == AF_NETLINK +stat: 1 +uname: 1 +# The following ioctls are: +# 0x4004550d == USBDEVFS_REAPURBNDELAY32 +# 0x550b == USBDEVFS_DISCARDURB +# 0x8004550f == USBDEVFS_CLAIMINTERFACE +# 0x80045510 == USBDEVFS_RELEASEINTERFACE +# 0x8004551a == USBDEVFS_GET_CAPABILITIES +# 0x802c550a == USBDEVFS_SUBMITURB +# 0xc0105500 == USBDEVFS_CONTROL +ioctl: arg1 == 0xc0105500 || arg1 == 0x802c550a || arg1 == 0x8004551a || arg1 == 0x4004550d || arg1 == 0x8004550f || arg1 == 0x80045510 || arg1 == 0x550b +fstat: 1 +sigaltstack: 1 +recvmsg: 1 +getrandom: 1 +getdents: 1 diff --git a/seccomp/x86_64/xhci.policy b/seccomp/x86_64/xhci.policy new file mode 100644 index 0000000..0f133d9 --- /dev/null +++ b/seccomp/x86_64/xhci.policy @@ -0,0 +1,38 @@ +# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# xhci need "openat" to enumerate device. "openat" is disabled in comman_device policy. +openat: 1 +@include /usr/share/policy/crosvm/common_device.policy + +lstat: 1 +gettid: 1 +readlinkat: 1 +timerfd_create: 1 +name_to_handle_at: 1 +access: 1 +timerfd_create: 1 +getsockname: 1 +pipe: 1 +setsockopt: 1 +bind: 1 +fcntl: 1 +socket: arg0 == AF_NETLINK +stat: 1 +uname: 1 +# The following ioctls are: +# 0x4008550d == USBDEVFS_REAPURBNDELAY +# 0x41045508 == USBDEVFS_GETDRIVER +# 0x550b == USBDEVFS_DISCARDURB +# 0x8004550f == USBDEVFS_CLAIMINTERFACE +# 0x80045510 == USBDEVFS_RELEASEINTERFACE +# 0x8004551a == USBDEVFS_GET_CAPABILITIES +# 0x8038550a == USBDEVFS_SUBMITURB +# 0xc0185500 == USBDEVFS_CONTROL +ioctl: arg1 == 0xc0185500 || arg1 == 0x41045508 || arg1 == 0x8004550f || arg1 == 0x4008550d || arg1 == 0x8004551a || arg1 == 0x550b || arg1 == 0x80045510 || arg1 == 0x8038550a +fstat: 1 +sigaltstack: 1 +recvmsg: 1 +getrandom: 1 +getdents: 1 diff --git a/src/linux.rs b/src/linux.rs index 37910f4..4674241 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -25,7 +25,7 @@ use libc::{self, c_int, gid_t, uid_t}; use audio_streams::DummyStreamSource; use byteorder::{ByteOrder, LittleEndian}; use devices::virtio::{self, VirtioDevice}; -use devices::{self, PciDevice, VirtioPciDevice}; +use devices::{self, HostBackendDeviceProvider, PciDevice, VirtioPciDevice, XhciController}; use io_jail::{self, Minijail}; use kvm::*; use libcras::CrasClient; @@ -44,7 +44,7 @@ use sys_util::{ #[cfg(feature = "gpu-forward")] use sys_util::{GuestAddress, MemoryMapping, Protection}; use vhost; -use vm_control::{VmRequest, VmResponse, VmRunMode}; +use vm_control::{UsbControlSocket, VmRequest, VmResponse, VmRunMode}; use crate::{Config, DiskOption, TouchDeviceOption}; @@ -80,6 +80,7 @@ pub enum Error { CreateTapDevice(NetError), CreateTimerFd(sys_util::Error), CreateTpmStorage(PathBuf, io::Error), + CreateUsbProvider(devices::usb::host_backend::error::Error), DetectImageType(qcow::Error), DeviceJail(io_jail::Error), DevicePivotRoot(io_jail::Error), @@ -151,6 +152,7 @@ impl Display for Error { CreateTpmStorage(p, e) => { write!(f, "failed to create tpm storage dir {}: {}", p.display(), e) } + CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e), DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e), DeviceJail(e) => write!(f, "failed to jail device: {}", e), DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e), @@ -765,6 +767,7 @@ fn create_devices( wayland_device_socket: UnixSeqpacket, balloon_device_socket: UnixSeqpacket, disk_device_sockets: &mut Vec<UnixSeqpacket>, + usb_provider: HostBackendDeviceProvider, ) -> DeviceResult<Vec<(Box<PciDevice>, Option<Minijail>)>> { let stubs = create_virtio_devices( &cfg, @@ -802,6 +805,9 @@ fn create_devices( simple_jail(&cfg, "null_audio_device.policy")?, )); } + // Create xhci controller. + let usb_controller = Box::new(XhciController::new(mem.clone(), usb_provider)); + pci_devices.push((usb_controller, simple_jail(&cfg, "xhci.policy")?)); Ok(pci_devices) } @@ -1068,6 +1074,8 @@ pub fn run_config(cfg: Config) -> Result<()> { info!("crosvm entering multiprocess mode"); } + let (usb_control_socket, usb_provider) = + HostBackendDeviceProvider::new().map_err(|e| Error::CreateUsbProvider(e))?; // Masking signals is inherently dangerous, since this can persist across clones/execs. Do this // before any jailed devices have been spawned, so that we can catch any of them that fail very // quickly. @@ -1130,6 +1138,7 @@ pub fn run_config(cfg: Config) -> Result<()> { wayland_device_socket, balloon_device_socket, &mut disk_device_sockets, + usb_provider, ) }) .map_err(Error::BuildVm)?; @@ -1174,6 +1183,7 @@ pub fn run_config(cfg: Config) -> Result<()> { control_sockets, balloon_host_socket, &disk_host_sockets, + usb_control_socket, sigchld_fd, _render_node_host, sandbox, @@ -1186,6 +1196,7 @@ fn run_control( mut control_sockets: Vec<MsgSocket<VmResponse, VmRequest>>, balloon_host_socket: UnixSeqpacket, disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>], + usb_control_socket: UsbControlSocket, sigchld_fd: SignalFd, _render_node_host: RenderNodeHost, sandbox: bool, @@ -1465,6 +1476,7 @@ fn run_control( &mut run_mode_opt, &balloon_host_socket, disk_host_sockets, + &usb_control_socket, ); if let Err(e) = socket.send(&response) { error!("failed to send VmResponse: {}", e); diff --git a/src/main.rs b/src/main.rs index a0cc1c2..d48b166 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,18 +43,22 @@ pub mod panic_hook; #[cfg(feature = "plugin")] pub mod plugin; -use std::fs::OpenOptions; +use std::fmt; +use std::fs::{File, OpenOptions}; use std::net; -use std::os::unix::io::RawFd; -use std::path::PathBuf; +use std::num::ParseIntError; +use std::os::unix::io::{FromRawFd, RawFd}; +use std::path::{Path, PathBuf}; use std::string::String; use std::thread::sleep; use std::time::Duration; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; use qcow::QcowFile; -use sys_util::{getpid, kill_process_group, net::UnixSeqpacket, reap_child, syslog}; -use vm_control::{VmRequest, VmResponse}; +use sys_util::{ + getpid, kill_process_group, net::UnixSeqpacket, reap_child, syslog, validate_raw_fd, +}; +use vm_control::{MaybeOwnedFd, UsbControlCommand, UsbControlResult, VmRequest, VmResponse}; use crate::argument::{print_help, set_arguments, Argument}; @@ -777,8 +781,11 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { } } -fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> { - let mut return_result = Ok(()); +fn handle_request( + request: &VmRequest, + args: std::env::Args, +) -> std::result::Result<VmResponse, ()> { + let mut return_result = Err(()); for socket_path in args { match UnixSeqpacket::connect(&socket_path) { Ok(s) => { @@ -792,7 +799,7 @@ fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result continue; } match socket.recv() { - Ok(response) => info!("request response was {}", response), + Ok(response) => return_result = Ok(response), Err(e) => { error!( "failed to send request to socket at2 '{}': {}", @@ -813,6 +820,12 @@ fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result return_result } +fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> { + let response = handle_request(request, args)?; + info!("request response was {}", response); + Ok(()) +} + fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> { if args.len() == 0 { print_help("crosvm stop", "VM_SOCKET...", &[]); @@ -930,13 +943,193 @@ fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> { vms_request(&request, args) } +enum ModifyUsbError { + ArgMissing(&'static str), + ArgParse(&'static str, String), + ArgParseInt(&'static str, String, ParseIntError), + FailedFdValidate(sys_util::Error), + PathDoesNotExist(PathBuf), + SocketFailed, + UnexpectedResponse(VmResponse), + UnknownCommand(String), + UsbControl(UsbControlResult), +} + +impl fmt::Display for ModifyUsbError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::ModifyUsbError::*; + + match self { + ArgMissing(a) => write!(f, "argument missing: {}", a), + ArgParse(name, value) => { + write!(f, "failed to parse argument {} value `{}`", name, value) + } + ArgParseInt(name, value, e) => write!( + f, + "failed to parse integer argument {} value `{}`: {}", + name, value, e + ), + FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e), + PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()), + SocketFailed => write!(f, "socket failed"), + UnexpectedResponse(r) => write!(f, "unexpected response: {}", r), + UnknownCommand(c) => write!(f, "unknown command: `{}`", c), + UsbControl(e) => write!(f, "{}", e), + } + } +} + +type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>; + +fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> { + debug!("parse_bus_id_addr: {}", v); + let mut ids = v.split(":"); + match (ids.next(), ids.next(), ids.next(), ids.next()) { + (Some(bus_id), Some(addr), Some(vid), Some(pid)) => { + let bus_id = bus_id + .parse::<u8>() + .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?; + let addr = addr + .parse::<u8>() + .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?; + let vid = u16::from_str_radix(&vid, 16) + .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?; + let pid = u16::from_str_radix(&pid, 16) + .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?; + Ok((bus_id, addr, vid, pid)) + } + _ => Err(ModifyUsbError::ArgParse( + "BUS_ID_ADDR_BUS_NUM_DEV_NUM", + v.to_owned(), + )), + } +} + +fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> { + if !path.exists() { + return Err(ModifyUsbError::PathDoesNotExist(path.to_owned())); + } + let raw_fd = path + .file_name() + .and_then(|fd_osstr| fd_osstr.to_str()) + .map_or( + Err(ModifyUsbError::ArgParse( + "USB_DEVICE_PATH", + path.to_string_lossy().into_owned(), + )), + |fd_str| { + fd_str.parse::<libc::c_int>().map_err(|e| { + ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e) + }) + }, + )?; + validate_raw_fd(raw_fd).map_err(|e| ModifyUsbError::FailedFdValidate(e)) +} + +fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> { + let val = args + .next() + .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?; + let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?; + let dev_path = PathBuf::from( + args.next() + .ok_or(ModifyUsbError::ArgMissing("usb device path"))?, + ); + let usb_file: Option<File> = if dev_path == Path::new("-") { + None + } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) { + // Special case '/proc/self/fd/*' paths. The FD is already open, just use it. + // Safe because we will validate |raw_fd|. + Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) }) + } else { + Some( + OpenOptions::new() + .read(true) + .write(true) + .open(&dev_path) + .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?, + ) + }; + + let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice { + bus, + addr, + vid, + pid, + fd: usb_file.map(|f| MaybeOwnedFd::Owned(f)), + }); + let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?; + match response { + VmResponse::UsbResponse(usb_resp) => Ok(usb_resp), + r => Err(ModifyUsbError::UnexpectedResponse(r)), + } +} + +fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> { + let port: u8 = args + .next() + .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| { + p.parse::<u8>() + .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e)) + })?; + let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port }); + let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?; + match response { + VmResponse::UsbResponse(usb_resp) => Ok(usb_resp), + r => Err(ModifyUsbError::UnexpectedResponse(r)), + } +} + +fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> { + let port: u8 = args + .next() + .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| { + p.parse::<u8>() + .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e)) + })?; + let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { port }); + let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?; + match response { + VmResponse::UsbResponse(usb_resp) => Ok(usb_resp), + r => Err(ModifyUsbError::UnexpectedResponse(r)), + } +} + +fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> { + if args.len() < 3 { + print_help("crosvm usb", + "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list PORT] VM_SOCKET...", &[]); + return Err(()); + } + + // This unwrap will not panic because of the above length check. + let command = args.next().unwrap(); + let result = match command.as_ref() { + "attach" => usb_attach(args), + "detach" => usb_detach(args), + "list" => usb_list(args), + other => Err(ModifyUsbError::UnknownCommand(other.to_owned())), + }; + match result { + Ok(response) => { + println!("{}", response); + Ok(()) + } + Err(e) => { + println!("error {}", e); + Err(()) + } + } +} + fn print_usage() { print_help("crosvm", "[stop|run]", &[]); println!("Commands:"); println!(" stop - Stops crosvm instances via their control sockets."); println!(" run - Start a new crosvm instance."); println!(" create_qcow2 - Create a new qcow2 disk image file."); - println!(" disk - Manage attached virtual disk devices.") + println!(" disk - Manage attached virtual disk devices."); + println!(" usb - Manage attached virtual USB devices."); } fn crosvm_main() -> std::result::Result<(), ()> { @@ -966,6 +1159,7 @@ fn crosvm_main() -> std::result::Result<(), ()> { Some("balloon") => balloon_vms(args), Some("create_qcow2") => create_qcow2(args), Some("disk") => disk_cmd(args), + Some("usb") => modify_usb(args), Some(c) => { println!("invalid subcommand: {:?}", c); print_usage(); diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs index ee24b61..c6115ad 100644 --- a/vm_control/src/lib.rs +++ b/vm_control/src/lib.rs @@ -220,6 +220,7 @@ impl VmRequest { run_mode: &mut Option<VmRunMode>, balloon_host_socket: &UnixSeqpacket, disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>], + usb_control_socket: &UsbControlSocket, ) -> VmResponse { match *self { VmRequest::Exit => { @@ -316,8 +317,18 @@ impl VmRequest { } } VmRequest::UsbCommand(ref cmd) => { - error!("not implemented yet"); - VmResponse::Ok + let res = usb_control_socket.send(cmd); + if let Err(e) = res { + error!("fail to send command to usb control socket: {}", e); + return VmResponse::Err(SysError::new(EIO)); + } + match usb_control_socket.recv() { + Ok(response) => VmResponse::UsbResponse(response), + Err(e) => { + error!("fail to recv command from usb control socket: {}", e); + return VmResponse::Err(SysError::new(EIO)); + } + } } } } |