blob: 42c97213cfd1663544370c101476a9bd77dcf2da [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <glib.h>
#include <pthread.h>
#include <base/basictypes.h>
// TODO(ers) remove following #include once logging spew is resolved
#include <base/logging.h>
#include <gtest/gtest_prod.h> // For FRIEND_TEST
#include <map>
#include <string>
#include <set>
#include <cromo/cromo_server.h>
#include <cromo/modem.h>
#include <cromo/modem_server_glue.h>
#include <cromo/modem-simple_server_glue.h>
#include <cromo/properties_server_glue.h>
#include <cromo/utilities.h>
#include <metrics/metrics_library.h>
#include <mm/mm-modem.h>
#include "modem_gobi_server_glue.h"
#include "gobi_sdk_wrapper.h"
#include "metrics_stopwatch.h"
// TODO(rochberg) Fix namespace pollution
#define METRIC_BASE_NAME "Network.3G.Gobi."
#define DEFINE_ERROR(name) \
extern const char* k##name##Error;
#define DEFINE_MM_ERROR(name, str) \
extern const char* kError##name;
#include "gobi_modem_errors.h"
const char* QMIReturnCodeToMMError(unsigned int qmicode);
const char* QMICallFailureToMMError(unsigned int qmireason);
unsigned int QMIReasonToMMReason(unsigned int qmireason);
#define ENSURE_SDK_SUCCESS_WITH_RESULT(function, rc, errtype, result) \
do { \
if (rc != 0) { \
const char *errname = QMIReturnCodeToMMError(rc); \
if (errname != NULL) \
error.set(errname, #function); \
else \
error.set(errtype, #function); \
LOG(WARNING) << #function << " failed : " << rc; \
return result; \
} \
} while (0) \
#define ENSURE_SDK_SUCCESS(function, rc, errtype) \
ENSURE_SDK_SUCCESS_WITH_RESULT(function, rc, errtype,)
// from mm-modem.h in ModemManager. This enum
// should move into an XML file to become part
// of the DBus API
typedef enum {
} MMModemState;
static const size_t kDefaultBufferSize = 128;
class Carrier;
class CromoServer;
class GobiModem;
class GobiModemHandler;
class SessionStarter;
class InjectedFaults;
class GobiModemHelper;
class PendingEnable;
class ScopedGSource {
ScopedGSource() : id_(0) { }
~ScopedGSource() { Remove(); }
// Remove old timeout if exists and add a new one
guint TimeoutAddFull(gint priority,
guint interval,
GSourceFunc function,
gpointer data,
GDestroyNotify notify) {
id_ = g_timeout_add_seconds_full(
priority, interval, function, data, notify);
return id_;
void Remove(void) {
if (id_) {
id_ = 0;
uint32 id_;
class GobiModem
: public Modem,
public org::freedesktop::ModemManager::Modem_adaptor,
public org::freedesktop::ModemManager::Modem::Simple_adaptor,
public org::chromium::ModemManager::Modem::Gobi_adaptor,
public org::freedesktop::DBus::Properties_adaptor,
public DBus::IntrospectableAdaptor,
public DBus::PropertiesAdaptor,
public DBus::ObjectAdaptor {
enum NetworkPreference {
kNetworkPreferenceAutomatic = 0,
kNetworkPreferenceCdma1xRtt = 1,
kNetworkPreferenceCdmaEvdo = 2,
kNetworkPreferenceGsm = 3,
kNetworkPreferenceWcdma = 4
typedef std::map<ULONG, int> StrengthMap;
GobiModem(DBus::Connection& connection,
const DBus::Path& path,
const gobi::DeviceElement &device,
gobi::Sdk *sdk,
GobiModemHelper *helper);
virtual ~GobiModem();
virtual void Init();
int last_seen() {return last_seen_;}
void set_last_seen(int scan_count) {
last_seen_ = scan_count;
uint32_t mm_state() { return mm_state_; }
void SetMmState(uint32_t new_state, uint32_t reason);
static void set_handler(GobiModemHandler* handler) { handler_ = handler; }
// Called by the SDK wrapper when it receives an error that requires
// that the modem be reset. Posts a main-thread callback that
// power cycles the modem.
static void SinkSdkError(const std::string& modem_path,
const std::string& sdk_function,
ULONG error);
std::string GetUSBAddress();
// Modem methods.
virtual ModemAdaptor *modem_adaptor() {
return static_cast<ModemAdaptor *>(this);
virtual SimpleAdaptor *simple_adaptor() {
return static_cast<SimpleAdaptor *>(this);
virtual CdmaAdaptor *cdma_adaptor() {
LOG(WARNING) << "Modem::cdma_adaptor() called on non-CDMA modem.";
return NULL;
// DBUS Methods: Modem
virtual void Enable(const bool& enable, DBus::Error& error);
virtual void Connect(const std::string& number, DBus::Error& error);
virtual void Disconnect(DBus::Error& error);
virtual void FactoryReset(const std::string& number, DBus::Error& error);
virtual ::DBus::Struct<
uint32_t, uint32_t, uint32_t, uint32_t> GetIP4Config(DBus::Error& error);
virtual ::DBus::Struct<
std::string, std::string, std::string> GetInfo(DBus::Error& error);
virtual void Reset(DBus::Error& error);
// DBUS Methods: ModemSimple
virtual void Connect(const utilities::DBusPropertyMap& properties,
DBus::Error& error);
// Contract addition: GetStatus never fails, it simply does not set
// properties it cannot determine.
virtual utilities::DBusPropertyMap GetStatus(DBus::Error& error);
// DBUS Methods: ModemGobi
virtual void SetCarrier(const std::string& image, DBus::Error& error);
virtual void SoftReset(DBus::Error& error);
virtual void PowerCycle(DBus::Error& error);
virtual void RequestEvents(const std::string& events, DBus::Error& error);
virtual void SetAutomaticTracking(const bool& service_enable,
const bool& port_enable,
DBus::Error& error);
virtual void InjectFault(const std::string& name,
const int32_t &value,
DBus::Error& error);
virtual void SetNetworkPreference(const int32_t &value,
DBus::Error& error);
virtual void ForceModemActivatedStatus(DBus::Error& error);
void ClearIdleCallbacks();
struct SerialNumbers {
std::string esn;
std::string imei;
std::string meid;
static const int kNumStartDataSessionRetries;
void ApiConnect(DBus::Error& error);
ULONG ApiDisconnect(void);
bool IsApiConnected() { return connected_modem_ != NULL; }
virtual uint32_t CommonGetSignalQuality(DBus::Error& error);
void GetSignalStrengthDbm(int& strength,
StrengthMap *interface_to_strength,
DBus::Error& error);
// Read power state from the modem; update Enabled and mm_state_
void GetPowerState();
virtual void RegisterCallbacks();
void ResetModem(DBus::Error& error);
void GetSerialNumbers(SerialNumbers* out, DBus::Error &error);
void LogGobiInformation();
// TODO(cros-connectivity): Move these to a utility class
static unsigned long MapDbmToPercent(INT8 signal_strength_dbm);
static unsigned long MapDataBearerToRfi(ULONG data_bearer_technology);
// Send the return for the outstanding dbus call associated with *tag.
void FinishDeferredCall(DBus::Tag *tag, const DBus::Error &error);
// This does not use the callback args mechanism below.
void SessionStarterDoneCallback(SessionStarter *starter);
unsigned int QCStateToMMState(ULONG qcstate);
struct CallbackArgs {
CallbackArgs() : path(NULL) { }
explicit CallbackArgs(DBus::Path *path) : path(path) { }
~CallbackArgs() { delete path; }
DBus::Path* path;
struct CallbackArgsWrapper {
CallbackArgsWrapper() : callback(NULL), callback_id(0), args(NULL) { }
~CallbackArgsWrapper() { delete args; }
GSourceFunc callback;
guint callback_id;
CallbackArgs *args;
static void PostCallbackRequest(GSourceFunc callback,
CallbackArgs* args) {
if (connected_modem_ && !connected_modem_->getting_deallocated_) {
args->path = new DBus::Path(connected_modem_->path());
CallbackArgsWrapper *args_wrapper = new CallbackArgsWrapper();
args_wrapper->args = args;
args_wrapper->callback = callback;
args_wrapper->callback_id =
g_idle_add(ExecuteCallbackRequest, args_wrapper);
} else {
delete args;
static gboolean ExecuteCallbackRequest(gpointer data) {
CallbackArgsWrapper *args_wrapper =
bool run_callback = false;
if (connected_modem_ && !connected_modem_->getting_deallocated_) {
run_callback = true;
if (run_callback)
delete args_wrapper;
return FALSE;
struct SessionStateArgs : public CallbackArgs {
SessionStateArgs(ULONG state,
ULONG session_end_reason)
: state(state),
session_end_reason(session_end_reason) { }
ULONG state;
ULONG session_end_reason;
static void SessionStateCallbackTrampoline(ULONG state,
ULONG session_end_reason) {
new SessionStateArgs(state,
static gboolean SessionStateCallback(gpointer data);
struct DataBearerTechnologyArgs : public CallbackArgs {
DataBearerTechnologyArgs(ULONG technology) : technology(technology) {}
ULONG technology;
static void DataBearerCallbackTrampoline(ULONG technology) {
// ignore the supplied argument
new DataBearerTechnologyArgs(technology));
static gboolean DataBearerTechnologyCallback(gpointer data);
static void RoamingIndicatorCallbackTrampoline(ULONG roaming) {
// ignore the supplied argument
PostCallbackRequest(RegistrationStateCallback, new CallbackArgs());
static void PowerCallbackTrampoline(ULONG power_mode) {
// ignore the supplied argument
PostCallbackRequest(PowerCallback, new CallbackArgs());
static gboolean PowerCallback(gpointer data);
static void RFInfoCallbackTrampoline(ULONG iface, ULONG bandclass,
ULONG channel) {
// ignore the supplied arguments
PostCallbackRequest(RegistrationStateCallback, new CallbackArgs());
static gboolean RegistrationStateCallback(gpointer data);
struct SignalStrengthArgs : public CallbackArgs {
SignalStrengthArgs(INT8 signal_strength,
ULONG radio_interface)
: signal_strength(signal_strength),
radio_interface(radio_interface) { }
INT8 signal_strength;
ULONG radio_interface;
static void SignalStrengthCallbackTrampoline(INT8 signal_strength,
ULONG radio_interface) {
new SignalStrengthArgs(signal_strength,
static gboolean SignalStrengthCallback(gpointer data);
struct DormancyStatusArgs : public CallbackArgs {
DormancyStatusArgs(ULONG status) : status(status) { }
ULONG status;
static void DormancyStatusCallbackTrampoline(ULONG status) {
new DormancyStatusArgs(status));
static gboolean DormancyStatusCallback(gpointer data);
struct DataCapabilitiesArgs : public CallbackArgs {
DataCapabilitiesArgs(BYTE num_caps, BYTE* data)
: num_data_caps(num_caps) {
ULONG* dcp = reinterpret_cast<ULONG*>(data);
if (num_data_caps > 12)
num_data_caps = 12;
for (int i = 0; i < num_data_caps; i++)
data_caps[i] = dcp[i];
BYTE num_data_caps;
// undocumented: the SDK limits the number of
// data capabilities reported to 12
ULONG data_caps[12];
static void DataCapabilitiesCallbackTrampoline(BYTE num_data_caps,
BYTE* data_caps) {
new DataCapabilitiesArgs(num_data_caps, data_caps));
static gboolean DataCapabilitiesCallback(gpointer data);
struct SdkErrorArgs : public CallbackArgs {
SdkErrorArgs(ULONG error) : error(error) { }
ULONG error;
static gboolean SdkErrorHandler(gpointer data);
// Set DBus-exported properties
void SetDeviceProperties();
void SetModemProperties();
virtual void SetTechnologySpecificProperties() = 0;
virtual void GetTechnologySpecificStatus(
utilities::DBusPropertyMap* properties) = 0;
virtual bool CheckEnableOk(DBus::Error &error) = 0;
// Handlers for events delivered as callbacks by the SDK. These
// all run in the main thread.
virtual void RegistrationStateHandler() = 0;
virtual void DataCapabilitiesHandler(BYTE num_data_caps,
ULONG* data_caps) = 0;
virtual void SignalStrengthHandler(INT8 signal_strength,
ULONG radio_interface) = 0;
virtual void SessionStateHandler(ULONG state, ULONG session_end_reason);
virtual void DataBearerTechnologyHandler(ULONG technology);
virtual void PowerModeHandler();
static GobiModemHandler *handler_;
// Wraps the Gobi SDK for dependency injection
gobi::Sdk *sdk_;
scoped_ptr<GobiModemHelper> modem_helper_;
gobi::DeviceElement device_;
int last_seen_; // Updated every scan where the modem is present
// Mirrors the DBus "State" property. This variable exists because
// the DBus properties are essentially write-only.
uint32_t mm_state_;
ULONG session_id_;
bool signal_available_;
std::string sysfs_path_;
struct mutex_wrapper_ {
mutex_wrapper_() { pthread_mutex_init(&mutex_, NULL); }
pthread_mutex_t mutex_;
static GobiModem *connected_modem_;
static mutex_wrapper_ modem_mutex_;
bool exiting_;
bool device_resetting_;
bool getting_deallocated_;
std::set<guint> idle_callback_ids_;
bool session_starter_in_flight_;
scoped_ptr<PendingEnable> pending_enable_;
ScopedGSource retry_disable_callback_source_;
void RescheduleDisable();
static void CleanupRetryDisableCallback(gpointer data);
static gboolean RetryDisableCallback(gpointer data);
bool EnableHelper(const bool& enable, DBus::Error& error,
const bool& user_initiated);
void FinishEnable(const DBus::Error &error);
void PerformDeferredDisable();
static gobi::RegistrationState GetRegistrationState(gobi::Sdk *sdk);
friend class GobiModemTest;
// Return true if a data session is in the process of starting or
// has started
bool is_connecting_or_connected();
// Call the Gobi API to stop a data session
ULONG StopDataSession(ULONG session_id);
// Force a disconnect of a data session, or stop the process of
// starting a datasession
ULONG ForceDisconnect();
bool StartExit();
bool ExitOk();
// Resets the device by kicking it off the USB and allowing it back
// on. Reason is a QCWWAN error code for logging/metrics, or 0 for
// 'externally initiated'
void ExitAndResetDevice(ULONG reason);
// Helper that compresses reason to a small int and mails to UMA
void RecordResetReason(ULONG reason);
bool CanMakeMethodCalls(void);
std::string hooks_name_;
friend bool StartExitTrampoline(void *arg);
friend bool ExitOkTrampoline(void *arg);
scoped_ptr<MetricsLibraryInterface> metrics_lib_;
MetricsStopwatch disconnect_time_;
MetricsStopwatch registration_time_;
std::map<std::string, int32_t> injected_faults_;
// This ought to go on the gobi modem XML interface, but dbus-c++ can't handle
// enums, and all the ways of hacking around it seem worse than just using
// strings and translating to the enum when we get the DBus call.
enum {
static int EventKeyToIndex(const char *key);
void RequestEvent(const std::string req, DBus::Error& error);
bool event_enabled[GOBI_EVENT_MAX];
// Sets the Enabled property and emits a property changed signal.
void SetEnabled(bool enabled);
FRIEND_TEST(GobiModemTest, GetSignalStrengthDbmDisconnected);
FRIEND_TEST(GobiModemTest, GetSignalStrengthDbmConnected);
friend class SessionStarter;
friend class PendingEnable;
friend class ScopedApiConnection;
friend class Gobi3KModemHelper; // a bad idea, but necessary until
// code is restructured
class ScopedApiConnection {
ScopedApiConnection(GobiModem &modem) : modem_(modem),
was_connected_(modem_.IsApiConnected()) { }
~ScopedApiConnection() {
if (!was_connected_ && modem_.IsApiConnected())
void ApiConnect(DBus::Error& error) {
if (!was_connected_)
// Force an immediate disconnect independent of prior state
void ApiDisconnect() {
// Prevent auto disconnect on destruction by faking we had been connected
was_connected_ = true;
GobiModem &modem_;
bool was_connected_;
class GobiModemHelper {
GobiModemHelper(gobi::Sdk* sdk) : sdk_(sdk) { };
virtual ~GobiModemHelper() { };
virtual void SetCarrier(GobiModem *modem,
GobiModemHandler *handler,
const std::string& carrier_name,
DBus::Error& error) = 0;
static const char kErrorUnknownCarrier[];
gobi::Sdk* sdk_;