// Copyright 2019 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::cmp; use std::sync::Arc; use sync::Mutex; use super::error::*; use super::utils::{submit_transfer, update_transfer_state}; use crate::usb::xhci::scatter_gather_buffer::ScatterGatherBuffer; use crate::usb::xhci::xhci_transfer::{ TransferDirection, XhciTransfer, XhciTransferState, XhciTransferType, }; use crate::utils::AsyncJobQueue; use crate::utils::FailHandle; use sys_util::error; use usb_util::device_handle::DeviceHandle; use usb_util::types::{EndpointDirection, EndpointType, ENDPOINT_DIRECTION_OFFSET}; use usb_util::usb_transfer::{ bulk_transfer, interrupt_transfer, BulkTransferBuffer, TransferStatus, UsbTransfer, }; /// Isochronous, Bulk or Interrupt endpoint. pub struct UsbEndpoint { fail_handle: Arc, job_queue: Arc, device_handle: Arc>, endpoint_number: u8, direction: EndpointDirection, ty: EndpointType, } impl UsbEndpoint { /// Create new endpoint. This function will panic if endpoint type is control. pub fn new( fail_handle: Arc, job_queue: Arc, device_handle: Arc>, endpoint_number: u8, direction: EndpointDirection, ty: EndpointType, ) -> UsbEndpoint { assert!(ty != EndpointType::Control); UsbEndpoint { fail_handle, job_queue, device_handle, endpoint_number, direction, ty, } } fn ep_addr(&self) -> u8 { self.endpoint_number | ((self.direction as u8) << ENDPOINT_DIRECTION_OFFSET) } /// Returns true is this endpoint matches number and direction. pub fn match_ep(&self, endpoint_number: u8, dir: TransferDirection) -> bool { let self_dir = match self.direction { EndpointDirection::HostToDevice => TransferDirection::Out, EndpointDirection::DeviceToHost => TransferDirection::In, }; self.endpoint_number == endpoint_number && self_dir == dir } /// Handle a xhci transfer. pub fn handle_transfer(&self, transfer: XhciTransfer) -> Result<()> { let buffer = match transfer .get_transfer_type() .map_err(Error::GetXhciTransferType)? { XhciTransferType::Normal(buffer) => buffer, XhciTransferType::Noop => { return transfer .on_transfer_complete(&TransferStatus::Completed, 0) .map_err(Error::TransferComplete); } _ => { error!("unhandled xhci transfer type by usb endpoint"); return transfer .on_transfer_complete(&TransferStatus::Error, 0) .map_err(Error::TransferComplete); } }; match self.ty { EndpointType::Bulk => { self.handle_bulk_transfer(transfer, buffer)?; } EndpointType::Interrupt => { self.handle_interrupt_transfer(transfer, buffer)?; } _ => { return transfer .on_transfer_complete(&TransferStatus::Error, 0) .map_err(Error::TransferComplete); } } Ok(()) } fn handle_bulk_transfer( &self, xhci_transfer: XhciTransfer, buffer: ScatterGatherBuffer, ) -> Result<()> { let usb_transfer = bulk_transfer(self.ep_addr(), 0, buffer.len().map_err(Error::BufferLen)?); self.do_handle_transfer(xhci_transfer, usb_transfer, buffer) } fn handle_interrupt_transfer( &self, xhci_transfer: XhciTransfer, buffer: ScatterGatherBuffer, ) -> Result<()> { let usb_transfer = interrupt_transfer(self.ep_addr(), 0, buffer.len().map_err(Error::BufferLen)?); self.do_handle_transfer(xhci_transfer, usb_transfer, buffer) } fn do_handle_transfer( &self, xhci_transfer: XhciTransfer, mut usb_transfer: UsbTransfer, buffer: ScatterGatherBuffer, ) -> Result<()> { let xhci_transfer = Arc::new(xhci_transfer); let tmp_transfer = xhci_transfer.clone(); match self.direction { EndpointDirection::HostToDevice => { // Read data from ScatterGatherBuffer to a continuous memory. buffer .read(usb_transfer.buffer_mut().as_mut_slice()) .map_err(Error::ReadBuffer)?; usb_debug!( "out transfer ep_addr {:#x}, buffer len {:?}, data {:#x?}", self.ep_addr(), buffer.len(), usb_transfer.buffer_mut().as_mut_slice() ); let callback = move |t: UsbTransfer| { usb_debug!("out transfer callback"); update_transfer_state(&xhci_transfer, &t)?; let state = xhci_transfer.state().lock(); match *state { XhciTransferState::Cancelled => { usb_debug!("transfer has been cancelled"); drop(state); xhci_transfer .on_transfer_complete(&TransferStatus::Cancelled, 0) .map_err(Error::TransferComplete) } XhciTransferState::Completed => { let status = t.status(); let actual_length = t.actual_length(); drop(state); xhci_transfer .on_transfer_complete(&status, actual_length as u32) .map_err(Error::TransferComplete) } _ => { error!("xhci trasfer state (host to device) is invalid"); Err(Error::BadXhciTransferState) } } }; let fail_handle = self.fail_handle.clone(); usb_transfer.set_callback( move |t: UsbTransfer| match callback(t) { Ok(_) => {} Err(e) => { error!("bulk transfer callback failed: {:?}", e); fail_handle.fail(); } }, ); submit_transfer( self.fail_handle.clone(), &self.job_queue, tmp_transfer, &self.device_handle, usb_transfer, )?; } EndpointDirection::DeviceToHost => { usb_debug!( "in transfer ep_addr {:#x}, buffer len {:?}", self.ep_addr(), buffer.len() ); let _addr = self.ep_addr(); let callback = move |t: UsbTransfer| { usb_debug!( "ep {:#x} in transfer data {:?}", _addr, t.buffer().as_slice() ); update_transfer_state(&xhci_transfer, &t)?; let state = xhci_transfer.state().lock(); match *state { XhciTransferState::Cancelled => { usb_debug!("transfer has been cancelled"); drop(state); xhci_transfer .on_transfer_complete(&TransferStatus::Cancelled, 0) .map_err(Error::TransferComplete) } XhciTransferState::Completed => { let status = t.status(); let actual_length = t.actual_length() as usize; let copied_length = buffer .write(t.buffer().as_slice()) .map_err(Error::WriteBuffer)?; let actual_length = cmp::min(actual_length, copied_length); drop(state); xhci_transfer .on_transfer_complete(&status, actual_length as u32) .map_err(Error::TransferComplete) } _ => { // update state is already invoked. This match should not be in any // other state. error!("xhci trasfer state (device to host) is invalid"); Err(Error::BadXhciTransferState) } } }; let fail_handle = self.fail_handle.clone(); usb_transfer.set_callback( move |t: UsbTransfer| match callback(t) { Ok(_) => {} Err(e) => { error!("bulk transfer callback {:?}", e); fail_handle.fail(); } }, ); submit_transfer( self.fail_handle.clone(), &self.job_queue, tmp_transfer, &self.device_handle, usb_transfer, )?; } } Ok(()) } }