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.