summary refs log tree commit diff
path: root/devices/src/usb
diff options
context:
space:
mode:
authorJingkui Wang <jkwang@google.com>2019-03-07 22:06:11 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-03-13 21:04:58 -0700
commit115fe4e233cd9263c0152bd37db1d7537a109400 (patch)
treef52c997b7b60557b47f3550d4ccf3fb871b33bd6 /devices/src/usb
parente9f5cae48f0707200331241a02d0c7b218949179 (diff)
downloadcrosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar.gz
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar.bz2
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar.lz
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar.xz
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.tar.zst
crosvm-115fe4e233cd9263c0152bd37db1d7537a109400.zip
usb: add interrupter and resample handler for xhci
Interrupter owns event ring and send interrupt. Resample handler will
reassert interrupt when resample happens.

CQ-DEPEND=CL:1510815
BUG=chromium:831850
TEST=local build

Change-Id: If2c4349e50d0ab275f29dfa48b0860c86f0c8ea3
Reviewed-on: https://chromium-review.googlesource.com/1510816
Commit-Ready: Jingkui Wang <jkwang@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Jingkui Wang <jkwang@google.com>
Diffstat (limited to 'devices/src/usb')
-rw-r--r--devices/src/usb/xhci/interrupter.rs208
-rw-r--r--devices/src/usb/xhci/intr_resample_handler.rs64
-rw-r--r--devices/src/usb/xhci/mod.rs2
3 files changed, 274 insertions, 0 deletions
diff --git a/devices/src/usb/xhci/interrupter.rs b/devices/src/usb/xhci/interrupter.rs
new file mode 100644
index 0000000..2e0089b
--- /dev/null
+++ b/devices/src/usb/xhci/interrupter.rs
@@ -0,0 +1,208 @@
+// 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::event_ring::{Error as EventRingError, EventRing};
+use super::xhci_abi::{
+    CommandCompletionEventTrb, Error as TrbError, PortStatusChangeEventTrb, TransferEventTrb, Trb,
+    TrbCast, TrbCompletionCode, TrbType,
+};
+use super::xhci_regs::*;
+use register_space::Register;
+use std::fmt::{self, Display};
+use sys_util::{Error as SysError, EventFd, GuestAddress, GuestMemory};
+
+#[derive(Debug)]
+pub enum Error {
+    CastTrb(TrbError),
+    AddEvent(EventRingError),
+    SetSegTableSize(EventRingError),
+    SetSegTableBaseAddr(EventRingError),
+    SendInterrupt(SysError),
+}
+
+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 {
+            CastTrb(e) => write!(f, "cannot cast trb: {}", e),
+            AddEvent(e) => write!(f, "cannot add event: {}", e),
+            SetSegTableSize(e) => write!(f, "cannot set seg table size: {}", e),
+            SetSegTableBaseAddr(e) => write!(f, "cannot set seg table base addr: {}", e),
+            SendInterrupt(e) => write!(f, "cannot send interrupt: {}", e),
+        }
+    }
+}
+
+/// See spec 4.17 for interrupters. Controller can send an event back to guest kernel driver
+/// through interrupter.
+pub struct Interrupter {
+    interrupt_fd: EventFd,
+    usbsts: Register<u32>,
+    iman: Register<u32>,
+    erdp: Register<u64>,
+    event_handler_busy: bool,
+    enabled: bool,
+    pending: bool,
+    moderation_interval: u16,
+    moderation_counter: u16,
+    event_ring: EventRing,
+}
+
+impl Interrupter {
+    /// Create a new interrupter.
+    pub fn new(mem: GuestMemory, irq_evt: EventFd, regs: &XhciRegs) -> Self {
+        Interrupter {
+            interrupt_fd: irq_evt,
+            usbsts: regs.usbsts.clone(),
+            iman: regs.iman.clone(),
+            erdp: regs.erdp.clone(),
+            event_handler_busy: false,
+            enabled: false,
+            pending: false,
+            moderation_interval: 0,
+            moderation_counter: 0,
+            event_ring: EventRing::new(mem),
+        }
+    }
+
+    /// Returns true if event ring is empty.
+    pub fn event_ring_is_empty(&self) -> bool {
+        self.event_ring.is_empty()
+    }
+
+    /// Add event to event ring.
+    fn add_event(&mut self, trb: Trb) -> Result<()> {
+        self.event_ring.add_event(trb).map_err(Error::AddEvent)?;
+        self.pending = true;
+        self.interrupt_if_needed()
+    }
+
+    /// Send port status change trb for port.
+    pub fn send_port_status_change_trb(&mut self, port_id: u8) -> Result<()> {
+        let mut trb = Trb::new();
+        {
+            let psctrb = trb
+                .cast_mut::<PortStatusChangeEventTrb>()
+                .map_err(Error::CastTrb)?;
+            psctrb.set_port_id(port_id);
+            psctrb.set_completion_code(TrbCompletionCode::Success as u8);
+            psctrb.set_trb_type(TrbType::PortStatusChangeEvent as u8);
+        }
+        self.add_event(trb)
+    }
+
+    /// Send command completion trb.
+    pub fn send_command_completion_trb(
+        &mut self,
+        completion_code: TrbCompletionCode,
+        slot_id: u8,
+        trb_addr: GuestAddress,
+    ) -> Result<()> {
+        let mut trb = Trb::new();
+        {
+            let ctrb = trb
+                .cast_mut::<CommandCompletionEventTrb>()
+                .map_err(Error::CastTrb)?;
+            ctrb.set_trb_pointer(trb_addr.0);
+            ctrb.set_command_completion_parameter(0);
+            ctrb.set_completion_code(completion_code as u8);
+            ctrb.set_trb_type(TrbType::CommandCompletionEvent as u8);
+            ctrb.set_vf_id(0);
+            ctrb.set_slot_id(slot_id);
+        }
+        self.add_event(trb)
+    }
+
+    /// Send transfer event trb.
+    pub fn send_transfer_event_trb(
+        &mut self,
+        completion_code: TrbCompletionCode,
+        trb_pointer: u64,
+        transfer_length: u32,
+        event_data: bool,
+        slot_id: u8,
+        endpoint_id: u8,
+    ) -> Result<()> {
+        let mut trb = Trb::new();
+        {
+            let event_trb = trb.cast_mut::<TransferEventTrb>().map_err(Error::CastTrb)?;
+            event_trb.set_trb_pointer(trb_pointer);
+            event_trb.set_trb_transfer_length(transfer_length);
+            event_trb.set_completion_code(completion_code as u8);
+            event_trb.set_event_data(event_data.into());
+            event_trb.set_trb_type(TrbType::TransferEvent as u8);
+            event_trb.set_endpoint_id(endpoint_id);
+            event_trb.set_slot_id(slot_id);
+        }
+        self.add_event(trb)
+    }
+
+    /// Enable/Disable this interrupter.
+    pub fn set_enabled(&mut self, enabled: bool) -> Result<()> {
+        usb_debug!("interrupter set enabled {}", enabled);
+        self.enabled = enabled;
+        self.interrupt_if_needed()
+    }
+
+    /// Set interrupt moderation.
+    pub fn set_moderation(&mut self, interval: u16, counter: u16) -> Result<()> {
+        // TODO(jkwang) Moderation is not implemented yet.
+        self.moderation_interval = interval;
+        self.moderation_counter = counter;
+        self.interrupt_if_needed()
+    }
+
+    /// Set event ring seg table size.
+    pub fn set_event_ring_seg_table_size(&mut self, size: u16) -> Result<()> {
+        usb_debug!("interrupter set seg table size {}", size);
+        self.event_ring
+            .set_seg_table_size(size)
+            .map_err(Error::SetSegTableSize)
+    }
+
+    /// Set event ring segment table base address.
+    pub fn set_event_ring_seg_table_base_addr(&mut self, addr: GuestAddress) -> Result<()> {
+        usb_debug!("interrupter set table base addr {:#x}", addr.0);
+        self.event_ring
+            .set_seg_table_base_addr(addr)
+            .map_err(Error::SetSegTableBaseAddr)
+    }
+
+    /// Set event ring dequeue pointer.
+    pub fn set_event_ring_dequeue_pointer(&mut self, addr: GuestAddress) -> Result<()> {
+        usb_debug!("interrupter set dequeue ptr addr {:#x}", addr.0);
+        self.event_ring.set_dequeue_pointer(addr);
+        if addr == self.event_ring.get_enqueue_pointer() {
+            self.pending = false;
+        }
+        self.interrupt_if_needed()
+    }
+
+    /// Set event hander busy.
+    pub fn set_event_handler_busy(&mut self, busy: bool) -> Result<()> {
+        usb_debug!("set event handler busy {}", busy);
+        self.event_handler_busy = busy;
+        self.interrupt_if_needed()
+    }
+
+    /// Send and interrupt.
+    pub fn interrupt(&mut self) -> Result<()> {
+        usb_debug!("sending interrupt");
+        self.event_handler_busy = true;
+        self.pending = false;
+        self.usbsts.set_bits(USB_STS_EVENT_INTERRUPT);
+        self.iman.set_bits(IMAN_INTERRUPT_PENDING);
+        self.erdp.set_bits(ERDP_EVENT_HANDLER_BUSY);
+        self.interrupt_fd.write(1).map_err(Error::SendInterrupt)
+    }
+
+    fn interrupt_if_needed(&mut self) -> Result<()> {
+        if self.enabled && self.pending && !self.event_handler_busy {
+            self.interrupt()?;
+        }
+        Ok(())
+    }
+}
diff --git a/devices/src/usb/xhci/intr_resample_handler.rs b/devices/src/usb/xhci/intr_resample_handler.rs
new file mode 100644
index 0000000..202ed1a
--- /dev/null
+++ b/devices/src/usb/xhci/intr_resample_handler.rs
@@ -0,0 +1,64 @@
+// 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::interrupter::Interrupter;
+use std::os::unix::io::RawFd;
+use std::sync::Arc;
+use sync::Mutex;
+use sys_util::{EventFd, WatchingEvents};
+use utils::{EventHandler, EventLoop};
+
+/// Interrupt Resample handler handles resample event. It will reassert interrupt if needed.
+pub struct IntrResampleHandler {
+    interrupter: Arc<Mutex<Interrupter>>,
+    resample_evt: EventFd,
+}
+
+impl IntrResampleHandler {
+    /// Start resample handler.
+    pub fn start(
+        event_loop: &EventLoop,
+        interrupter: Arc<Mutex<Interrupter>>,
+        resample_evt: EventFd,
+    ) -> Option<Arc<IntrResampleHandler>> {
+        let handler = Arc::new(IntrResampleHandler {
+            interrupter,
+            resample_evt,
+        });
+        let tmp_handler: Arc<EventHandler> = handler.clone();
+        if let Err(e) = event_loop.add_event(
+            &handler.resample_evt,
+            WatchingEvents::empty().set_read(),
+            Arc::downgrade(&tmp_handler),
+        ) {
+            error!("cannot add intr resample handler to event loop: {}", e);
+            return None;
+        }
+        Some(handler)
+    }
+}
+impl EventHandler for IntrResampleHandler {
+    fn on_event(&self, _fd: RawFd) -> Result<(), ()> {
+        match self.resample_evt.read() {
+            Ok(_) => {}
+            Err(e) => {
+                error!("cannot read resample evt: {}", e);
+                return Err(());
+            }
+        }
+        usb_debug!("resample triggered");
+        if !self.interrupter.lock().event_ring_is_empty() {
+            usb_debug!("irq resample re-assert irq event");
+            // There could be a race condition. When we get resample_evt and other
+            // component is sending interrupt at the same time.
+            // This might result in one more interrupt than we want. It's handled by
+            // kernel correctly.
+            if let Err(e) = self.interrupter.lock().interrupt() {
+                error!("cannot send interrupt: {}", e);
+                return Err(());
+            }
+        }
+        Ok(())
+    }
+}
diff --git a/devices/src/usb/xhci/mod.rs b/devices/src/usb/xhci/mod.rs
index 4fac7a7..5af9be4 100644
--- a/devices/src/usb/xhci/mod.rs
+++ b/devices/src/usb/xhci/mod.rs
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 mod event_ring;
+mod interrupter;
+mod intr_resample_handler;
 mod xhci_abi;
 mod xhci_abi_schema;
 mod xhci_regs;