Retry stalled USB bulk transfers during a modem switch operation.

This CL modifies UsbModemSwitchOperation to handle USB halt conditions
better:
- Instead of clearing the halt condition on the bulk endpoint before
  every transfer, clear the halt condition on the output bulk endpoint
  (and also the input bulk endpoint if we expect a response after each
  outgoing message) at the beginning of the switch operation.
- If a bulk transfer stalls, clear the halt condition on the endpoint
  and retry the transfer.
- Clear any halt condition at the end of the switch operation.

BUG=chromium:304960
TEST=Tested the following:
1. Build and run unit tests.
2. Verify on daisy and peppy that mist successfully switches the ZTE
   MF190 dongle from mass storage mode to the modem mode. On peppy,
   USB bulk transfers may stall, but mist should clear the halt
   condition on the endpoint and retry the transfer.

Change-Id: I229ea2b062c7da50a7f40ac42d6bdef1934dc8f0
Reviewed-on: https://chromium-review.googlesource.com/172161
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/usb_modem_switch_operation.cc b/usb_modem_switch_operation.cc
index 42268d4..c94cd98 100644
--- a/usb_modem_switch_operation.cc
+++ b/usb_modem_switch_operation.cc
@@ -228,8 +228,9 @@
 
   interface_number_ = interface_descriptor->GetInterfaceNumber();
   out_endpoint_address_ = out_endpoint_descriptor->GetEndpointAddress();
+  bool expect_response = switch_context_->modem_info()->expect_response();
 
-  if (switch_context_->modem_info()->expect_response()) {
+  if (expect_response) {
     scoped_ptr<UsbEndpointDescriptor> in_endpoint_descriptor(
         interface_descriptor->GetEndpointDescriptorByTransferTypeAndDirection(
             kUsbTransferTypeBulk, kUsbDirectionIn));
@@ -260,6 +261,18 @@
     return;
   }
 
+  if (!ClearHalt(out_endpoint_address_)) {
+    Complete(false);
+    return;
+  }
+
+  if (expect_response) {
+    if (!ClearHalt(in_endpoint_address_)) {
+      Complete(false);
+      return;
+    }
+  }
+
   interface_claimed_ = true;
   message_index_ = 0;
   num_usb_messages_ = switch_context_->modem_info()->usb_message_size();
@@ -272,6 +285,16 @@
   ScheduleTask(&UsbModemSwitchOperation::SendMessageToMassStorageEndpoint);
 }
 
+bool UsbModemSwitchOperation::ClearHalt(uint8 endpoint_address) {
+  if (device_->ClearHalt(endpoint_address))
+    return true;
+
+  LOG(ERROR) << StringPrintf(
+      "Could not clear halt condition for endpoint %u: %s",
+      endpoint_address, device_->error().ToString());
+  return false;
+}
+
 void UsbModemSwitchOperation::SendMessageToMassStorageEndpoint() {
   CHECK_LT(message_index_, num_usb_messages_);
 
@@ -318,14 +341,6 @@
     UsbTransferCompletionHandler completion_handler) {
   CHECK_GT(length, 0);
 
-  if (!device_->ClearHalt(endpoint_address)) {
-    LOG(ERROR) << StringPrintf(
-        "Could not clear halt condition for endpoint %u: %s",
-        endpoint_address, device_->error().ToString());
-    Complete(false);
-    return;
-  }
-
   scoped_ptr<UsbBulkTransfer> bulk_transfer(new UsbBulkTransfer());
   if (!bulk_transfer->Initialize(*device_,
                                  endpoint_address,
@@ -366,6 +381,16 @@
   // Keep the bulk transfer valid until this method goes out of scope.
   scoped_ptr<UsbBulkTransfer> scoped_bulk_transfer = bulk_transfer_.Pass();
 
+  if (transfer->GetStatus() == kUsbTransferStatusStall) {
+    if (!ClearHalt(transfer->GetEndpointAddress())) {
+      Complete(false);
+      return;
+    }
+
+    ScheduleTask(&UsbModemSwitchOperation::SendMessageToMassStorageEndpoint);
+    return;
+  }
+
   if (!transfer->IsCompletedWithExpectedLength(transfer->GetLength())) {
     LOG(ERROR) << StringPrintf(
                       "Could not successfully send USB message (%d/%d).",
@@ -397,6 +422,17 @@
   // Keep the bulk transfer valid until this method goes out of scope.
   scoped_ptr<UsbBulkTransfer> scoped_bulk_transfer = bulk_transfer_.Pass();
 
+  if (transfer->GetStatus() == kUsbTransferStatusStall) {
+    if (!ClearHalt(transfer->GetEndpointAddress())) {
+      Complete(false);
+      return;
+    }
+
+    ScheduleTask(
+        &UsbModemSwitchOperation::ReceiveMessageFromMassStorageEndpoint);
+    return;
+  }
+
   if (!transfer->IsCompletedWithExpectedLength(kExpectedResponseLength)) {
     LOG(ERROR) << StringPrintf(
                       "Could not successfully receive USB message (%d/%d).",
@@ -420,6 +456,12 @@
     return;
   }
 
+  // Be a bit cautious, clear any halt condition on the bulk endpoints, but
+  // ignore any error as the device may already disconnect from USB.
+  ClearHalt(out_endpoint_address_);
+  if (switch_context_->modem_info()->expect_response())
+    ClearHalt(in_endpoint_address_);
+
   // After sending the last message (and receiving its response, if expected),
   // wait for the device to reconnect.
   pending_task_.Cancel();
diff --git a/usb_modem_switch_operation.h b/usb_modem_switch_operation.h
index a3253b1..8fbd278 100644
--- a/usb_modem_switch_operation.h
+++ b/usb_modem_switch_operation.h
@@ -90,6 +90,10 @@
   // Opens the device and claims the mass storage interface on the device.
   void OpenDeviceAndClaimMassStorageInterface();
 
+  // Clears the halt condition on the endpoint at |endpoint_address|. Returns
+  // true on success.
+  bool ClearHalt(uint8 endpoint_address);
+
   // Sends a special USB message to the mass storage endpoint of the device.
   void SendMessageToMassStorageEndpoint();