adb: win32: fix USB device hang when resuming from sleep/hibernation

After resuming Windows from sleep or hibernation, USB connections are not
automatically disconnected. Writing to the USB connections does not return any
errors, but read never returns. My theory is that the device saw the host
sleep/hibernation as a disconnect, so the device is waiting for re-auth from the
host as if the host was just connected.

To solve this, detect resume from sleep/hibernation, disconnect all USB
connections and let the device poll thread re-detect the USB devices in 1 sec.

This is done by using a hidden window that receives power notifications. The
hidden window code is based on Chromium's similar code (platform-tools already
includes the Chromium Authors license).

This depends on a change to AdbWinUsbApi.dll that makes AdbCloseHandle(endpoint)
abort any pending IOs and wait for those IOs to be aborted.

I expect that this should solve many adb and Android Studio related bugs
regarding hangs or errors.

Also in this change:

 - Add D() logging for any errors from AdbWinApi.dll API calls.

 - Stop setting errno to Win32 error values which the caller can't really do
   anything with. Stop calling SetLastError() because the callers don't check
   GetLastError() anyway.

 - Check the return value from writing zero length packets.

 - If the full amount of data isn't written, return an error.

 - Upon any usb_read/usb_write error, kick the connection instead of only
   kicking when ERROR_INVALID_HANDLE is encountered.

 - Restructure some code from nested-if-trees to goto-fail to make it easier
   to follow.

 - Delete usb_name() since it isn't thread-safe and it isn't used.

Change-Id: Iffcf5315ad8593d0c7e93012afaabe6fae354ac1
Signed-off-by: Spencer Low <CompareAndSwap@gmail.com>
This commit is contained in:
Spencer Low 2015-07-22 16:17:07 -07:00
parent c70ac57db0
commit a5b06b0ff8
1 changed files with 286 additions and 124 deletions

View File

@ -33,6 +33,10 @@
/** Structure usb_handle describes our connection to the usb device via
AdbWinApi.dll. This structure is returned from usb_open() routine and
is expected in each subsequent call that is accessing the device.
Most members are protected by usb_lock, except for adb_{read,write}_pipe which
rely on AdbWinApi.dll's handle validation and AdbCloseHandle(endpoint)'s
ability to break a thread out of pipe IO.
*/
struct usb_handle {
/// Previous entry in the list of opened usb handles
@ -86,6 +90,9 @@ int recognized_device(usb_handle* handle);
/// registers usb transport for them.
void find_devices();
/// Kicks all USB devices
static void kick_devices();
/// Entry point for thread that polls (every second) for new usb interfaces.
/// This routine calls find_devices in infinite loop.
void* device_poll_thread(void* unused);
@ -111,9 +118,6 @@ void usb_kick(usb_handle* handle);
/// Closes opened usb handle
int usb_close(usb_handle* handle);
/// Gets interface (device) name for an opened usb handle
const char *usb_name(usb_handle* handle);
int known_device_locked(const char* dev_name) {
usb_handle* usb;
@ -177,17 +181,99 @@ void* device_poll_thread(void* unused) {
return NULL;
}
static LRESULT CALLBACK _power_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_POWERBROADCAST:
switch (wParam) {
case PBT_APMRESUMEAUTOMATIC:
// Resuming from sleep or hibernation, so kick all existing USB devices
// and then allow the device_poll_thread to redetect USB devices from
// scratch. If we don't do this, existing USB devices will never respond
// to us because they'll be waiting for the connect/auth handshake.
D("Received (WM_POWERBROADCAST, PBT_APMRESUMEAUTOMATIC) notification, "
"so kicking all USB devices\n");
kick_devices();
return TRUE;
}
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static void* _power_notification_thread(void* unused) {
// This uses a thread with its own window message pump to get power
// notifications. If adb runs from a non-interactive service account, this
// might not work (not sure). If that happens to not work, we could use
// heavyweight WMI APIs to get power notifications. But for the common case
// of a developer's interactive session, a window message pump is more
// appropriate.
D("Created power notification thread\n");
// Window class names are process specific.
static const WCHAR kPowerNotificationWindowClassName[] =
L"PowerNotificationWindow";
// Get the HINSTANCE corresponding to the module that _power_window_proc
// is in (the main module).
const HINSTANCE instance = GetModuleHandleW(NULL);
if (!instance) {
// This is such a common API call that this should never fail.
fatal("GetModuleHandleW failed: %s",
SystemErrorCodeToString(GetLastError()).c_str());
}
WNDCLASSEXW wndclass;
memset(&wndclass, 0, sizeof(wndclass));
wndclass.cbSize = sizeof(wndclass);
wndclass.lpfnWndProc = _power_window_proc;
wndclass.hInstance = instance;
wndclass.lpszClassName = kPowerNotificationWindowClassName;
if (!RegisterClassExW(&wndclass)) {
fatal("RegisterClassExW failed: %s",
SystemErrorCodeToString(GetLastError()).c_str());
}
if (!CreateWindowExW(WS_EX_NOACTIVATE, kPowerNotificationWindowClassName,
L"ADB Power Notification Window", WS_POPUP, 0, 0, 0, 0,
NULL, NULL, instance, NULL)) {
fatal("CreateWindowExW failed: %s",
SystemErrorCodeToString(GetLastError()).c_str());
}
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
// GetMessageW() will return false if a quit message is posted. We don't
// do that, but it might be possible for that to occur when logging off or
// shutting down. Not a big deal since the whole process will be going away
// soon anyway.
D("Power notification thread exiting\n");
return NULL;
}
void usb_init() {
if (!adb_thread_create(device_poll_thread, nullptr)) {
fatal_errno("cannot create input thread");
fatal_errno("cannot create device poll thread");
}
if (!adb_thread_create(_power_notification_thread, nullptr)) {
fatal_errno("cannot create power notification thread");
}
}
usb_handle* do_usb_open(const wchar_t* interface_name) {
unsigned long name_len = 0;
// Allocate our handle
usb_handle* ret = (usb_handle*)malloc(sizeof(usb_handle));
if (NULL == ret)
return NULL;
usb_handle* ret = (usb_handle*)calloc(1, sizeof(usb_handle));
if (NULL == ret) {
D("Could not allocate %u bytes for usb_handle: %s\n", sizeof(usb_handle),
strerror(errno));
goto fail;
}
// Set linkers back to the handle
ret->next = ret;
@ -195,11 +281,10 @@ usb_handle* do_usb_open(const wchar_t* interface_name) {
// Create interface.
ret->adb_interface = AdbCreateInterfaceByName(interface_name);
if (NULL == ret->adb_interface) {
free(ret);
errno = GetLastError();
return NULL;
D("AdbCreateInterfaceByName failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
goto fail;
}
// Open read pipe (endpoint)
@ -207,45 +292,60 @@ usb_handle* do_usb_open(const wchar_t* interface_name) {
AdbOpenDefaultBulkReadEndpoint(ret->adb_interface,
AdbOpenAccessTypeReadWrite,
AdbOpenSharingModeReadWrite);
if (NULL != ret->adb_read_pipe) {
// Open write pipe (endpoint)
ret->adb_write_pipe =
AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface,
AdbOpenAccessTypeReadWrite,
AdbOpenSharingModeReadWrite);
if (NULL != ret->adb_write_pipe) {
// Save interface name
unsigned long name_len = 0;
// First get expected name length
AdbGetInterfaceName(ret->adb_interface,
NULL,
&name_len,
true);
if (0 != name_len) {
ret->interface_name = (char*)malloc(name_len);
if (NULL != ret->interface_name) {
// Now save the name
if (AdbGetInterfaceName(ret->adb_interface,
ret->interface_name,
&name_len,
true)) {
// We're done at this point
return ret;
}
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
}
}
if (NULL == ret->adb_read_pipe) {
D("AdbOpenDefaultBulkReadEndpoint failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
goto fail;
}
// Something went wrong.
int saved_errno = GetLastError();
usb_cleanup_handle(ret);
free(ret);
SetLastError(saved_errno);
// Open write pipe (endpoint)
ret->adb_write_pipe =
AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface,
AdbOpenAccessTypeReadWrite,
AdbOpenSharingModeReadWrite);
if (NULL == ret->adb_write_pipe) {
D("AdbOpenDefaultBulkWriteEndpoint failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
goto fail;
}
// Save interface name
// First get expected name length
AdbGetInterfaceName(ret->adb_interface,
NULL,
&name_len,
true);
if (0 == name_len) {
D("AdbGetInterfaceName returned name length of zero: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
goto fail;
}
ret->interface_name = (char*)malloc(name_len);
if (NULL == ret->interface_name) {
D("Could not allocate %lu bytes for interface_name: %s\n", name_len,
strerror(errno));
goto fail;
}
// Now save the name
if (!AdbGetInterfaceName(ret->adb_interface,
ret->interface_name,
&name_len,
true)) {
D("AdbGetInterfaceName failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
goto fail;
}
// We're done at this point
return ret;
fail:
if (NULL != ret) {
usb_cleanup_handle(ret);
free(ret);
}
return NULL;
}
@ -253,92 +353,130 @@ usb_handle* do_usb_open(const wchar_t* interface_name) {
int usb_write(usb_handle* handle, const void* data, int len) {
unsigned long time_out = 5000;
unsigned long written = 0;
int ret;
int err = 0;
D("usb_write %d\n", len);
if (NULL != handle) {
// Perform write
ret = AdbWriteEndpointSync(handle->adb_write_pipe,
(void*)data,
(unsigned long)len,
&written,
time_out);
int saved_errno = GetLastError();
if (ret) {
// Make sure that we've written what we were asked to write
D("usb_write got: %ld, expected: %d\n", written, len);
if (written == (unsigned long)len) {
if(handle->zero_mask && (len & handle->zero_mask) == 0) {
// Send a zero length packet
AdbWriteEndpointSync(handle->adb_write_pipe,
(void*)data,
0,
&written,
time_out);
}
return 0;
}
} else {
// assume ERROR_INVALID_HANDLE indicates we are disconnected
if (saved_errno == ERROR_INVALID_HANDLE)
usb_kick(handle);
}
errno = saved_errno;
} else {
D("usb_write NULL handle\n");
SetLastError(ERROR_INVALID_HANDLE);
if (NULL == handle) {
D("usb_write was passed NULL handle\n");
err = EINVAL;
goto fail;
}
D("usb_write failed: %d\n", errno);
// Perform write
if (!AdbWriteEndpointSync(handle->adb_write_pipe,
(void*)data,
(unsigned long)len,
&written,
time_out)) {
D("AdbWriteEndpointSync failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
err = EIO;
goto fail;
}
// Make sure that we've written what we were asked to write
D("usb_write got: %ld, expected: %d\n", written, len);
if (written != (unsigned long)len) {
// If this occurs, this code should be changed to repeatedly call
// AdbWriteEndpointSync() until all bytes are written.
D("AdbWriteEndpointSync was supposed to write %d, but only wrote %ld\n",
len, written);
err = EIO;
goto fail;
}
if (handle->zero_mask && (len & handle->zero_mask) == 0) {
// Send a zero length packet
if (!AdbWriteEndpointSync(handle->adb_write_pipe,
(void*)data,
0,
&written,
time_out)) {
D("AdbWriteEndpointSync of zero length packet failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
err = EIO;
goto fail;
}
}
return 0;
fail:
// Any failure should cause us to kick the device instead of leaving it a
// zombie state with potential to hang.
if (NULL != handle) {
D("Kicking device due to error in usb_write\n");
usb_kick(handle);
}
D("usb_write failed\n");
errno = err;
return -1;
}
int usb_read(usb_handle *handle, void* data, int len) {
unsigned long time_out = 0;
unsigned long read = 0;
int err = 0;
D("usb_read %d\n", len);
if (handle != nullptr) {
while (len > 0) {
int ret = AdbReadEndpointSync(handle->adb_read_pipe, data, len, &read, time_out);
int saved_errno = GetLastError();
D("usb_write got: %ld, expected: %d, errno: %d\n", read, len, saved_errno);
if (ret) {
data = (char *)data + read;
len -= read;
if (len == 0)
return 0;
} else {
// assume ERROR_INVALID_HANDLE indicates we are disconnected
if (saved_errno == ERROR_INVALID_HANDLE)
usb_kick(handle);
break;
}
errno = saved_errno;
}
} else {
D("usb_read NULL handle\n");
SetLastError(ERROR_INVALID_HANDLE);
if (NULL == handle) {
D("usb_read was passed NULL handle\n");
err = EINVAL;
goto fail;
}
D("usb_read failed: %d\n", errno);
while (len > 0) {
if (!AdbReadEndpointSync(handle->adb_read_pipe, data, len, &read,
time_out)) {
D("AdbReadEndpointSync failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
err = EIO;
goto fail;
}
D("usb_read got: %ld, expected: %d\n", read, len);
data = (char *)data + read;
len -= read;
}
return 0;
fail:
// Any failure should cause us to kick the device instead of leaving it a
// zombie state with potential to hang.
if (NULL != handle) {
D("Kicking device due to error in usb_read\n");
usb_kick(handle);
}
D("usb_read failed\n");
errno = err;
return -1;
}
// Wrapper around AdbCloseHandle() that logs diagnostics.
static void _adb_close_handle(ADBAPIHANDLE adb_handle) {
if (!AdbCloseHandle(adb_handle)) {
D("AdbCloseHandle(%p) failed: %s\n", adb_handle,
SystemErrorCodeToString(GetLastError()).c_str());
}
}
void usb_cleanup_handle(usb_handle* handle) {
D("usb_cleanup_handle\n");
if (NULL != handle) {
if (NULL != handle->interface_name)
free(handle->interface_name);
// AdbCloseHandle(pipe) will break any threads out of pending IO calls and
// wait until the pipe no longer uses the interface. Then we can
// AdbCloseHandle() the interface.
if (NULL != handle->adb_write_pipe)
AdbCloseHandle(handle->adb_write_pipe);
_adb_close_handle(handle->adb_write_pipe);
if (NULL != handle->adb_read_pipe)
AdbCloseHandle(handle->adb_read_pipe);
_adb_close_handle(handle->adb_read_pipe);
if (NULL != handle->adb_interface)
AdbCloseHandle(handle->adb_interface);
_adb_close_handle(handle->adb_interface);
handle->interface_name = NULL;
handle->adb_write_pipe = NULL;
@ -347,16 +485,22 @@ void usb_cleanup_handle(usb_handle* handle) {
}
}
static void usb_kick_locked(usb_handle* handle) {
// The reason the lock must be acquired before calling this function is in
// case multiple threads are trying to kick the same device at the same time.
usb_cleanup_handle(handle);
}
void usb_kick(usb_handle* handle) {
D("usb_kick\n");
if (NULL != handle) {
adb_mutex_lock(&usb_lock);
usb_cleanup_handle(handle);
usb_kick_locked(handle);
adb_mutex_unlock(&usb_lock);
} else {
SetLastError(ERROR_INVALID_HANDLE);
errno = ERROR_INVALID_HANDLE;
errno = EINVAL;
}
}
@ -384,16 +528,6 @@ int usb_close(usb_handle* handle) {
return 0;
}
const char *usb_name(usb_handle* handle) {
if (NULL == handle) {
SetLastError(ERROR_INVALID_HANDLE);
errno = ERROR_INVALID_HANDLE;
return NULL;
}
return (const char*)handle->interface_name;
}
int recognized_device(usb_handle* handle) {
if (NULL == handle)
return 0;
@ -403,6 +537,8 @@ int recognized_device(usb_handle* handle) {
if (!AdbGetUsbDeviceDescriptor(handle->adb_interface,
&device_desc)) {
D("AdbGetUsbDeviceDescriptor failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
return 0;
}
@ -411,6 +547,8 @@ int recognized_device(usb_handle* handle) {
if (!AdbGetUsbInterfaceDescriptor(handle->adb_interface,
&interf_desc)) {
D("AdbGetUsbInterfaceDescriptor failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
return 0;
}
@ -427,6 +565,10 @@ int recognized_device(usb_handle* handle) {
// assuming zero is a valid bulk endpoint ID
if (AdbGetEndpointInformation(handle->adb_interface, 0, &endpoint_info)) {
handle->zero_mask = endpoint_info.max_packet_size - 1;
D("device zero_mask: 0x%x\n", handle->zero_mask);
} else {
D("AdbGetEndpointInformation failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
}
}
@ -448,8 +590,11 @@ void find_devices() {
ADBAPIHANDLE enum_handle =
AdbEnumInterfaces(usb_class_id, true, true, true);
if (NULL == enum_handle)
if (NULL == enum_handle) {
D("AdbEnumInterfaces failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
return;
}
while (AdbNextInterface(enum_handle, next_interface, &entry_buffer_size)) {
// TODO: FIXME - temp hack converting wchar_t into char.
@ -486,7 +631,8 @@ void find_devices() {
free(handle);
}
} else {
D("cannot get serial number\n");
D("cannot get serial number: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
usb_cleanup_handle(handle);
free(handle);
}
@ -500,5 +646,21 @@ void find_devices() {
entry_buffer_size = sizeof(entry_buffer);
}
AdbCloseHandle(enum_handle);
if (GetLastError() != ERROR_NO_MORE_ITEMS) {
// Only ERROR_NO_MORE_ITEMS is expected at the end of enumeration.
D("AdbNextInterface failed: %s\n",
SystemErrorCodeToString(GetLastError()).c_str());
}
_adb_close_handle(enum_handle);
}
static void kick_devices() {
// Need to acquire lock to safely walk the list which might be modified
// by another thread.
adb_mutex_lock(&usb_lock);
for (usb_handle* usb = handle_list.next; usb != &handle_list; usb = usb->next) {
usb_kick_locked(usb);
}
adb_mutex_unlock(&usb_lock);
}