When device goes offline, user usually has to manually replug the usb device. This patch tries to solve two offline situations, all because when adb on host is killed, the adbd on device is not notified. 1. When adb server is killed while pushing a large file to device, the device is still reading the unfinished large message. So the device thinks of the CNXN message as part of the previous unfinished message, so it doesn't reply and the device is in offline state. The solution is to add a write_msg_lock in atransport struct. And it kicks the transport only after sending a whole message. By kicking all transports before exit, we ensure that we don't write part of a message to any device. So next time we start adb server, the device should be waiting for a new message. 2. When adb server is killed while pulling a large file from device, the device is still trying to send the unfinished large message. So adb on host usually reads data with EOVERFLOW error. This is because adb on host is reading less than one packet sent from device. The solution is to use buffered read on host. The max packet size of bulk transactions in USB 3.0 is 1024 bytes. By preparing an at least 1024 bytes buffer when reading, EOVERFLOW no longer occurs. And teach adb host to ignore wrong messages. To be safe, this patch doesn't change any logic on device. Bug: http://b/32952319 Test: run python -m unittest -q test_device.DeviceOfflineTest Test: on linux/mac/windows with bullhead, ryu. Change-Id: Ib149d30028a62a6f03857b8a95ab5a1d6e9b9c4e
510 lines
18 KiB
C++
510 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "usb.h"
|
|
|
|
#include "sysdeps.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
|
|
#include <libusb/libusb.h>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/quick_exit.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include "adb.h"
|
|
#include "transport.h"
|
|
#include "usb.h"
|
|
|
|
using namespace std::literals;
|
|
|
|
using android::base::StringPrintf;
|
|
|
|
// RAII wrappers for libusb.
|
|
struct ConfigDescriptorDeleter {
|
|
void operator()(libusb_config_descriptor* desc) {
|
|
libusb_free_config_descriptor(desc);
|
|
}
|
|
};
|
|
|
|
using unique_config_descriptor = std::unique_ptr<libusb_config_descriptor, ConfigDescriptorDeleter>;
|
|
|
|
struct DeviceHandleDeleter {
|
|
void operator()(libusb_device_handle* h) {
|
|
libusb_close(h);
|
|
}
|
|
};
|
|
|
|
using unique_device_handle = std::unique_ptr<libusb_device_handle, DeviceHandleDeleter>;
|
|
|
|
struct transfer_info {
|
|
transfer_info(const char* name, uint16_t zero_mask, bool is_bulk_out)
|
|
: name(name),
|
|
transfer(libusb_alloc_transfer(0)),
|
|
is_bulk_out(is_bulk_out),
|
|
zero_mask(zero_mask) {}
|
|
|
|
~transfer_info() {
|
|
libusb_free_transfer(transfer);
|
|
}
|
|
|
|
const char* name;
|
|
libusb_transfer* transfer;
|
|
bool is_bulk_out;
|
|
bool transfer_complete;
|
|
std::condition_variable cv;
|
|
std::mutex mutex;
|
|
uint16_t zero_mask;
|
|
|
|
void Notify() {
|
|
LOG(DEBUG) << "notifying " << name << " transfer complete";
|
|
transfer_complete = true;
|
|
cv.notify_one();
|
|
}
|
|
};
|
|
|
|
namespace libusb {
|
|
struct usb_handle : public ::usb_handle {
|
|
usb_handle(const std::string& device_address, const std::string& serial,
|
|
unique_device_handle&& device_handle, uint8_t interface, uint8_t bulk_in,
|
|
uint8_t bulk_out, size_t zero_mask)
|
|
: device_address(device_address),
|
|
serial(serial),
|
|
closing(false),
|
|
device_handle(device_handle.release()),
|
|
read("read", zero_mask, false),
|
|
write("write", zero_mask, true),
|
|
interface(interface),
|
|
bulk_in(bulk_in),
|
|
bulk_out(bulk_out) {}
|
|
|
|
~usb_handle() {
|
|
Close();
|
|
}
|
|
|
|
void Close() {
|
|
std::unique_lock<std::mutex> lock(device_handle_mutex);
|
|
// Cancelling transfers will trigger more Closes, so make sure this only happens once.
|
|
if (closing) {
|
|
return;
|
|
}
|
|
closing = true;
|
|
|
|
// Make sure that no new transfers come in.
|
|
libusb_device_handle* handle = device_handle;
|
|
if (!handle) {
|
|
return;
|
|
}
|
|
|
|
device_handle = nullptr;
|
|
|
|
// Cancel already dispatched transfers.
|
|
libusb_cancel_transfer(read.transfer);
|
|
libusb_cancel_transfer(write.transfer);
|
|
|
|
libusb_release_interface(handle, interface);
|
|
libusb_close(handle);
|
|
}
|
|
|
|
std::string device_address;
|
|
std::string serial;
|
|
|
|
std::atomic<bool> closing;
|
|
std::mutex device_handle_mutex;
|
|
libusb_device_handle* device_handle;
|
|
|
|
transfer_info read;
|
|
transfer_info write;
|
|
|
|
uint8_t interface;
|
|
uint8_t bulk_in;
|
|
uint8_t bulk_out;
|
|
};
|
|
|
|
static auto& usb_handles = *new std::unordered_map<std::string, std::unique_ptr<usb_handle>>();
|
|
static auto& usb_handles_mutex = *new std::mutex();
|
|
|
|
static std::thread* device_poll_thread = nullptr;
|
|
static std::atomic<bool> terminate_device_poll_thread(false);
|
|
|
|
static std::string get_device_address(libusb_device* device) {
|
|
return StringPrintf("usb:%d:%d", libusb_get_bus_number(device),
|
|
libusb_get_device_address(device));
|
|
}
|
|
|
|
static bool endpoint_is_output(uint8_t endpoint) {
|
|
return (endpoint & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT;
|
|
}
|
|
|
|
static bool should_perform_zero_transfer(uint8_t endpoint, size_t write_length, uint16_t zero_mask) {
|
|
return endpoint_is_output(endpoint) && write_length != 0 && zero_mask != 0 &&
|
|
(write_length & zero_mask) == 0;
|
|
}
|
|
|
|
static void poll_for_devices() {
|
|
libusb_device** list;
|
|
adb_thread_setname("device poll");
|
|
while (!terminate_device_poll_thread) {
|
|
const ssize_t device_count = libusb_get_device_list(nullptr, &list);
|
|
|
|
LOG(VERBOSE) << "found " << device_count << " attached devices";
|
|
|
|
for (ssize_t i = 0; i < device_count; ++i) {
|
|
libusb_device* device = list[i];
|
|
std::string device_address = get_device_address(device);
|
|
std::string device_serial;
|
|
|
|
// Figure out if we want to open the device.
|
|
libusb_device_descriptor device_desc;
|
|
int rc = libusb_get_device_descriptor(device, &device_desc);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to get device descriptor for device at " << device_address
|
|
<< ": " << libusb_error_name(rc);
|
|
}
|
|
|
|
if (device_desc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) {
|
|
// Assume that all Android devices have the device class set to per interface.
|
|
// TODO: Is this assumption valid?
|
|
LOG(VERBOSE) << "skipping device with incorrect class at " << device_address;
|
|
continue;
|
|
}
|
|
|
|
libusb_config_descriptor* config_raw;
|
|
rc = libusb_get_active_config_descriptor(device, &config_raw);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to get active config descriptor for device at "
|
|
<< device_address << ": " << libusb_error_name(rc);
|
|
continue;
|
|
}
|
|
const unique_config_descriptor config(config_raw);
|
|
|
|
// Use size_t for interface_num so <iostream>s don't mangle it.
|
|
size_t interface_num;
|
|
uint16_t zero_mask;
|
|
uint8_t bulk_in = 0, bulk_out = 0;
|
|
bool found_adb = false;
|
|
|
|
for (interface_num = 0; interface_num < config->bNumInterfaces; ++interface_num) {
|
|
const libusb_interface& interface = config->interface[interface_num];
|
|
if (interface.num_altsetting != 1) {
|
|
// Assume that interfaces with alternate settings aren't adb interfaces.
|
|
// TODO: Is this assumption valid?
|
|
LOG(VERBOSE) << "skipping interface with incorrect num_altsetting at "
|
|
<< device_address << " (interface " << interface_num << ")";
|
|
continue;
|
|
}
|
|
|
|
const libusb_interface_descriptor& interface_desc = interface.altsetting[0];
|
|
if (!is_adb_interface(interface_desc.bInterfaceClass,
|
|
interface_desc.bInterfaceSubClass,
|
|
interface_desc.bInterfaceProtocol)) {
|
|
LOG(VERBOSE) << "skipping non-adb interface at " << device_address
|
|
<< " (interface " << interface_num << ")";
|
|
continue;
|
|
}
|
|
|
|
LOG(VERBOSE) << "found potential adb interface at " << device_address
|
|
<< " (interface " << interface_num << ")";
|
|
|
|
bool found_in = false;
|
|
bool found_out = false;
|
|
for (size_t endpoint_num = 0; endpoint_num < interface_desc.bNumEndpoints;
|
|
++endpoint_num) {
|
|
const auto& endpoint_desc = interface_desc.endpoint[endpoint_num];
|
|
const uint8_t endpoint_addr = endpoint_desc.bEndpointAddress;
|
|
const uint8_t endpoint_attr = endpoint_desc.bmAttributes;
|
|
|
|
const uint8_t transfer_type = endpoint_attr & LIBUSB_TRANSFER_TYPE_MASK;
|
|
|
|
if (transfer_type != LIBUSB_TRANSFER_TYPE_BULK) {
|
|
continue;
|
|
}
|
|
|
|
if (endpoint_is_output(endpoint_addr) && !found_out) {
|
|
found_out = true;
|
|
bulk_out = endpoint_addr;
|
|
zero_mask = endpoint_desc.wMaxPacketSize - 1;
|
|
} else if (!endpoint_is_output(endpoint_addr) && !found_in) {
|
|
found_in = true;
|
|
bulk_in = endpoint_addr;
|
|
}
|
|
}
|
|
|
|
if (found_in && found_out) {
|
|
found_adb = true;
|
|
break;
|
|
} else {
|
|
LOG(VERBOSE) << "rejecting potential adb interface at " << device_address
|
|
<< "(interface " << interface_num << "): missing bulk endpoints "
|
|
<< "(found_in = " << found_in << ", found_out = " << found_out
|
|
<< ")";
|
|
}
|
|
}
|
|
|
|
if (!found_adb) {
|
|
LOG(VERBOSE) << "skipping device with no adb interfaces at " << device_address;
|
|
continue;
|
|
}
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(usb_handles_mutex);
|
|
if (usb_handles.find(device_address) != usb_handles.end()) {
|
|
LOG(VERBOSE) << "device at " << device_address
|
|
<< " has already been registered, skipping";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
libusb_device_handle* handle_raw;
|
|
rc = libusb_open(list[i], &handle_raw);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to open usb device at " << device_address << ": "
|
|
<< libusb_error_name(rc);
|
|
continue;
|
|
}
|
|
|
|
unique_device_handle handle(handle_raw);
|
|
LOG(DEBUG) << "successfully opened adb device at " << device_address << ", "
|
|
<< StringPrintf("bulk_in = %#x, bulk_out = %#x", bulk_in, bulk_out);
|
|
|
|
device_serial.resize(255);
|
|
rc = libusb_get_string_descriptor_ascii(
|
|
handle_raw, device_desc.iSerialNumber,
|
|
reinterpret_cast<unsigned char*>(&device_serial[0]), device_serial.length());
|
|
if (rc == 0) {
|
|
LOG(WARNING) << "received empty serial from device at " << device_address;
|
|
continue;
|
|
} else if (rc < 0) {
|
|
LOG(WARNING) << "failed to get serial from device at " << device_address
|
|
<< libusb_error_name(rc);
|
|
continue;
|
|
}
|
|
device_serial.resize(rc);
|
|
|
|
// WARNING: this isn't released via RAII.
|
|
rc = libusb_claim_interface(handle.get(), interface_num);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to claim adb interface for device '" << device_serial << "'"
|
|
<< libusb_error_name(rc);
|
|
continue;
|
|
}
|
|
|
|
for (uint8_t endpoint : {bulk_in, bulk_out}) {
|
|
rc = libusb_clear_halt(handle.get(), endpoint);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to clear halt on device '" << device_serial
|
|
<< "' endpoint 0x" << std::hex << endpoint << ": "
|
|
<< libusb_error_name(rc);
|
|
libusb_release_interface(handle.get(), interface_num);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto result =
|
|
std::make_unique<usb_handle>(device_address, device_serial, std::move(handle),
|
|
interface_num, bulk_in, bulk_out, zero_mask);
|
|
usb_handle* usb_handle_raw = result.get();
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(usb_handles_mutex);
|
|
usb_handles[device_address] = std::move(result);
|
|
}
|
|
|
|
register_usb_transport(usb_handle_raw, device_serial.c_str(), device_address.c_str(), 1);
|
|
|
|
LOG(INFO) << "registered new usb device '" << device_serial << "'";
|
|
}
|
|
libusb_free_device_list(list, 1);
|
|
|
|
std::this_thread::sleep_for(500ms);
|
|
}
|
|
}
|
|
|
|
void usb_init() {
|
|
LOG(DEBUG) << "initializing libusb...";
|
|
int rc = libusb_init(nullptr);
|
|
if (rc != 0) {
|
|
LOG(FATAL) << "failed to initialize libusb: " << libusb_error_name(rc);
|
|
}
|
|
|
|
// Spawn a thread for libusb_handle_events.
|
|
std::thread([]() {
|
|
adb_thread_setname("libusb");
|
|
while (true) {
|
|
libusb_handle_events(nullptr);
|
|
}
|
|
}).detach();
|
|
|
|
// Spawn a thread to do device enumeration.
|
|
// TODO: Use libusb_hotplug_* instead?
|
|
device_poll_thread = new std::thread(poll_for_devices);
|
|
android::base::at_quick_exit([]() {
|
|
terminate_device_poll_thread = true;
|
|
device_poll_thread->join();
|
|
});
|
|
}
|
|
|
|
// Dispatch a libusb transfer, unlock |device_lock|, and then wait for the result.
|
|
static int perform_usb_transfer(usb_handle* h, transfer_info* info,
|
|
std::unique_lock<std::mutex> device_lock) {
|
|
libusb_transfer* transfer = info->transfer;
|
|
|
|
transfer->user_data = info;
|
|
transfer->callback = [](libusb_transfer* transfer) {
|
|
transfer_info* info = static_cast<transfer_info*>(transfer->user_data);
|
|
|
|
LOG(DEBUG) << info->name << " transfer callback entered";
|
|
|
|
// Make sure that the original submitter has made it to the condition_variable wait.
|
|
std::unique_lock<std::mutex> lock(info->mutex);
|
|
|
|
LOG(DEBUG) << info->name << " callback successfully acquired lock";
|
|
|
|
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
|
LOG(WARNING) << info->name
|
|
<< " transfer failed: " << libusb_error_name(transfer->status);
|
|
info->Notify();
|
|
return;
|
|
}
|
|
|
|
// usb_read() can return when receiving some data.
|
|
if (info->is_bulk_out && transfer->actual_length != transfer->length) {
|
|
LOG(DEBUG) << info->name << " transfer incomplete, resubmitting";
|
|
transfer->length -= transfer->actual_length;
|
|
transfer->buffer += transfer->actual_length;
|
|
int rc = libusb_submit_transfer(transfer);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to submit " << info->name
|
|
<< " transfer: " << libusb_error_name(rc);
|
|
transfer->status = LIBUSB_TRANSFER_ERROR;
|
|
info->Notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (should_perform_zero_transfer(transfer->endpoint, transfer->length, info->zero_mask)) {
|
|
LOG(DEBUG) << "submitting zero-length write";
|
|
transfer->length = 0;
|
|
int rc = libusb_submit_transfer(transfer);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to submit zero-length write: " << libusb_error_name(rc);
|
|
transfer->status = LIBUSB_TRANSFER_ERROR;
|
|
info->Notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
LOG(VERBOSE) << info->name << "transfer fully complete";
|
|
info->Notify();
|
|
};
|
|
|
|
LOG(DEBUG) << "locking " << info->name << " transfer_info mutex";
|
|
std::unique_lock<std::mutex> lock(info->mutex);
|
|
info->transfer_complete = false;
|
|
LOG(DEBUG) << "submitting " << info->name << " transfer";
|
|
int rc = libusb_submit_transfer(transfer);
|
|
if (rc != 0) {
|
|
LOG(WARNING) << "failed to submit " << info->name << " transfer: " << libusb_error_name(rc);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
LOG(DEBUG) << info->name << " transfer successfully submitted";
|
|
device_lock.unlock();
|
|
info->cv.wait(lock, [info]() { return info->transfer_complete; });
|
|
if (transfer->status != 0) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_write(usb_handle* h, const void* d, int len) {
|
|
LOG(DEBUG) << "usb_write of length " << len;
|
|
|
|
std::unique_lock<std::mutex> lock(h->device_handle_mutex);
|
|
if (!h->device_handle) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
transfer_info* info = &h->write;
|
|
info->transfer->dev_handle = h->device_handle;
|
|
info->transfer->flags = 0;
|
|
info->transfer->endpoint = h->bulk_out;
|
|
info->transfer->type = LIBUSB_TRANSFER_TYPE_BULK;
|
|
info->transfer->length = len;
|
|
info->transfer->buffer = reinterpret_cast<unsigned char*>(const_cast<void*>(d));
|
|
info->transfer->num_iso_packets = 0;
|
|
|
|
int rc = perform_usb_transfer(h, info, std::move(lock));
|
|
LOG(DEBUG) << "usb_write(" << len << ") = " << rc;
|
|
return rc;
|
|
}
|
|
|
|
int usb_read(usb_handle* h, void* d, int len) {
|
|
LOG(DEBUG) << "usb_read of length " << len;
|
|
|
|
std::unique_lock<std::mutex> lock(h->device_handle_mutex);
|
|
if (!h->device_handle) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
transfer_info* info = &h->read;
|
|
info->transfer->dev_handle = h->device_handle;
|
|
info->transfer->flags = 0;
|
|
info->transfer->endpoint = h->bulk_in;
|
|
info->transfer->type = LIBUSB_TRANSFER_TYPE_BULK;
|
|
info->transfer->length = len;
|
|
info->transfer->buffer = reinterpret_cast<unsigned char*>(d);
|
|
info->transfer->num_iso_packets = 0;
|
|
|
|
int rc = perform_usb_transfer(h, info, std::move(lock));
|
|
LOG(DEBUG) << "usb_read(" << len << ") = " << rc << ", actual_length "
|
|
<< info->transfer->actual_length;
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
return info->transfer->actual_length;
|
|
}
|
|
|
|
int usb_close(usb_handle* h) {
|
|
std::unique_lock<std::mutex> lock(usb_handles_mutex);
|
|
auto it = usb_handles.find(h->device_address);
|
|
if (it == usb_handles.end()) {
|
|
LOG(FATAL) << "attempted to close unregistered usb_handle for '" << h->serial << "'";
|
|
}
|
|
usb_handles.erase(h->device_address);
|
|
return 0;
|
|
}
|
|
|
|
void usb_kick(usb_handle* h) {
|
|
h->Close();
|
|
}
|
|
} // namespace libusb
|