// 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::mem; use std::sync::Arc; use super::error::*; use crate::usb::xhci::xhci_transfer::{XhciTransfer, XhciTransferState}; use crate::utils::AsyncJobQueue; use crate::utils::FailHandle; use base::{error, warn}; use usb_util::{Device, Transfer, TransferStatus}; /// Helper function to update xhci_transfer state. pub fn update_transfer_state( xhci_transfer: &Arc, usb_transfer: &Transfer, ) -> Result<()> { let status = usb_transfer.status(); let mut state = xhci_transfer.state().lock(); if status == TransferStatus::Cancelled { *state = XhciTransferState::Cancelled; return Ok(()); } match *state { XhciTransferState::Cancelling => { *state = XhciTransferState::Cancelled; } XhciTransferState::Submitted { .. } => { *state = XhciTransferState::Completed; } _ => { error!("xhci trasfer state is invalid"); *state = XhciTransferState::Completed; return Err(Error::BadXhciTransferState); } } Ok(()) } /// Helper function to submit usb_transfer to device handle. pub fn submit_transfer( fail_handle: Arc, job_queue: &Arc, xhci_transfer: Arc, device: &mut Device, usb_transfer: Transfer, ) -> Result<()> { let transfer_status = { // We need to hold the lock to avoid race condition. // While we are trying to submit the transfer, another thread might want to cancel the same // transfer. Holding the lock here makes sure one of them is cancelled. let mut state = xhci_transfer.state().lock(); match mem::replace(&mut *state, XhciTransferState::Cancelled) { XhciTransferState::Created => { match device.submit_transfer(usb_transfer) { Err(e) => { error!("fail to submit transfer {:?}", e); *state = XhciTransferState::Completed; TransferStatus::NoDevice } // If it's submitted, we don't need to send on_transfer_complete now. Ok(canceller) => { let cancel_callback = Box::new(move || match canceller.cancel() { Ok(()) => { usb_debug!("cancel issued to kernel"); } Err(e) => { usb_debug!("fail to cancel: {}", e); } }); *state = XhciTransferState::Submitted { cancel_callback }; return Ok(()); } } } XhciTransferState::Cancelled => { warn!("Transfer is already cancelled"); TransferStatus::Cancelled } _ => { // The transfer could not be in the following states: // Submitted: A transfer should only be submitted once. // Cancelling: Transfer is cancelling only when it's submitted and someone is // trying to cancel it. // Completed: A completed transfer should not be submitted again. error!("xhci trasfer state is invalid"); return Err(Error::BadXhciTransferState); } } }; // We are holding locks to of backends, we want to call on_transfer_complete // without any lock. job_queue .queue_job( move || match xhci_transfer.on_transfer_complete(&transfer_status, 0) { Ok(_) => {} Err(e) => { error!("transfer complete failed: {:?}", e); fail_handle.fail(); } }, ) .map_err(Error::QueueAsyncJob) }