summary refs log blame commit diff
path: root/devices/src/usb/host_backend/utils.rs
blob: ac506e70bdeefab9eec7070a57da3a9ec52a1860 (plain) (tree)
1
2
3
4
5
6
7
8





                                                                         

                    


                                                                       
                            
                                                 

                                                  
                             
                                      
                            












                                                  
                                                











                                                            
                       
                                     

                                     

                           







                                                                                                   
                                                            





                                                                                         
                                      


                                                                                         
                             

                                                                    




                                                                                  






























                                                                                             
// 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 sys_util::{error, warn};
use usb_util::{Device, Transfer, TransferStatus};

/// Helper function to update xhci_transfer state.
pub fn update_transfer_state(
    xhci_transfer: &Arc<XhciTransfer>,
    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<dyn FailHandle>,
    job_queue: &Arc<AsyncJobQueue>,
    xhci_transfer: Arc<XhciTransfer>,
    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)
}