| // 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. |
| |
| #ifndef GOBI_CROMO_PLUGIN_GOBI_MODEM_H_ |
| #define GOBI_CROMO_PLUGIN_GOBI_MODEM_H_ |
| |
| #include <glib.h> |
| #include <pthread.h> |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| |
| // TODO(ers) remove following #include once logging spew is resolved |
| #include <base/logging.h> |
| #include <base/macros.h> |
| #include <gtest/gtest_prod.h> // For FRIEND_TEST |
| #include <cromo/cromo_server.h> |
| #include <cromo/dbus_adaptors/org.freedesktop.DBus.Properties.h> |
| #include <cromo/dbus_adaptors/org.freedesktop.ModemManager.Modem.h> |
| #include <cromo/dbus_adaptors/org.freedesktop.ModemManager.Modem.Simple.h> |
| #include <cromo/modem.h> |
| #include <cromo/utilities.h> |
| #include <metrics/metrics_library.h> |
| #include <mm/mm-modem.h> |
| |
| #include "gobi-cromo-plugin/gobi_sdk_wrapper.h" |
| #include "gobi-cromo-plugin/metrics_stopwatch.h" |
| #include "gobi-cromo-plugin/modem_gobi_server_glue.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-cromo-plugin/gobi_modem_errors.h" // NOLINT(build/include_alpha) |
| #undef DEFINE_ERROR |
| #undef DEFINE_MM_ERROR |
| |
| |
| 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) \ |
| 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 { |
| MM_MODEM_STATE_UNKNOWN = 0, |
| MM_MODEM_STATE_DISABLED = 10, |
| MM_MODEM_STATE_DISABLING = 20, |
| MM_MODEM_STATE_ENABLING = 30, |
| MM_MODEM_STATE_ENABLED = 40, |
| MM_MODEM_STATE_SEARCHING = 50, |
| MM_MODEM_STATE_REGISTERED = 60, |
| MM_MODEM_STATE_DISCONNECTING = 70, |
| MM_MODEM_STATE_CONNECTING = 80, |
| MM_MODEM_STATE_CONNECTED = 90, |
| |
| MM_MODEM_STATE_LAST = MM_MODEM_STATE_CONNECTED |
| } 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 { |
| public: |
| 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) { |
| Remove(); |
| id_ = g_timeout_add_seconds_full( |
| priority, interval, function, data, notify); |
| return id_; |
| } |
| |
| void Remove(void) { |
| if (id_) { |
| g_source_remove(id_); |
| id_ = 0; |
| } |
| } |
| |
| private: |
| uint32_t id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedGSource); |
| }; |
| |
| 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 { |
| public: |
| 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 nullptr; |
| } |
| |
| // 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(); |
| |
| protected: |
| 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_ != nullptr; } |
| 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(nullptr) {} |
| explicit CallbackArgs(DBus::Path* path) : path(path) {} |
| ~CallbackArgs() { delete path; } |
| DBus::Path* path; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CallbackArgs); |
| }; |
| |
| struct CallbackArgsWrapper { |
| CallbackArgsWrapper() : callback(nullptr), callback_id(0), args(nullptr) {} |
| ~CallbackArgsWrapper() { delete args; } |
| GSourceFunc callback; |
| guint callback_id; |
| CallbackArgs* args; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CallbackArgsWrapper); |
| }; |
| |
| static void PostCallbackRequest(GSourceFunc callback, |
| CallbackArgs* args) { |
| pthread_mutex_lock(&modem_mutex_.mutex_); |
| 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); |
| connected_modem_->idle_callback_ids_.insert(args_wrapper->callback_id); |
| } else { |
| delete args; |
| } |
| pthread_mutex_unlock(&modem_mutex_.mutex_); |
| } |
| |
| static gboolean ExecuteCallbackRequest(gpointer data) { |
| CallbackArgsWrapper *args_wrapper = |
| reinterpret_cast<CallbackArgsWrapper*>(data); |
| bool run_callback = false; |
| pthread_mutex_lock(&modem_mutex_.mutex_); |
| if (connected_modem_ && !connected_modem_->getting_deallocated_) { |
| connected_modem_->idle_callback_ids_.erase(args_wrapper->callback_id); |
| run_callback = true; |
| } |
| pthread_mutex_unlock(&modem_mutex_.mutex_); |
| if (run_callback) |
| args_wrapper->callback(args_wrapper->args); |
| 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) { |
| PostCallbackRequest(SessionStateCallback, |
| new SessionStateArgs(state, session_end_reason)); |
| } |
| static gboolean SessionStateCallback(gpointer data); |
| |
| struct DataBearerTechnologyArgs : public CallbackArgs { |
| explicit DataBearerTechnologyArgs(ULONG technology) |
| : technology(technology) {} |
| ULONG technology; |
| }; |
| |
| static void DataBearerCallbackTrampoline(ULONG technology) { |
| // ignore the supplied argument |
| PostCallbackRequest(DataBearerTechnologyCallback, |
| 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) { |
| PostCallbackRequest(SignalStrengthCallback, |
| new SignalStrengthArgs(signal_strength, |
| radio_interface)); |
| } |
| static gboolean SignalStrengthCallback(gpointer data); |
| |
| struct DormancyStatusArgs : public CallbackArgs { |
| explicit DormancyStatusArgs(ULONG status) : status(status) {} |
| ULONG status; |
| }; |
| |
| static void DormancyStatusCallbackTrampoline(ULONG status) { |
| PostCallbackRequest(DormancyStatusCallback, |
| 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) { |
| PostCallbackRequest(DataCapabilitiesCallback, |
| new DataCapabilitiesArgs(num_data_caps, data_caps)); |
| } |
| static gboolean DataCapabilitiesCallback(gpointer data); |
| |
| struct SdkErrorArgs : public CallbackArgs { |
| explicit 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_; |
| std::unique_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_, nullptr); } |
| 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_; |
| std::unique_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); |
| |
| std::unique_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 { |
| GOBI_EVENT_DORMANCY = 0, |
| GOBI_EVENT_MAX |
| }; |
| |
| 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 |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(GobiModem); |
| }; |
| |
| class ScopedApiConnection { |
| public: |
| explicit ScopedApiConnection(GobiModem& modem) |
| : modem_(modem), was_connected_(modem_.IsApiConnected()) {} |
| |
| ~ScopedApiConnection() { |
| if (!was_connected_ && modem_.IsApiConnected()) |
| modem_.ApiDisconnect(); |
| } |
| |
| void ApiConnect(DBus::Error& error) { |
| if (!was_connected_) |
| modem_.ApiConnect(error); |
| } |
| |
| // Force an immediate disconnect independent of prior state |
| void ApiDisconnect() { |
| // Prevent auto disconnect on destruction by faking we had been connected |
| was_connected_ = true; |
| modem_.ApiDisconnect(); |
| } |
| |
| private: |
| GobiModem& modem_; |
| bool was_connected_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedApiConnection); |
| }; |
| |
| class GobiModemHelper { |
| public: |
| explicit GobiModemHelper(gobi::Sdk* sdk) : sdk_(sdk) {} |
| virtual ~GobiModemHelper() {} |
| |
| virtual void SetCarrier(GobiModem* modem, |
| GobiModemHandler* handler, |
| const std::string& carrier_name, |
| DBus::Error& error) = 0; |
| |
| protected: |
| static const char kErrorUnknownCarrier[]; |
| |
| gobi::Sdk* sdk_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(GobiModemHelper); |
| }; |
| |
| #endif // GOBI_CROMO_PLUGIN_GOBI_MODEM_H_ |