diff options
author | Jingkui Wang <jkwang@google.com> | 2019-03-07 22:06:11 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-03-13 21:04:58 -0700 |
commit | 115fe4e233cd9263c0152bd37db1d7537a109400 (patch) | |
tree | f52c997b7b60557b47f3550d4ccf3fb871b33bd6 /devices/src/usb | |
parent | e9f5cae48f0707200331241a02d0c7b218949179 (diff) | |
download | crosvm-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.rs | 208 | ||||
-rw-r--r-- | devices/src/usb/xhci/intr_resample_handler.rs | 64 | ||||
-rw-r--r-- | devices/src/usb/xhci/mod.rs | 2 |
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; |