| Index: chrome/browser/usb/usb_service.cc
|
| diff --git a/chrome/browser/usb/usb_service.cc b/chrome/browser/usb/usb_service.cc
|
| index 038786debf85048856500ce8f184578faade605f..e4c20b4dc39075aae0567c6a75f7572971fa0505 100644
|
| --- a/chrome/browser/usb/usb_service.cc
|
| +++ b/chrome/browser/usb/usb_service.cc
|
| @@ -4,13 +4,17 @@
|
|
|
| #include "chrome/browser/usb/usb_service.h"
|
|
|
| +#include <cstring>
|
| +#include <set>
|
| #include <vector>
|
|
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/logging.h"
|
| #include "base/stl_util.h"
|
| -#include "chrome/browser/usb/usb_device.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "chrome/browser/usb/usb_device_handle.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| #include "third_party/libusb/src/libusb/libusb.h"
|
|
|
| #if defined(OS_CHROMEOS)
|
| @@ -20,61 +24,197 @@
|
| #endif // defined(OS_CHROMEOS)
|
|
|
| using std::vector;
|
| +using std::set;
|
| +using content::BrowserThread;
|
| +using base::PlatformThreadHandle;
|
| +using base::RefCountedThreadSafe;
|
|
|
| -// The UsbEventHandler works around a design flaw in the libusb interface. There
|
| -// is currently no way to signal to libusb that any caller into one of the event
|
| +// The UsbEventHandler dispatches USB events on separate thread. There is
|
| +// currently no way to signal to libusb that any caller into one of the event
|
| // handler calls should return without handling any events.
|
| +//
|
| +// This class manages the polling thread and assures the thread exits safely.
|
| +// This class is only visible to UsbContext. UsbContext manages its life cycle.
|
| class UsbEventHandler : public base::PlatformThread::Delegate {
|
| - public:
|
| - explicit UsbEventHandler(PlatformUsbContext context)
|
| - : running_(true), context_(context) {
|
| - base::PlatformThread::CreateNonJoinable(0, this);
|
| - }
|
| + private:
|
| + friend class UsbContext;
|
| + friend struct base::DefaultDeleter<UsbEventHandler>;
|
|
|
| - virtual ~UsbEventHandler() {}
|
| + explicit UsbEventHandler(PlatformUsbContext context);
|
| + virtual ~UsbEventHandler();
|
|
|
| virtual void ThreadMain() OVERRIDE {
|
| - base::PlatformThread::SetName("UsbEventHandler");
|
| -
|
| - DLOG(INFO) << "UsbEventHandler started.";
|
| - while (running_) {
|
| + base::PlatformThread::SetName("UsbEventDispatcher");
|
| + VLOG(1) << "UsbEventDispatcher started.";
|
| + while (true) {
|
| + {
|
| + base::AutoLock running_guard(running_lock_);
|
| + if (!running_) break;
|
| + }
|
| libusb_handle_events(context_);
|
| }
|
| - DLOG(INFO) << "UsbEventHandler shutting down.";
|
| - libusb_exit(context_);
|
| -
|
| - delete this;
|
| + VLOG(1) << "UsbEventDispatcher shutting down.";
|
| }
|
|
|
| void Stop() {
|
| - running_ = false;
|
| + {
|
| + base::AutoLock running_guard(running_lock_);
|
| + running_ = false;
|
| + }
|
| + // Send an event to interrupt the the polling.
|
| + libusb_send_event(context_);
|
| + // Wait for the thread to exit.
|
| + base::PlatformThread::Join(thread_handle);
|
| }
|
|
|
| + volatile bool running_;
|
| + base::Lock running_lock_;
|
| + const PlatformUsbContext context_;
|
| + PlatformThreadHandle thread_handle;
|
| + DISALLOW_COPY_AND_ASSIGN(UsbEventHandler);
|
| +};
|
| +
|
| +UsbEventHandler::UsbEventHandler(PlatformUsbContext context)
|
| + : running_(true), context_(context), thread_handle(0) {
|
| + base::PlatformThread::Create(0, this, &thread_handle);
|
| +}
|
| +
|
| +UsbEventHandler::~UsbEventHandler() {}
|
| +
|
| +// Ref-counted wrapper for PlatformUsbContext.
|
| +// It also manages the life-cycle of UsbEventHandler
|
| +class UsbContext : public RefCountedThreadSafe<UsbContext> {
|
| + public:
|
| + UsbContext();
|
| + PlatformUsbContext context() const { return context_; }
|
| +
|
| private:
|
| - bool running_;
|
| - PlatformUsbContext context_;
|
| + friend class RefCountedThreadSafe<UsbContext>;
|
|
|
| - DISALLOW_EVIL_CONSTRUCTORS(UsbEventHandler);
|
| + virtual ~UsbContext();
|
| + PlatformUsbContext context_;
|
| + scoped_ptr<UsbEventHandler> event_handler_;
|
| };
|
|
|
| -UsbService::UsbService() {
|
| +UsbContext::UsbContext()
|
| + : context_(NULL) {
|
| libusb_init(&context_);
|
| - event_handler_ = new UsbEventHandler(context_);
|
| + event_handler_.reset(new UsbEventHandler(context_));
|
| }
|
|
|
| -UsbService::~UsbService() {}
|
| -
|
| -void UsbService::Cleanup() {
|
| +UsbContext::~UsbContext() {
|
| event_handler_->Stop();
|
| - event_handler_ = NULL;
|
| + event_handler_.reset(NULL);
|
| + // The following statement will inform the event handler to stop waiting.
|
| + libusb_exit(context_);
|
| +}
|
| +
|
| +// UsbDevice class uniquely represents a USB devices recognized by libusb and
|
| +// maintains all its opened handles. It is assigned with an unique id by
|
| +// UsbService. Once the device is disconnected it will invalidate all the
|
| +// UsbDeviceHandle objects attached to it. The class is only visible to
|
| +// UsbService and other classes need to access the device using its unique id.
|
| +class UsbDevice : public base::NonThreadSafe {
|
| + public:
|
| + explicit UsbDevice(UsbContext* context, PlatformUsbDevice device,
|
| + const int unique_id, const uint16 vendor_id,
|
| + const uint16 product_id);
|
| + virtual ~UsbDevice();
|
| + PlatformUsbDevice device() const { return device_; }
|
| + int unique_id() const { return unique_id_; }
|
| + int vendor_id() const { return vendor_id_; }
|
| + int product_id() const { return product_id_; }
|
| +
|
| + scoped_refptr<UsbDeviceHandle> OpenDevice(UsbService* service);
|
| + void CloseDeviceHandle(UsbDeviceHandle* device);
|
| +
|
| + private:
|
| + // Retain the context so it will not be release before the destruction
|
| + // of the UsbDevice object.
|
| + scoped_refptr<UsbContext> context_;
|
| + vector<scoped_refptr<UsbDeviceHandle> > handles_;
|
| + const PlatformUsbDevice device_;
|
| + const uint16 unique_id_;
|
| + const uint16 vendor_id_;
|
| + const int product_id_;
|
| + DISALLOW_COPY_AND_ASSIGN(UsbDevice);
|
| +};
|
| +
|
| +UsbDevice::UsbDevice(UsbContext* context, PlatformUsbDevice device,
|
| + const int unique_id, const uint16 vendor_id,
|
| + const uint16 product_id)
|
| + : context_(context),
|
| + device_(device),
|
| + unique_id_(unique_id),
|
| + vendor_id_(vendor_id),
|
| + product_id_(product_id) {
|
| + DCHECK(CalledOnValidThread());
|
| + libusb_ref_device(device_);
|
| +}
|
| +
|
| +UsbDevice::~UsbDevice() {
|
| + DCHECK(CalledOnValidThread());
|
| + libusb_unref_device(device_);
|
| +
|
| + // Device is lost.
|
| + // Invalidates all the opened handle.
|
| + for (vector<scoped_refptr<UsbDeviceHandle> >::iterator it = handles_.begin();
|
| + it != handles_.end(); ++it) {
|
| + it->get()->InternalClose();
|
| + }
|
| + STLClearObject(&handles_);
|
| +}
|
| +
|
| +scoped_refptr<UsbDeviceHandle> UsbDevice::OpenDevice(UsbService* service) {
|
| + DCHECK(CalledOnValidThread());
|
| + PlatformUsbDeviceHandle handle;
|
| + if (0 == libusb_open(device_, &handle)) {
|
| + scoped_refptr<UsbDeviceHandle> wrapper =
|
| + make_scoped_refptr(new UsbDeviceHandle(service, unique_id_, vendor_id_,
|
| + product_id_, handle));
|
| + handles_.push_back(wrapper);
|
| + return wrapper;
|
| + }
|
| + return scoped_refptr<UsbDeviceHandle>();
|
| +}
|
| +
|
| +void UsbDevice::CloseDeviceHandle(UsbDeviceHandle* device) {
|
| + DCHECK(CalledOnValidThread());
|
| + device->InternalClose();
|
| + for (vector<scoped_refptr<UsbDeviceHandle> >::iterator it = handles_.begin();
|
| + it != handles_.end(); ++it) {
|
| + if (it->get() == device) {
|
| + handles_.erase(it);
|
| + return;
|
| + }
|
| + }
|
| +}
|
| +
|
| +UsbService::UsbService()
|
| + : context_(new UsbContext()),
|
| + next_unique_id_(1),
|
| + device_enumeration_scheduled_(false) {
|
| + // This class will be consequently called on FILE thread.
|
| + DetachFromThread();
|
| }
|
|
|
| -void UsbService::FindDevices(const uint16 vendor_id,
|
| - const uint16 product_id,
|
| - int interface_id,
|
| - vector<scoped_refptr<UsbDevice> >* devices,
|
| +UsbService::~UsbService() {
|
| + // The destructor will be called on UI thread.
|
| + DetachFromThread();
|
| +}
|
| +
|
| +void UsbService::Shutdown() {
|
| + context_ = NULL;
|
| + for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
|
| + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, it->second);
|
| + }
|
| + devices_.clear();
|
| +}
|
| +
|
| +void UsbService::FindDevices(const uint16 vendor_id, const uint16 product_id,
|
| + const int interface_id, vector<int>* devices,
|
| const base::Callback<void()>& callback) {
|
| - DCHECK(event_handler_) << "FindDevices called after event handler stopped.";
|
| + DCHECK(CalledOnValidThread());
|
| #if defined(OS_CHROMEOS)
|
| // ChromeOS builds on non-ChromeOS machines (dev) should not attempt to
|
| // use permission broker.
|
| @@ -105,10 +245,10 @@ void UsbService::FindDevices(const uint16 vendor_id,
|
| }
|
|
|
| void UsbService::FindDevicesImpl(const uint16 vendor_id,
|
| - const uint16 product_id,
|
| - vector<scoped_refptr<UsbDevice> >* devices,
|
| + const uint16 product_id, vector<int>* devices,
|
| const base::Callback<void()>& callback,
|
| bool success) {
|
| + DCHECK(CalledOnValidThread());
|
| base::ScopedClosureRunner run_callback(callback);
|
|
|
| devices->clear();
|
| @@ -116,91 +256,92 @@ void UsbService::FindDevicesImpl(const uint16 vendor_id,
|
| // If the permission broker was unable to obtain permission for the specified
|
| // devices then there is no point in attempting to enumerate the devices. On
|
| // platforms without a permission broker, we assume permission is granted.
|
| - if (!success)
|
| - return;
|
| + if (!success) return;
|
|
|
| - DeviceVector enumerated_devices;
|
| - EnumerateDevices(&enumerated_devices);
|
| - if (enumerated_devices.empty())
|
| - return;
|
| + EnumerateDevices();
|
|
|
| - for (unsigned int i = 0; i < enumerated_devices.size(); ++i) {
|
| - PlatformUsbDevice device = enumerated_devices[i].device();
|
| - if (DeviceMatches(device, vendor_id, product_id)) {
|
| - UsbDevice* const wrapper = LookupOrCreateDevice(device);
|
| - if (wrapper)
|
| - devices->push_back(wrapper);
|
| - }
|
| + for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
|
| + if (DeviceMatches(it->second, vendor_id, product_id))
|
| + devices->push_back(it->second->unique_id());
|
| }
|
| }
|
|
|
| -void UsbService::CloseDevice(scoped_refptr<UsbDevice> device) {
|
| - DCHECK(event_handler_) << "CloseDevice called after event handler stopped.";
|
| -
|
| - PlatformUsbDevice platform_device = libusb_get_device(device->handle());
|
| - if (!ContainsKey(devices_, platform_device)) {
|
| - LOG(WARNING) << "CloseDevice called for device we're not tracking!";
|
| - return;
|
| +scoped_refptr<UsbDeviceHandle> UsbService::OpenDevice(int device) {
|
| + DCHECK(CalledOnValidThread());
|
| + EnumerateDevices();
|
| + for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
|
| + if (it->second->unique_id() == device)
|
| + return it->second->OpenDevice(this);
|
| }
|
| -
|
| - devices_.erase(platform_device);
|
| - libusb_close(device->handle());
|
| -}
|
| -
|
| -UsbService::RefCountedPlatformUsbDevice::RefCountedPlatformUsbDevice(
|
| - PlatformUsbDevice device) : device_(device) {
|
| - libusb_ref_device(device_);
|
| + return NULL;
|
| }
|
|
|
| -UsbService::RefCountedPlatformUsbDevice::RefCountedPlatformUsbDevice(
|
| - const RefCountedPlatformUsbDevice& other) : device_(other.device_) {
|
| - libusb_ref_device(device_);
|
| -}
|
| +void UsbService::CloseDeviceHandle(scoped_refptr<UsbDeviceHandle> device) {
|
| + DCHECK(CalledOnValidThread());
|
| + int id = device->device();
|
|
|
| -UsbService::RefCountedPlatformUsbDevice::~RefCountedPlatformUsbDevice() {
|
| - libusb_unref_device(device_);
|
| + for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
|
| + if (it->second->unique_id() == id) {
|
| + it->second->CloseDeviceHandle(device);
|
| + break;
|
| + }
|
| + }
|
| }
|
|
|
| -PlatformUsbDevice UsbService::RefCountedPlatformUsbDevice::device() {
|
| - return device_;
|
| +void UsbService::ScheduleEnumerateDevice() {
|
| + DCHECK(CalledOnValidThread());
|
| + if (device_enumeration_scheduled_)
|
| + return;
|
| + device_enumeration_scheduled_ = true;
|
| + BrowserThread::PostTask(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&UsbService::EnumerateDevices, base::Unretained(this)));
|
| }
|
|
|
| -void UsbService::EnumerateDevices(DeviceVector* output) {
|
| - STLClearObject(output);
|
| -
|
| +void UsbService::EnumerateDevices() {
|
| + DCHECK(CalledOnValidThread());
|
| + device_enumeration_scheduled_ = false;
|
| libusb_device** devices = NULL;
|
| - const ssize_t device_count = libusb_get_device_list(context_, &devices);
|
| + const ssize_t device_count =
|
| + libusb_get_device_list(context_->context(), &devices);
|
| if (device_count < 0)
|
| return;
|
|
|
| - for (int i = 0; i < device_count; ++i) {
|
| - libusb_device* device = devices[i];
|
| - libusb_ref_device(device);
|
| - output->push_back(RefCountedPlatformUsbDevice(device));
|
| + set<int> connected_devices;
|
| + vector<PlatformUsbDevice> disconnected_devices;
|
| +
|
| + // Populates new devices.
|
| + for (ssize_t i = 0; i < device_count; ++i) {
|
| + if (!ContainsKey(devices_, devices[i])) {
|
| + libusb_device_descriptor descriptor;
|
| + if (0 != libusb_get_device_descriptor(devices[i], &descriptor))
|
| + continue;
|
| + devices_[devices[i]] = new UsbDevice(context_.get(), devices[i],
|
| + next_unique_id_, descriptor.idVendor,
|
| + descriptor.idProduct);
|
| + ++next_unique_id_;
|
| + }
|
| + connected_devices.insert(devices_[devices[i]]->unique_id());
|
| + }
|
| +
|
| + // Find disconnected devices.
|
| + for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
|
| + if (!ContainsKey(connected_devices, it->second->unique_id())) {
|
| + disconnected_devices.push_back(it->first);
|
| + }
|
| }
|
|
|
| + // Remove disconnected devices from devices_.
|
| + for (size_t i = 0; i < disconnected_devices.size(); ++i) {
|
| + // This should delete those devices and invalidate their handles.
|
| + // It might take long.
|
| + delete devices_[disconnected_devices[i]];
|
| + devices_.erase(disconnected_devices[i]);
|
| + }
|
| libusb_free_device_list(devices, true);
|
| }
|
|
|
| -bool UsbService::DeviceMatches(PlatformUsbDevice device,
|
| - const uint16 vendor_id,
|
| +bool UsbService::DeviceMatches(const UsbDevice* device, const uint16 vendor_id,
|
| const uint16 product_id) {
|
| - libusb_device_descriptor descriptor;
|
| - if (libusb_get_device_descriptor(device, &descriptor))
|
| - return false;
|
| - return descriptor.idVendor == vendor_id && descriptor.idProduct == product_id;
|
| -}
|
| -
|
| -UsbDevice* UsbService::LookupOrCreateDevice(PlatformUsbDevice device) {
|
| - if (!ContainsKey(devices_, device)) {
|
| - libusb_device_handle* handle = NULL;
|
| - if (libusb_open(device, &handle)) {
|
| - LOG(WARNING) << "Could not open device.";
|
| - return NULL;
|
| - }
|
| -
|
| - UsbDevice* wrapper = new UsbDevice(this, handle);
|
| - devices_[device] = wrapper;
|
| - }
|
| - return devices_[device].get();
|
| + return device->vendor_id() == vendor_id && device->product_id() == product_id;
|
| }
|
|
|