Index: chrome/browser/usb/usb_device_handle.cc |
diff --git a/chrome/browser/usb/usb_device_handle.cc b/chrome/browser/usb/usb_device_handle.cc |
index 5fbde1bc650f72fa60e3b5f3ffb8fd03cb078fb8..6e276431a93ae26b0879ca2dcfabdea6b569194a 100644 |
--- a/chrome/browser/usb/usb_device_handle.cc |
+++ b/chrome/browser/usb/usb_device_handle.cc |
@@ -7,9 +7,11 @@ |
#include <algorithm> |
#include <vector> |
+#include "base/message_loop/message_loop.h" |
#include "base/stl_util.h" |
#include "base/strings/string16.h" |
#include "base/synchronization/lock.h" |
+#include "chrome/browser/usb/usb_context.h" |
#include "chrome/browser/usb/usb_device.h" |
#include "chrome/browser/usb/usb_interface.h" |
#include "chrome/browser/usb/usb_service.h" |
@@ -17,6 +19,7 @@ |
#include "third_party/libusb/src/libusb/libusb.h" |
using content::BrowserThread; |
+void HandleTransferCompletion(PlatformUsbTransferHandle transfer); |
namespace { |
@@ -94,15 +97,77 @@ static UsbTransferStatus ConvertTransferStatus( |
} |
} |
-static void LIBUSB_CALL HandleTransferCompletion( |
- struct libusb_transfer* transfer) { |
- UsbDeviceHandle* const device = |
- reinterpret_cast<UsbDeviceHandle*>(transfer->user_data); |
- device->TransferComplete(transfer); |
+static void LIBUSB_CALL PlatformTransferCompletionCallback( |
+ PlatformUsbTransferHandle transfer) { |
+ BrowserThread::PostTask(BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(HandleTransferCompletion, transfer)); |
} |
} // namespace |
+void HandleTransferCompletion(PlatformUsbTransferHandle transfer) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ UsbDeviceHandle* const device_handle = |
+ reinterpret_cast<UsbDeviceHandle*>(transfer->user_data); |
+ CHECK(device_handle) << "Device handle is closed before transfer finishes."; |
+ device_handle->TransferComplete(transfer); |
+ libusb_free_transfer(transfer); |
+} |
+ |
+ |
+class UsbDeviceHandle::InterfaceClaimer |
+ : public base::RefCountedThreadSafe<UsbDeviceHandle::InterfaceClaimer> { |
+ public: |
+ InterfaceClaimer(const scoped_refptr<UsbDeviceHandle> handle, |
+ const int interface_number); |
+ |
+ bool Claim() const; |
+ |
+ int alternate_setting() const { return alternate_setting_; } |
+ void set_alternate_setting(const int alternate_setting) { |
+ alternate_setting_ = alternate_setting; |
+ } |
+ |
+ private: |
+ friend class UsbDevice; |
+ friend class base::RefCountedThreadSafe<InterfaceClaimer>; |
+ ~InterfaceClaimer(); |
+ |
+ const scoped_refptr<UsbDeviceHandle> handle_; |
+ const int interface_number_; |
+ int alternate_setting_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(InterfaceClaimer); |
+}; |
+ |
+UsbDeviceHandle::InterfaceClaimer::InterfaceClaimer( |
+ const scoped_refptr<UsbDeviceHandle> handle, const int interface_number) |
+ : handle_(handle), |
+ interface_number_(interface_number), |
+ alternate_setting_(0) { |
+} |
+ |
+UsbDeviceHandle::InterfaceClaimer::~InterfaceClaimer() { |
+ libusb_release_interface(handle_->handle(), interface_number_); |
+} |
+ |
+bool UsbDeviceHandle::InterfaceClaimer::Claim() const { |
+ return libusb_claim_interface(handle_->handle(), interface_number_) == 0; |
+} |
+ |
+struct UsbDeviceHandle::Transfer { |
+ Transfer(); |
+ ~Transfer(); |
+ |
+ UsbTransferType transfer_type; |
+ scoped_refptr<net::IOBuffer> buffer; |
+ scoped_refptr<UsbDeviceHandle::InterfaceClaimer> claimed_interface; |
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy; |
+ size_t length; |
+ UsbTransferCallback callback; |
+}; |
+ |
UsbDeviceHandle::Transfer::Transfer() |
: transfer_type(USB_TRANSFER_CONTROL), |
length(0) { |
@@ -111,11 +176,14 @@ UsbDeviceHandle::Transfer::Transfer() |
UsbDeviceHandle::Transfer::~Transfer() {} |
UsbDeviceHandle::UsbDeviceHandle( |
+ scoped_refptr<UsbContext> context, |
UsbDevice* device, |
PlatformUsbDeviceHandle handle) |
- : device_(device), handle_(handle) { |
+ : device_(device), handle_(handle), context_(context) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
DCHECK(handle) << "Cannot create device with NULL handle."; |
+ interfaces_ = new UsbConfigDescriptor(); |
+ device->ListInterfaces(interfaces_.get()); |
} |
UsbDeviceHandle::UsbDeviceHandle() : device_(NULL), handle_(NULL) { |
@@ -123,7 +191,9 @@ UsbDeviceHandle::UsbDeviceHandle() : device_(NULL), handle_(NULL) { |
UsbDeviceHandle::~UsbDeviceHandle() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- Close(); |
+ |
+ libusb_close(handle_); |
+ handle_ = NULL; |
} |
scoped_refptr<UsbDevice> UsbDeviceHandle::device() const { |
@@ -132,35 +202,34 @@ scoped_refptr<UsbDevice> UsbDeviceHandle::device() const { |
void UsbDeviceHandle::Close() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- if (handle_) |
+ if (device_) |
device_->Close(this); |
} |
void UsbDeviceHandle::TransferComplete(PlatformUsbTransferHandle handle) { |
- base::AutoLock lock(lock_); |
- |
- // TODO(gdk): Handle device disconnect. |
DCHECK(ContainsKey(transfers_, handle)) << "Missing transfer completed"; |
- Transfer* const transfer = &transfers_[handle]; |
+ |
+ Transfer transfer = transfers_[handle]; |
+ transfers_.erase(handle); |
DCHECK_GE(handle->actual_length, 0) << "Negative actual length received"; |
size_t actual_length = |
static_cast<size_t>(std::max(handle->actual_length, 0)); |
- DCHECK(transfer->length >= actual_length) << |
+ DCHECK(transfer.length >= actual_length) << |
"data too big for our buffer (libusb failure?)"; |
- scoped_refptr<net::IOBuffer> buffer = transfer->buffer; |
- switch (transfer->transfer_type) { |
+ scoped_refptr<net::IOBuffer> buffer = transfer.buffer; |
+ switch (transfer.transfer_type) { |
case USB_TRANSFER_CONTROL: |
// If the transfer is a control transfer we do not expose the control |
// setup header to the caller. This logic strips off the header if |
// present before invoking the callback provided with the transfer. |
if (actual_length > 0) { |
- CHECK(transfer->length >= LIBUSB_CONTROL_SETUP_SIZE) << |
+ CHECK(transfer.length >= LIBUSB_CONTROL_SETUP_SIZE) << |
"buffer was not correctly set: too small for the control header"; |
- if (transfer->length >= actual_length && |
+ if (transfer.length >= actual_length && |
actual_length >= LIBUSB_CONTROL_SETUP_SIZE) { |
// If the payload is zero bytes long, pad out the allocated buffer |
// size to one byte so that an IOBuffer of that size can be allocated. |
@@ -188,7 +257,7 @@ void UsbDeviceHandle::TransferComplete(PlatformUsbTransferHandle handle) { |
// all the data the packet can hold. |
if (actual_length < packet_buffer_start) { |
CHECK(packet_buffer_start + packet->actual_length <= |
- transfer->length); |
+ transfer.length); |
memmove(buffer->data() + actual_length, |
buffer->data() + packet_buffer_start, |
packet->actual_length); |
@@ -210,45 +279,72 @@ void UsbDeviceHandle::TransferComplete(PlatformUsbTransferHandle handle) { |
break; |
} |
- transfer->callback.Run(ConvertTransferStatus(handle->status), buffer, |
- actual_length); |
+ transfer.message_loop_proxy->PostTask( |
+ FROM_HERE, |
+ base::Bind(transfer.callback, |
+ ConvertTransferStatus(handle->status), |
+ buffer, |
+ actual_length)); |
- transfers_.erase(handle); |
- libusb_free_transfer(handle); |
+ // Must release interface first before actually delete this. |
+ transfer.claimed_interface = NULL; |
} |
bool UsbDeviceHandle::ClaimInterface(const int interface_number) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!handle_) return false; |
+ if (!device_) return false; |
+ if (ContainsKey(claimed_interfaces_, interface_number)) return true; |
+ |
+ scoped_refptr<InterfaceClaimer> claimer = |
+ new InterfaceClaimer(this, interface_number); |
- const int claim_result = libusb_claim_interface(handle_, interface_number); |
- return claim_result == 0; |
+ if (claimer->Claim()) { |
+ claimed_interfaces_[interface_number]= claimer; |
+ RefreshEndpointMap(); |
+ return true; |
+ } |
+ return false; |
} |
bool UsbDeviceHandle::ReleaseInterface(const int interface_number) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!handle_) return false; |
+ if (!device_) return false; |
+ if (!ContainsKey(claimed_interfaces_, interface_number)) return false; |
+ |
+ // Cancel all the transfers on that interface. |
+ InterfaceClaimer* interface_claimer = |
+ claimed_interfaces_[interface_number].get(); |
+ for (TransferMap::iterator it = transfers_.begin(); |
+ it != transfers_.end(); ++it) { |
+ if (it->second.claimed_interface.get() == interface_claimer) |
+ libusb_cancel_transfer(it->first); |
+ } |
+ claimed_interfaces_.erase(interface_number); |
- const int release_result = libusb_release_interface(handle_, |
- interface_number); |
- return release_result == 0; |
+ RefreshEndpointMap(); |
+ return true; |
} |
bool UsbDeviceHandle::SetInterfaceAlternateSetting( |
const int interface_number, |
const int alternate_setting) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!handle_) return false; |
- |
- const int setting_result = libusb_set_interface_alt_setting(handle_, |
+ if (!device_) return false; |
+ if (!ContainsKey(claimed_interfaces_, interface_number)) return false; |
+ const int rv = libusb_set_interface_alt_setting(handle_, |
interface_number, alternate_setting); |
- |
- return setting_result == 0; |
+ if (rv == 0) { |
+ claimed_interfaces_[interface_number]-> |
+ set_alternate_setting(alternate_setting); |
+ RefreshEndpointMap(); |
+ return true; |
+ } |
+ return false; |
} |
bool UsbDeviceHandle::ResetDevice() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!handle_) return false; |
+ if (!device_) return false; |
return libusb_reset_device(handle_) == 0; |
} |
@@ -300,7 +396,7 @@ void UsbDeviceHandle::ControlTransfer(const UsbEndpointDirection direction, |
const uint8 request, const uint16 value, const uint16 index, |
net::IOBuffer* buffer, const size_t length, const unsigned int timeout, |
const UsbTransferCallback& callback) { |
- if (!handle_) { |
+ if (!device_) { |
callback.Run(USB_TRANSFER_DISCONNECT, buffer, 0); |
return; |
} |
@@ -311,57 +407,90 @@ void UsbDeviceHandle::ControlTransfer(const UsbEndpointDirection direction, |
memcpy(resized_buffer->data() + LIBUSB_CONTROL_SETUP_SIZE, buffer->data(), |
length); |
- struct libusb_transfer* const transfer = libusb_alloc_transfer(0); |
+ PlatformUsbTransferHandle const transfer = libusb_alloc_transfer(0); |
const uint8 converted_type = CreateRequestType(direction, request_type, |
recipient); |
libusb_fill_control_setup(reinterpret_cast<uint8*>(resized_buffer->data()), |
converted_type, request, value, index, length); |
- libusb_fill_control_transfer(transfer, handle_, reinterpret_cast<uint8*>( |
- resized_buffer->data()), HandleTransferCompletion, this, timeout); |
- SubmitTransfer(transfer, |
+ libusb_fill_control_transfer( |
+ transfer, |
+ handle_, |
+ reinterpret_cast<uint8*>(resized_buffer->data()), |
+ PlatformTransferCompletionCallback, |
+ this, |
+ timeout); |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&UsbDeviceHandle::SubmitTransfer, |
+ this, |
+ transfer, |
USB_TRANSFER_CONTROL, |
- resized_buffer.get(), |
+ resized_buffer, |
resized_length, |
- callback); |
+ base::MessageLoopProxy::current(), |
+ callback)); |
} |
void UsbDeviceHandle::BulkTransfer(const UsbEndpointDirection direction, |
const uint8 endpoint, net::IOBuffer* buffer, const size_t length, |
const unsigned int timeout, const UsbTransferCallback& callback) { |
- if (!handle_) { |
+ if (!device_) { |
callback.Run(USB_TRANSFER_DISCONNECT, buffer, 0); |
return; |
} |
- struct libusb_transfer* const transfer = libusb_alloc_transfer(0); |
+ PlatformUsbTransferHandle const transfer = libusb_alloc_transfer(0); |
const uint8 new_endpoint = ConvertTransferDirection(direction) | endpoint; |
libusb_fill_bulk_transfer(transfer, handle_, new_endpoint, |
reinterpret_cast<uint8*>(buffer->data()), length, |
- HandleTransferCompletion, this, timeout); |
- SubmitTransfer(transfer, USB_TRANSFER_BULK, buffer, length, callback); |
+ PlatformTransferCompletionCallback, this, timeout); |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&UsbDeviceHandle::SubmitTransfer, |
+ this, |
+ transfer, |
+ USB_TRANSFER_BULK, |
+ make_scoped_refptr(buffer), |
+ length, |
+ base::MessageLoopProxy::current(), |
+ callback)); |
} |
void UsbDeviceHandle::InterruptTransfer(const UsbEndpointDirection direction, |
const uint8 endpoint, net::IOBuffer* buffer, const size_t length, |
const unsigned int timeout, const UsbTransferCallback& callback) { |
- if (!handle_) { |
+ if (!device_) { |
callback.Run(USB_TRANSFER_DISCONNECT, buffer, 0); |
return; |
} |
- struct libusb_transfer* const transfer = libusb_alloc_transfer(0); |
+ PlatformUsbTransferHandle const transfer = libusb_alloc_transfer(0); |
const uint8 new_endpoint = ConvertTransferDirection(direction) | endpoint; |
libusb_fill_interrupt_transfer(transfer, handle_, new_endpoint, |
reinterpret_cast<uint8*>(buffer->data()), length, |
- HandleTransferCompletion, this, timeout); |
- SubmitTransfer(transfer, USB_TRANSFER_INTERRUPT, buffer, length, callback); |
+ PlatformTransferCompletionCallback, this, timeout); |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&UsbDeviceHandle::SubmitTransfer, |
+ this, |
+ transfer, |
+ USB_TRANSFER_INTERRUPT, |
+ make_scoped_refptr(buffer), |
+ length, |
+ base::MessageLoopProxy::current(), |
+ callback)); |
} |
void UsbDeviceHandle::IsochronousTransfer(const UsbEndpointDirection direction, |
const uint8 endpoint, net::IOBuffer* buffer, const size_t length, |
const unsigned int packets, const unsigned int packet_length, |
const unsigned int timeout, const UsbTransferCallback& callback) { |
- if (!handle_) { |
+ if (!device_) { |
callback.Run(USB_TRANSFER_DISCONNECT, buffer, 0); |
return; |
} |
@@ -370,41 +499,101 @@ void UsbDeviceHandle::IsochronousTransfer(const UsbEndpointDirection direction, |
CHECK(packets <= length && total_length <= length) << |
"transfer length is too small"; |
- struct libusb_transfer* const transfer = libusb_alloc_transfer(packets); |
+ PlatformUsbTransferHandle const transfer = libusb_alloc_transfer(packets); |
const uint8 new_endpoint = ConvertTransferDirection(direction) | endpoint; |
libusb_fill_iso_transfer(transfer, handle_, new_endpoint, |
reinterpret_cast<uint8*>(buffer->data()), length, packets, |
- HandleTransferCompletion, this, timeout); |
+ PlatformTransferCompletionCallback, this, timeout); |
libusb_set_iso_packet_lengths(transfer, packet_length); |
- SubmitTransfer(transfer, USB_TRANSFER_ISOCHRONOUS, buffer, length, callback); |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&UsbDeviceHandle::SubmitTransfer, |
+ this, |
+ transfer, |
+ USB_TRANSFER_ISOCHRONOUS, |
+ make_scoped_refptr(buffer), |
+ length, |
+ base::MessageLoopProxy::current(), |
+ callback)); |
} |
-void UsbDeviceHandle::SubmitTransfer(PlatformUsbTransferHandle handle, |
- UsbTransferType transfer_type, |
- net::IOBuffer* buffer, |
- const size_t length, |
- const UsbTransferCallback& callback) { |
+void UsbDeviceHandle::RefreshEndpointMap() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ endpoint_map_.clear(); |
+ for (ClaimedInterfaceMap::iterator it = claimed_interfaces_.begin(); |
+ it != claimed_interfaces_.end(); ++it) { |
+ scoped_refptr<const UsbInterfaceDescriptor> interface_desc = |
+ interfaces_->GetInterface(it->first)->GetAltSetting( |
+ it->second->alternate_setting()); |
+ for (size_t i = 0; i < interface_desc->GetNumEndpoints(); i++) { |
+ scoped_refptr<const UsbEndpointDescriptor> endpoint = |
+ interface_desc->GetEndpoint(i); |
+ endpoint_map_[endpoint->GetAddress()] = it->first; |
+ } |
+ } |
+} |
+ |
+void UsbDeviceHandle::SubmitTransfer( |
+ PlatformUsbTransferHandle handle, |
+ UsbTransferType transfer_type, |
+ net::IOBuffer* buffer, |
+ const size_t length, |
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy, |
+ const UsbTransferCallback& callback) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (!device_) { |
+ message_loop_proxy->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, USB_TRANSFER_DISCONNECT, |
+ make_scoped_refptr(buffer), 0)); |
+ } |
+ |
+ unsigned char address = handle->endpoint & LIBUSB_ENDPOINT_ADDRESS_MASK; |
+ if (!ContainsKey(endpoint_map_, address)) { |
+ // Endpoint for given transfer not found or the interface is not claimed. |
+ message_loop_proxy->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, USB_TRANSFER_ERROR, |
+ make_scoped_refptr(buffer), 0)); |
+ return; |
+ } |
+ |
Transfer transfer; |
transfer.transfer_type = transfer_type; |
transfer.buffer = buffer; |
transfer.length = length; |
transfer.callback = callback; |
+ transfer.message_loop_proxy = message_loop_proxy; |
+ transfer.claimed_interface = claimed_interfaces_[endpoint_map_[address]]; |
- { |
- base::AutoLock lock(lock_); |
+ if (libusb_submit_transfer(handle) == LIBUSB_SUCCESS) { |
transfers_[handle] = transfer; |
- } |
- if (libusb_submit_transfer(handle) != LIBUSB_SUCCESS) { |
- base::AutoLock lock(lock_); |
- transfers_.erase(handle); |
- callback.Run(USB_TRANSFER_DISCONNECT, buffer, 0); |
+ } else { |
+ message_loop_proxy->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, USB_TRANSFER_ERROR, |
+ make_scoped_refptr(buffer), 0)); |
} |
} |
void UsbDeviceHandle::InternalClose() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- libusb_close(handle_); |
- handle_ = NULL; |
+ if (!device_) return; |
+ |
+ // Cancel all the transfers. |
+ for (TransferMap::iterator it = transfers_.begin(); |
+ it != transfers_.end(); ++it) { |
+ // The callback will be called some time later. |
+ libusb_cancel_transfer(it->first); |
+ } |
+ |
+ // Attempt-release all the interfaces. |
+ // It will be retained until the transfer cancellation is finished. |
+ claimed_interfaces_.clear(); |
+ |
+ // Cannot close device handle here. Need to wait for libusb_cancel_transfer to |
+ // finish. |
device_ = NULL; |
} |