blob: 692f6b2106a9f2bedc45307ced692e8f4ba79ff8 [file] [log] [blame]
// Copyright (c) 2013 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.
#include "mist/usb_transfer.h"
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <libusb.h>
using base::StringPrintf;
using std::ostream;
using std::string;
namespace mist {
UsbTransfer::UsbTransfer()
: transfer_(nullptr),
buffer_length_(0),
state_(kIdle) {}
UsbTransfer::~UsbTransfer() {
Free();
}
bool UsbTransfer::Submit(const CompletionCallback& completion_callback) {
if (!VerifyAllocated())
return false;
if (state_ != kIdle) {
error_.set_type(UsbError::kErrorTransferAlreadySubmitted);
return false;
}
completion_callback_ = completion_callback;
VLOG(1) << "Submit USB transfer: " << *this;
int result = libusb_submit_transfer(transfer_);
if (error_.SetFromLibUsbError(static_cast<libusb_error>(result))) {
state_ = kInProgress;
return true;
}
return false;
}
bool UsbTransfer::Cancel() {
if (state_ == kIdle) {
error_.set_type(UsbError::kErrorTransferNotSubmitted);
return false;
}
if (state_ == kCancelling) {
error_.set_type(UsbError::kErrorTransferBeingCancelled);
return false;
}
int result = libusb_cancel_transfer(transfer_);
if (error_.SetFromLibUsbError(static_cast<libusb_error>(result))) {
state_ = kCancelling;
return true;
}
return false;
}
uint8_t UsbTransfer::GetEndpointAddress() const {
return transfer_ ? transfer_->endpoint : 0;
}
UsbTransferType UsbTransfer::GetType() const {
if (!transfer_)
return kUsbTransferTypeUnknown;
switch (transfer_->type) {
case LIBUSB_TRANSFER_TYPE_CONTROL:
return kUsbTransferTypeControl;
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
return kUsbTransferTypeIsochronous;
case LIBUSB_TRANSFER_TYPE_BULK:
return kUsbTransferTypeBulk;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
return kUsbTransferTypeInterrupt;
}
return kUsbTransferTypeUnknown;
}
UsbTransferStatus UsbTransfer::GetStatus() const {
if (!transfer_)
return kUsbTransferStatusUnknown;
switch (transfer_->status) {
case LIBUSB_TRANSFER_COMPLETED:
return kUsbTransferStatusCompleted;
case LIBUSB_TRANSFER_ERROR:
return kUsbTransferStatusError;
case LIBUSB_TRANSFER_TIMED_OUT:
return kUsbTransferStatusTimedOut;
case LIBUSB_TRANSFER_CANCELLED:
return kUsbTransferStatusCancelled;
case LIBUSB_TRANSFER_STALL:
return kUsbTransferStatusStall;
case LIBUSB_TRANSFER_NO_DEVICE:
return kUsbTransferStatusNoDevice;
case LIBUSB_TRANSFER_OVERFLOW:
return kUsbTransferStatusOverflow;
}
return kUsbTransferStatusUnknown;
}
int UsbTransfer::GetLength() const {
return transfer_ ? transfer_->length : 0;
}
int UsbTransfer::GetActualLength() const {
return transfer_ ? transfer_->actual_length : 0;
}
bool UsbTransfer::IsCompletedWithExpectedLength(int expected_length) const {
return GetStatus() == kUsbTransferStatusCompleted &&
GetActualLength() == expected_length;
}
string UsbTransfer::ToString() const {
if (!transfer_)
return "Transfer (not allocated)";
return StringPrintf("Transfer %p (Type=%s, "
"Flags=0x%08x, "
"DeviceHandle=%p, "
"EndpointAddress=%u, "
"NumIsoPackets=%d, "
"Buffer=%p, "
"Length=%d, "
"Transferred=%d, "
"Timeout=%u, "
"Status=%s)",
transfer_,
UsbTransferTypeToString(GetType()),
transfer_->flags,
transfer_->dev_handle,
transfer_->endpoint,
transfer_->num_iso_packets,
transfer_->buffer,
transfer_->length,
transfer_->actual_length,
transfer_->timeout,
UsbTransferStatusToString(GetStatus()));
}
bool UsbTransfer::VerifyAllocated() {
if (transfer_)
return true;
LOG(ERROR) << "USB transfer is not allocated.";
error_.set_type(UsbError::kErrorTransferNotAllocated);
return false;
}
bool UsbTransfer::Allocate(int num_iso_packets) {
if (transfer_) {
LOG(ERROR) << "USB transfer already allocated.";
error_.set_type(UsbError::kErrorTransferAlreadyAllocated);
return false;
}
transfer_ = libusb_alloc_transfer(num_iso_packets);
if (!transfer_) {
LOG(ERROR) << "Could not allocate USB transfer.";
error_.set_type(UsbError::kErrorNoMemory);
return false;
}
VLOG(2) << StringPrintf("Allocated USB transfer %p.", transfer_);
error_.Clear();
return true;
}
void UsbTransfer::Free() {
// It is not ok to free a transfer while it is still in progress or being
// cancelled.
CHECK_EQ(kIdle, state_);
if (transfer_) {
libusb_free_transfer(transfer_);
VLOG(2) << StringPrintf("Freed USB transfer %p.", transfer_);
transfer_ = nullptr;
}
}
bool UsbTransfer::AllocateBuffer(int length) {
if (state_ != kIdle) {
error_.set_type(UsbError::kErrorTransferAlreadySubmitted);
return false;
}
buffer_.reset(new uint8_t[length]);
if (buffer_) {
buffer_length_ = length;
VLOG(2) << StringPrintf("Allocated data buffer %p for USB transfer %p.",
buffer_.get(),
transfer_);
return true;
}
buffer_length_ = 0;
LOG(ERROR) << StringPrintf(
"Could not allocate data buffer for USB transfer %p.", transfer_);
error_.set_type(UsbError::kErrorNoMemory);
return false;
}
void UsbTransfer::OnCompleted(libusb_transfer* transfer) {
CHECK(transfer);
UsbTransfer* usb_transfer =
reinterpret_cast<UsbTransfer*>(transfer->user_data);
CHECK(usb_transfer);
CHECK_EQ(transfer, usb_transfer->transfer_);
VLOG(1) << StringPrintf("USB transfer %p completed.", usb_transfer);
usb_transfer->Complete();
}
void UsbTransfer::Complete() {
// Change the state to idle before calling the completion callback as this
// object may be destructed in the completion callback and Free(), which is
// called in the destructor of this object, expects the state to be idle.
state_ = kIdle;
if (!completion_callback_.is_null()) {
VLOG(2) << StringPrintf("Invoke completion callback for USB transfer %p.",
transfer_);
completion_callback_.Run(this);
}
}
} // namespace mist
ostream& operator<<(ostream& stream, const mist::UsbTransfer& transfer) {
stream << transfer.ToString();
return stream;
}