Support devices that expose a MBIM interface.

This CL modifies UsbModemSwitchOperation to first check if the device
has a USB configuration that exposes a MBIM interface. If a MBIM
configuration exists, the switch operation completes by selecting that
configuration. Otherwise, the switch operation proceeds the same way as
it used to be by sending special USB messages.

BUG=chromium:349256
TEST=Tested the following:
1. Build and run unit tests.
2. Verify that mist successfully switches supported non-MBIM dongles
   from the mass storage mode to the modem mode.
3. Verify that mist successfully selects the MBIM configuration for
   supported MBIM dongles.

Change-Id: I288517f9f153ec2dc9a8ece22dcffacfc559baae
Reviewed-on: https://chromium-review.googlesource.com/188988
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/usb_constants.h b/usb_constants.h
index 0d61579..9028f7c 100644
--- a/usb_constants.h
+++ b/usb_constants.h
@@ -13,9 +13,15 @@
 
 // USB class codes.
 enum UsbClass {
+  kUsbClassCommunication = 0x02,
   kUsbClassMassStorage = 0x08
 };
 
+// USB subclass codes.
+enum UsbSubClass {
+  kUsbSubClassMBIM = 0x0e
+};
+
 // USB endpoint direction, which is one-to-one equivalent to the
 // libusb_endpoint_direction enum defined in libusb 1.0.
 enum UsbDirection {
@@ -60,6 +66,9 @@
   kUsbTransferStatusUnknown
 };
 
+// Invalid USB configuration value
+const int kUsbConfigurationValueInvalid = -1;
+
 // Returns the USB endpoint direction of |endpoint_address|.
 UsbDirection GetUsbDirectionOfEndpointAddress(uint8 endpoint_address);
 
diff --git a/usb_modem_switch_operation.cc b/usb_modem_switch_operation.cc
index 417480a..483b582 100644
--- a/usb_modem_switch_operation.cc
+++ b/usb_modem_switch_operation.cc
@@ -102,7 +102,7 @@
   LOG(INFO) << "Starting the modem switch operation in "
             << initial_delay.InMilliseconds() << " ms.";
   ScheduleDelayedTask(
-      &UsbModemSwitchOperation::OpenDeviceAndClaimMassStorageInterface,
+      &UsbModemSwitchOperation::OpenDeviceAndSelectInterface,
       initial_delay);
 }
 
@@ -149,6 +149,113 @@
       Bind(completion_callback_, Unretained(this), success));
 }
 
+void UsbModemSwitchOperation::DetachAllKernelDrivers() {
+  scoped_ptr<UsbConfigDescriptor> config_descriptor(
+      device_->GetActiveConfigDescriptor());
+  if (!config_descriptor)
+    return;
+
+  for (uint8 interface_number = 0;
+       interface_number < config_descriptor->GetNumInterfaces();
+       ++interface_number) {
+    if (!device_->DetachKernelDriver(interface_number) &&
+        // UsbDevice::DetachKernelDriver returns UsbError::kErrorNotFound when
+        // there is no driver attached to the device.
+        device_->error().type() != UsbError::kErrorNotFound) {
+      LOG(ERROR) << StringPrintf(
+          "Could not detach kernel driver from interface %u: %s",
+          interface_number, device_->error().ToString());
+      // Continue to detach other kernel drivers in case of an error.
+    }
+  }
+}
+
+int UsbModemSwitchOperation::GetMBIMConfigurationValue() {
+  CHECK(device_);
+
+  scoped_ptr<UsbDeviceDescriptor> device_descriptor(
+      device_->GetDeviceDescriptor());
+  if (!device_descriptor) {
+    LOG(ERROR) << "Could not get device descriptor: " << device_->error();
+    return kUsbConfigurationValueInvalid;
+  }
+
+  VLOG(2) << *device_descriptor;
+
+  for (uint8 config_index = 0;
+       config_index < device_descriptor->GetNumConfigurations();
+       ++config_index) {
+    scoped_ptr<UsbConfigDescriptor> config_descriptor(
+        device_->GetConfigDescriptor(config_index));
+    if (!config_descriptor)
+      continue;
+
+    VLOG(2) << *config_descriptor;
+
+    for (uint8 interface_number = 0;
+         interface_number < config_descriptor->GetNumInterfaces();
+         ++interface_number) {
+      scoped_ptr<UsbInterface> interface(
+          config_descriptor->GetInterface(interface_number));
+      if (!interface)
+        continue;
+
+      scoped_ptr<UsbInterfaceDescriptor> interface_descriptor(
+          interface->GetAlternateSetting(
+              kDefaultUsbInterfaceAlternateSettingIndex));
+      if (!interface_descriptor)
+        continue;
+
+      VLOG(2) << *interface_descriptor;
+
+      if (interface_descriptor->GetInterfaceClass() != kUsbClassCommunication ||
+          interface_descriptor->GetInterfaceSubclass() != kUsbSubClassMBIM)
+        continue;
+
+      int configuration_value = config_descriptor->GetConfigurationValue();
+      LOG(INFO) << StringPrintf("Found MBIM support at configuration %d "
+                                "on device '%s'.",
+                                configuration_value,
+                                switch_context_->sys_path().c_str());
+      return configuration_value;
+    }
+  }
+  return kUsbConfigurationValueInvalid;
+}
+
+bool UsbModemSwitchOperation::SetConfiguration(int configuration) {
+  scoped_ptr<UsbConfigDescriptor> config_descriptor(
+      device_->GetActiveConfigDescriptor());
+  if (!config_descriptor) {
+    LOG(ERROR) << "Could not get active configuration descriptor: "
+               << device_->error();
+    return false;
+  }
+
+  if (config_descriptor->GetConfigurationValue() == configuration) {
+    LOG(INFO) << StringPrintf("Device '%s' is already in configuration %d. ",
+                              switch_context_->sys_path().c_str(),
+                              configuration);
+    return true;
+  }
+
+  DetachAllKernelDrivers();
+  if (device_->SetConfiguration(configuration)) {
+    LOG(INFO) << StringPrintf(
+                     "Successfully selected configuration %d for device '%s'.",
+                     configuration,
+                     switch_context_->sys_path().c_str());
+    return true;
+  }
+
+  LOG(ERROR) << StringPrintf(
+                    "Could not select configuration %d for device '%s': %s",
+                    configuration,
+                    switch_context_->sys_path().c_str(),
+                    device_->error().ToString());
+  return false;
+}
+
 void UsbModemSwitchOperation::CloseDevice() {
   if (!device_)
     return;
@@ -169,7 +276,7 @@
   device_.reset();
 }
 
-void UsbModemSwitchOperation::OpenDeviceAndClaimMassStorageInterface() {
+void UsbModemSwitchOperation::OpenDeviceAndSelectInterface() {
   CHECK(!interface_claimed_);
 
   device_.reset(
@@ -206,6 +313,15 @@
   }
   VLOG(2) << *config_descriptor;
 
+  int mbim_configuration_value = GetMBIMConfigurationValue();
+  if (mbim_configuration_value != kUsbConfigurationValueInvalid) {
+    LOG(INFO) << StringPrintf("Switching device '%s' to MBIM configuration %d.",
+                              switch_context_->sys_path().c_str(),
+                              mbim_configuration_value);
+    Complete(SetConfiguration(mbim_configuration_value));
+    return;
+  }
+
   scoped_ptr<UsbInterface> interface(
       config_descriptor->GetInterface(kDefaultUsbInterfaceIndex));
   if (!interface) {
@@ -242,9 +358,8 @@
 
   interface_number_ = interface_descriptor->GetInterfaceNumber();
   out_endpoint_address_ = out_endpoint_descriptor->GetEndpointAddress();
-  bool expect_response = switch_context_->modem_info()->expect_response();
 
-  if (expect_response) {
+  if (switch_context_->modem_info()->expect_response()) {
     scoped_ptr<UsbEndpointDescriptor> in_endpoint_descriptor(
         interface_descriptor->GetEndpointDescriptorByTransferTypeAndDirection(
             kUsbTransferTypeBulk, kUsbDirectionIn));
diff --git a/usb_modem_switch_operation.h b/usb_modem_switch_operation.h
index 070e680..fb5961f 100644
--- a/usb_modem_switch_operation.h
+++ b/usb_modem_switch_operation.h
@@ -26,11 +26,12 @@
 class UsbModemSwitchContext;
 class UsbTransfer;
 
-// A USB modem switch operation, which switches a USB modem from the mass
-// storage mode to the modem mode. The whole operation involves the following
-// tasks:
-// 1. Open the USB modem device, find and claim the mass storage interface of
-//    the modem.
+// A USB modem switch operation for switching a USB modem into the modem mode.
+// The whole operation involves the following tasks:
+// 1. Open the USB modem device. If the modem has a USB configuration that
+//    exposes a MBIM interface, select that configuration and complete the
+//    switch operation. Otherwise, find and claim the mass storage interface of
+//    the mdoem.
 // 2. Initiate a bulk output transfer of a (or multiple) special USB message(s)
 //    to the mass storage endpoint of the modem.
 // 3. On some modems, a bulk input transfer from the mass storage endpoint of
@@ -93,11 +94,27 @@
   // returns.
   void Complete(bool success);
 
+  // Detaches all the kernel drivers associated with the interfaces of the
+  // currently active USB configuration. Continues to detach other kernel
+  // drivers if it fails to detach any driver.
+  void DetachAllKernelDrivers();
+
+  // Returns the value of the USB configuration at which the device exposes a
+  // MBIM interface, or kUsbConfigurationValueInvalid if no MBIM interface is
+  // found.
+  int GetMBIMConfigurationValue();
+
+  // Sets the USB configuration of the device to |configuration|. Returns true
+  // on success.
+  bool SetConfiguration(int configuration);
+
   // Closes the device.
   void CloseDevice();
 
-  // Opens the device and claims the mass storage interface on the device.
-  void OpenDeviceAndClaimMassStorageInterface();
+  // Opens the device. If the device has a USB configuration that exposes a MBIM
+  // interface, selects that configuration and completes the switch operation.
+  // Otherwise, finds and claims the mass storage interface on the device.
+  void OpenDeviceAndSelectInterface();
 
   // Clears the halt condition on the endpoint at |endpoint_address|. Returns
   // true on success.