| // Copyright 2013 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "brillo/usb/usb_manager.h" | |
| #include <libusb.h> | |
| #include <poll.h> | |
| #include <memory> | |
| #include <utility> | |
| #include <base/check.h> | |
| #include <base/functional/bind.h> | |
| #include <base/logging.h> | |
| #include <base/memory/free_deleter.h> | |
| #include <base/memory/ptr_util.h> | |
| #include <brillo/streams/stream_utils.h> | |
| #include <base/strings/stringprintf.h> | |
| #include <brillo/usb/usb_device.h> | |
| #include <brillo/usb/usb_device_descriptor.h> | |
| namespace brillo { | |
| namespace { | |
| brillo::Stream::AccessMode ConvertEventFlagsToWatchMode( | |
| short events) { // NOLINT | |
| if ((events & POLLIN) && (events & POLLOUT)) | |
| return brillo::Stream::AccessMode::READ_WRITE; | |
| if (events & POLLIN) | |
| return brillo::Stream::AccessMode::READ; | |
| if (events & POLLOUT) | |
| return brillo::Stream::AccessMode::WRITE; | |
| return brillo::Stream::AccessMode::READ_WRITE; | |
| } | |
| } // namespace | |
| std::unique_ptr<UsbManager> UsbManager::Create() { | |
| // Using new to access non-public constructor. See | |
| // https://abseil.io/tips/134. | |
| std::unique_ptr<UsbManager> usb_manager = base::WrapUnique(new UsbManager()); | |
| if (!usb_manager->Initialize()) { | |
| return nullptr; | |
| } | |
| return usb_manager; | |
| } | |
| UsbManager::UsbManager() : context_(nullptr) {} | |
| UsbManager::~UsbManager() { | |
| if (context_) { | |
| libusb_exit(context_); | |
| context_ = nullptr; | |
| } | |
| } | |
| bool UsbManager::Initialize() { | |
| CHECK(!context_); | |
| int result = libusb_init(&context_); | |
| if (!error_.SetFromLibUsbError(static_cast<libusb_error>(result))) { | |
| LOG(ERROR) << "Could not initialize libusb: " << error_; | |
| return false; | |
| } | |
| if (!StartWatchingPollFileDescriptors()) { | |
| error_.set_type(UsbError::kErrorNotSupported); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void UsbManager::SetDebugLevel(int level) { | |
| CHECK(context_); | |
| libusb_set_option(context_, LIBUSB_OPTION_LOG_LEVEL, level); | |
| } | |
| std::unique_ptr<UsbDevice> UsbManager::GetDevice(uint8_t bus_number, | |
| uint8_t device_address, | |
| uint16_t vendor_id, | |
| uint16_t product_id) { | |
| std::vector<std::unique_ptr<UsbDevice>> devices; | |
| if (!GetDevices(&devices)) | |
| return nullptr; | |
| for (auto& device : devices) { | |
| if (device->GetBusNumber() != bus_number || | |
| device->GetDeviceAddress() != device_address) | |
| continue; | |
| std::unique_ptr<UsbDeviceDescriptor> device_descriptor = | |
| device->GetDeviceDescriptor(); | |
| VLOG(2) << *device_descriptor; | |
| if (device_descriptor->GetVendorId() == vendor_id && | |
| device_descriptor->GetProductId() == product_id) { | |
| return std::move(device); | |
| } | |
| } | |
| error_.set_type(UsbError::kErrorNotFound); | |
| return nullptr; | |
| } | |
| bool UsbManager::GetDevices(std::vector<std::unique_ptr<UsbDevice>>* devices) { | |
| CHECK(context_); | |
| CHECK(devices); | |
| devices->clear(); | |
| libusb_device** device_list = nullptr; | |
| ssize_t result = libusb_get_device_list(context_, &device_list); | |
| if (result < 0) | |
| return error_.SetFromLibUsbError(static_cast<libusb_error>(result)); | |
| for (ssize_t i = 0; i < result; ++i) { | |
| devices->push_back(std::make_unique<UsbDevice>(device_list[i])); | |
| } | |
| // UsbDevice holds a reference count of a libusb_device struct. Thus, | |
| // decrement the reference count of the libusb_device struct in the list by | |
| // one when freeing the list. | |
| libusb_free_device_list(device_list, 1); | |
| return true; | |
| } | |
| void UsbManager::OnPollFileDescriptorAdded(int file_descriptor, | |
| short events, // NOLINT | |
| void* user_data) { | |
| CHECK(user_data); | |
| VLOG(2) << base::StringPrintf( | |
| "Poll file descriptor %d on events 0x%016x added.", file_descriptor, | |
| events); | |
| auto* manager = reinterpret_cast<UsbManager*>(user_data); | |
| manager->StartWatchingFileDescriptor( | |
| file_descriptor, ConvertEventFlagsToWatchMode(events), | |
| base::BindRepeating(&UsbManager::HandleEventsNonBlocking, | |
| manager->weak_factory_.GetWeakPtr())); | |
| } | |
| void UsbManager::OnPollFileDescriptorRemoved(int file_descriptor, | |
| void* user_data) { | |
| CHECK(user_data); | |
| VLOG(2) << base::StringPrintf("Poll file descriptor %d removed.", | |
| file_descriptor); | |
| auto* manager = reinterpret_cast<UsbManager*>(user_data); | |
| manager->StopWatchingFileDescriptor(file_descriptor); | |
| } | |
| bool UsbManager::StartWatchingPollFileDescriptors() { | |
| CHECK(context_); | |
| libusb_set_pollfd_notifiers(context_, &OnPollFileDescriptorAdded, | |
| &OnPollFileDescriptorRemoved, this); | |
| std::unique_ptr<const libusb_pollfd*, base::FreeDeleter> pollfd_list( | |
| libusb_get_pollfds(context_)); | |
| if (!pollfd_list) { | |
| LOG(ERROR) << "Could not get file descriptors for monitoring USB events."; | |
| return false; | |
| } | |
| for (const libusb_pollfd** fd_ptr = pollfd_list.get(); *fd_ptr; ++fd_ptr) { | |
| const libusb_pollfd& pollfd = *(*fd_ptr); | |
| VLOG(2) << base::StringPrintf( | |
| "Poll file descriptor %d for events 0x%016x added.", pollfd.fd, | |
| pollfd.events); | |
| if (!StartWatchingFileDescriptor( | |
| pollfd.fd, ConvertEventFlagsToWatchMode(pollfd.events), | |
| base::BindRepeating(&UsbManager::HandleEventsNonBlocking, | |
| weak_factory_.GetWeakPtr()))) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void UsbManager::HandleEventsNonBlocking() { | |
| CHECK(context_); | |
| timeval zero_tv = {0}; | |
| int result = | |
| libusb_handle_events_timeout_completed(context_, &zero_tv, nullptr); | |
| UsbError error(static_cast<libusb_error>(result)); | |
| LOG_IF(ERROR, !error.IsSuccess()) << "Could not handle USB events: " << error; | |
| } | |
| bool UsbManager::StartWatchingFileDescriptor( | |
| int file_descriptor, | |
| brillo::Stream::AccessMode mode, | |
| const base::RepeatingClosure& callback) { | |
| CHECK_GE(file_descriptor, 0); | |
| Watcher& watcher = file_descriptor_watchers_[file_descriptor]; | |
| // Reset once if it is already being watched. | |
| watcher.read_watcher = nullptr; | |
| watcher.write_watcher = nullptr; | |
| bool success = true; | |
| if (brillo::stream_utils::IsReadAccessMode(mode)) { | |
| watcher.read_watcher = | |
| base::FileDescriptorWatcher::WatchReadable(file_descriptor, callback); | |
| success = watcher.read_watcher.get(); | |
| } | |
| if (brillo::stream_utils::IsWriteAccessMode(mode)) { | |
| watcher.write_watcher = | |
| base::FileDescriptorWatcher::WatchWritable(file_descriptor, callback); | |
| success = success && watcher.write_watcher.get(); | |
| } | |
| if (!success) { | |
| LOG(ERROR) << "Could not watch file descriptor: " << file_descriptor; | |
| file_descriptor_watchers_.erase(file_descriptor); | |
| return false; | |
| } | |
| VLOG(2) << "Started watching file descriptor: " << file_descriptor; | |
| return true; | |
| } | |
| bool UsbManager::StopWatchingFileDescriptor(int file_descriptor) { | |
| CHECK_GE(file_descriptor, 0); | |
| auto it = file_descriptor_watchers_.find(file_descriptor); | |
| if (it == file_descriptor_watchers_.end()) { | |
| LOG(ERROR) << "File descriptor " << file_descriptor | |
| << " is not being watched."; | |
| return false; | |
| } | |
| file_descriptor_watchers_.erase(it); | |
| VLOG(2) << "Stopped watching file descriptor: " << file_descriptor; | |
| return true; | |
| } | |
| void UsbManager::StopWatchingAllFileDescriptors() { | |
| file_descriptor_watchers_.clear(); | |
| } | |
| } // namespace brillo |