/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details: * * Copyright (C) 2011 Google, Inc. */ #include #include #include "mm-modem-helpers.h" #include "mm-iface-modem.h" #include "mm-base-modem.h" #include "mm-base-modem-at.h" #include "mm-sim.h" #include "mm-bearer-list.h" #include "mm-log.h" #include "mm-context.h" #define SIGNAL_QUALITY_RECENT_TIMEOUT_SEC 60 #define SIGNAL_QUALITY_CHECK_TIMEOUT_SEC 30 #define ACCESS_TECHNOLOGIES_CHECK_TIMEOUT_SEC 30 #define STATE_UPDATE_CONTEXT_TAG "state-update-context-tag" #define SIGNAL_QUALITY_UPDATE_CONTEXT_TAG "signal-quality-update-context-tag" #define SIGNAL_QUALITY_CHECK_CONTEXT_TAG "signal-quality-check-context-tag" #define ACCESS_TECHNOLOGIES_CHECK_CONTEXT_TAG "access-technologies-check-context-tag" static GQuark state_update_context_quark; static GQuark signal_quality_update_context_quark; static GQuark signal_quality_check_context_quark; static GQuark access_technologies_check_context_quark; /*****************************************************************************/ void mm_iface_modem_bind_simple_status (MMIfaceModem *self, MMSimpleStatus *status) { MmGdbusModem *skeleton; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); g_object_bind_property (skeleton, "state", status, MM_SIMPLE_PROPERTY_STATE, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_bind_property (skeleton, "signal-quality", status, MM_SIMPLE_PROPERTY_SIGNAL_QUALITY, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_bind_property (skeleton, "bands", status, MM_SIMPLE_PROPERTY_BANDS, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_bind_property (skeleton, "access-technologies", status, MM_SIMPLE_PROPERTY_ACCESS_TECHNOLOGIES, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_unref (skeleton); } /*****************************************************************************/ static MMModemState get_current_consolidated_state (MMIfaceModem *self); typedef struct { MMBearer *self; guint others_connected; } CountOthersConnectedContext; static void bearer_list_count_others_connected (MMBearer *bearer, CountOthersConnectedContext *ctx) { /* We can safely compare pointers here */ if (bearer != ctx->self && mm_bearer_get_status (bearer) == MM_BEARER_STATUS_CONNECTED) { ctx->others_connected++; } } static void bearer_status_changed (MMBearer *bearer, GParamSpec *pspec, MMIfaceModem *self) { CountOthersConnectedContext ctx; MMBearerList *list = NULL; g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); ctx.self = bearer; ctx.others_connected = 0; /* We now count how many *other* bearers are connected */ mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_list_count_others_connected, &ctx); /* If no other bearers are connected, change modem state */ if (!ctx.others_connected) { MMModemState new_state = MM_MODEM_STATE_UNKNOWN; switch (mm_bearer_get_status (bearer)) { case MM_BEARER_STATUS_CONNECTED: new_state = MM_MODEM_STATE_CONNECTED; break; case MM_BEARER_STATUS_CONNECTING: new_state = MM_MODEM_STATE_CONNECTING; break; case MM_BEARER_STATUS_DISCONNECTING: new_state = MM_MODEM_STATE_DISCONNECTING; break; case MM_BEARER_STATUS_DISCONNECTED: new_state = get_current_consolidated_state (self); break; } mm_iface_modem_update_state (self, new_state, MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED); } g_object_unref (list); } MMBearer * mm_iface_modem_create_bearer_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { MMBearer *bearer; if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) return NULL; bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); return g_object_ref (bearer); } static void create_bearer_ready (MMIfaceModem *self, GAsyncResult *res, GSimpleAsyncResult *simple) { MMBearer *bearer; GError *error = NULL; bearer = MM_IFACE_MODEM_GET_INTERFACE (self)->create_bearer_finish (self, res, &error); if (error) g_simple_async_result_take_error (simple, error); else { MMBearerList *list = NULL; g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); if (!mm_bearer_list_add_bearer (list, bearer, &error)) g_simple_async_result_take_error (simple, error); else { /* If bearer properly created and added to the list, follow its * status */ g_signal_connect (bearer, "notify::" MM_BEARER_STATUS, (GCallback)bearer_status_changed, self); g_simple_async_result_set_op_res_gpointer (simple, g_object_ref (bearer), g_object_unref); } g_object_unref (bearer); g_object_unref (list); } g_simple_async_result_complete (simple); g_object_unref (simple); } void mm_iface_modem_create_bearer (MMIfaceModem *self, MMBearerProperties *properties, GAsyncReadyCallback callback, gpointer user_data) { MMBearerList *list = NULL; g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); if (mm_bearer_list_get_count (list) == mm_bearer_list_get_max (list)) g_simple_async_report_error_in_idle ( G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_TOO_MANY, "Cannot add new bearer: already reached maximum (%u)", mm_bearer_list_get_count (list)); else MM_IFACE_MODEM_GET_INTERFACE (self)->create_bearer ( self, properties, (GAsyncReadyCallback)create_bearer_ready, g_simple_async_result_new (G_OBJECT (self), callback, user_data, mm_iface_modem_create_bearer)); g_object_unref (list); } typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; GVariant *dictionary; } HandleCreateBearerContext; static void handle_create_bearer_context_free (HandleCreateBearerContext *ctx) { g_variant_unref (ctx->dictionary); g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void handle_create_bearer_ready (MMIfaceModem *self, GAsyncResult *res, HandleCreateBearerContext *ctx) { MMBearer *bearer; GError *error = NULL; bearer = mm_iface_modem_create_bearer_finish (self, res, &error); if (!bearer) g_dbus_method_invocation_take_error (ctx->invocation, error); else { mm_gdbus_modem_complete_create_bearer (ctx->skeleton, ctx->invocation, mm_bearer_get_path (bearer)); g_object_unref (bearer); } handle_create_bearer_context_free (ctx); } static void handle_create_bearer_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleCreateBearerContext *ctx) { MMBearerProperties *properties; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_create_bearer_context_free (ctx); return; } properties = mm_bearer_properties_new_from_dictionary (ctx->dictionary, &error); if (!properties) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_create_bearer_context_free (ctx); return; } mm_iface_modem_create_bearer ( ctx->self, properties, (GAsyncReadyCallback)handle_create_bearer_ready, ctx); g_object_unref (properties); } static gboolean handle_create_bearer (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, GVariant *dictionary, MMIfaceModem *self) { HandleCreateBearerContext *ctx; ctx = g_new (HandleCreateBearerContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->dictionary = g_variant_ref (dictionary); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_create_bearer_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; gchar *cmd; guint timeout; } HandleCommandContext; static void handle_command_context_free (HandleCommandContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx->cmd); g_free (ctx); } static void command_ready (MMIfaceModem *self, GAsyncResult *res, HandleCommandContext *ctx) { GError *error = NULL; const gchar *result; result = MM_IFACE_MODEM_GET_INTERFACE (self)->command_finish (self, res, &error); if (error) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_command (ctx->skeleton, ctx->invocation, result); handle_command_context_free (ctx); } static void handle_command_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleCommandContext *ctx) { GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_command_context_free (ctx); return; } /* If we are not in Debug mode, report an error */ if (!mm_context_get_debug ()) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED, "Cannot send AT command to modem: " "operation only allowed in debug mode"); handle_command_context_free (ctx); return; } /* If command is not implemented, report an error */ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->command || !MM_IFACE_MODEM_GET_INTERFACE (self)->command_finish) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot send AT command to modem: " "operation not supported"); handle_command_context_free (ctx); return; } MM_IFACE_MODEM_GET_INTERFACE (self)->command (ctx->self, ctx->cmd, ctx->timeout, (GAsyncReadyCallback)command_ready, ctx); } static gboolean handle_command (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, const gchar *cmd, guint timeout, MMIfaceModem *self) { HandleCommandContext *ctx; ctx = g_new (HandleCommandContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->cmd = g_strdup (cmd); ctx->timeout = timeout; mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_command_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; gchar *bearer_path; } HandleDeleteBearerContext; static void handle_delete_bearer_context_free (HandleDeleteBearerContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx->bearer_path); g_free (ctx); } static void handle_delete_bearer_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleDeleteBearerContext *ctx) { MMBearerList *list = NULL; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_delete_bearer_context_free (ctx); return; } g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); if (!mm_bearer_list_delete_bearer (list, ctx->bearer_path, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_delete_bearer (ctx->skeleton, ctx->invocation); g_object_unref (list); handle_delete_bearer_context_free (ctx); } static gboolean handle_delete_bearer (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, const gchar *bearer, MMIfaceModem *self) { HandleDeleteBearerContext *ctx; ctx = g_new (HandleDeleteBearerContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->bearer_path = g_strdup (bearer); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_delete_bearer_auth_ready, ctx); return TRUE; } /*****************************************************************************/ static gboolean handle_list_bearers (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, MMIfaceModem *self) { GStrv paths; MMBearerList *list = NULL; g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); paths = mm_bearer_list_get_paths (list); mm_gdbus_modem_complete_list_bearers (skeleton, invocation, (const gchar *const *)paths); g_strfreev (paths); g_object_unref (list); return TRUE; } /*****************************************************************************/ void mm_iface_modem_update_access_technologies (MMIfaceModem *self, MMModemAccessTechnology new_access_tech, guint32 mask) { MmGdbusModem *skeleton = NULL; MMModemAccessTechnology old_access_tech; MMModemAccessTechnology built_access_tech; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); /* Don't process updates if the interface is shut down */ if (!skeleton) return; old_access_tech = mm_gdbus_modem_get_access_technologies (skeleton); /* Build the new access tech */ built_access_tech = old_access_tech; built_access_tech &= ~mask; built_access_tech |= new_access_tech; if (built_access_tech != old_access_tech) { gchar *old_access_tech_string; gchar *new_access_tech_string; mm_gdbus_modem_set_access_technologies (skeleton, built_access_tech); /* Log */ old_access_tech_string = mm_modem_access_technology_build_string_from_mask (old_access_tech); new_access_tech_string = mm_modem_access_technology_build_string_from_mask (built_access_tech); mm_info ("Modem %s: access technology changed (%s -> %s)", g_dbus_object_get_object_path (G_DBUS_OBJECT (self)), old_access_tech_string, new_access_tech_string); g_free (old_access_tech_string); g_free (new_access_tech_string); } g_object_unref (skeleton); } /*****************************************************************************/ typedef struct { guint timeout_source; gboolean running; } AccessTechnologiesCheckContext; static void access_technologies_check_context_free (AccessTechnologiesCheckContext *ctx) { if (ctx->timeout_source) g_source_remove (ctx->timeout_source); g_free (ctx); } static void access_technologies_check_ready (MMIfaceModem *self, GAsyncResult *res) { GError *error = NULL; MMModemAccessTechnology access_technologies = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; guint mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; AccessTechnologiesCheckContext *ctx; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies_finish ( self, res, &access_technologies, &mask, &error)) { mm_dbg ("Couldn't refresh access technologies: '%s'", error->message); g_error_free (error); } else mm_iface_modem_update_access_technologies (self, access_technologies, mask); /* Remove the running tag. Note that the context may have been removed by * mm_iface_modem_shutdown when this function is invoked as a callback of * load_access_technologies. */ ctx = g_object_get_qdata (G_OBJECT (self), access_technologies_check_context_quark); if (ctx) ctx->running = FALSE; } static gboolean periodic_access_technologies_check (MMIfaceModem *self) { AccessTechnologiesCheckContext *ctx; ctx = g_object_get_qdata (G_OBJECT (self), access_technologies_check_context_quark); /* Only launch a new one if not one running already OR if the last one run * was more than 15s ago. */ if (!ctx->running) { ctx->running = TRUE; MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies ( self, (GAsyncReadyCallback)access_technologies_check_ready, NULL); } return TRUE; } static void periodic_access_technologies_check_disable (MMIfaceModem *self) { if (G_UNLIKELY (!access_technologies_check_context_quark)) access_technologies_check_context_quark = (g_quark_from_static_string ( ACCESS_TECHNOLOGIES_CHECK_CONTEXT_TAG)); /* Clear access technology */ mm_iface_modem_update_access_technologies (self, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, MM_MODEM_ACCESS_TECHNOLOGY_ANY); /* Overwriting the data will free the previous context */ g_object_set_qdata (G_OBJECT (self), access_technologies_check_context_quark, NULL); mm_dbg ("Periodic access technology checks disabled"); } static void periodic_access_technologies_check_enable (MMIfaceModem *self) { AccessTechnologiesCheckContext *ctx; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies || !MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies_finish) { /* If loading access technology not supported, don't even bother setting up * a timeout */ return; } if (G_UNLIKELY (!access_technologies_check_context_quark)) access_technologies_check_context_quark = (g_quark_from_static_string ( ACCESS_TECHNOLOGIES_CHECK_CONTEXT_TAG)); ctx = g_object_get_qdata (G_OBJECT (self), access_technologies_check_context_quark); /* If context is already there, we're already enabled */ if (ctx) { periodic_access_technologies_check (self); return; } /* Create context and keep it as object data */ mm_dbg ("Periodic access technology checks enabled"); ctx = g_new0 (AccessTechnologiesCheckContext, 1); ctx->timeout_source = g_timeout_add_seconds (ACCESS_TECHNOLOGIES_CHECK_TIMEOUT_SEC, (GSourceFunc)periodic_access_technologies_check, self); g_object_set_qdata_full (G_OBJECT (self), access_technologies_check_context_quark, ctx, (GDestroyNotify)access_technologies_check_context_free); /* Get first access technology value */ periodic_access_technologies_check (self); } /*****************************************************************************/ typedef struct { time_t last_update; guint recent_timeout_source; } SignalQualityUpdateContext; static void signal_quality_update_context_free (SignalQualityUpdateContext *ctx) { if (ctx->recent_timeout_source) g_source_remove (ctx->recent_timeout_source); g_free (ctx); } static time_t get_last_signal_quality_update_time (MMIfaceModem *self) { SignalQualityUpdateContext *ctx; if (G_UNLIKELY (!signal_quality_update_context_quark)) signal_quality_update_context_quark = (g_quark_from_static_string ( SIGNAL_QUALITY_UPDATE_CONTEXT_TAG)); ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_update_context_quark); return (ctx ? ctx->last_update : 0); } static gboolean expire_signal_quality (MMIfaceModem *self) { GVariant *old; guint signal_quality = 0; gboolean recent = FALSE; MmGdbusModem *skeleton = NULL; SignalQualityUpdateContext *ctx; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); old = mm_gdbus_modem_get_signal_quality (skeleton); g_variant_get (old, "(ub)", &signal_quality, &recent); /* If value is already not recent, we're done */ if (recent) { mm_dbg ("Signal quality value not updated in %us, " "marking as not being recent", SIGNAL_QUALITY_RECENT_TIMEOUT_SEC); mm_gdbus_modem_set_signal_quality (skeleton, g_variant_new ("(ub)", signal_quality, FALSE)); } g_object_unref (skeleton); /* Remove source id */ ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_update_context_quark); ctx->recent_timeout_source = 0; return FALSE; } static void update_signal_quality (MMIfaceModem *self, guint signal_quality, gboolean expire) { SignalQualityUpdateContext *ctx; MmGdbusModem *skeleton = NULL; const gchar *dbus_path; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); /* Don't process updates if the interface is shut down */ if (!skeleton) return; if (G_UNLIKELY (!signal_quality_update_context_quark)) signal_quality_update_context_quark = (g_quark_from_static_string ( SIGNAL_QUALITY_UPDATE_CONTEXT_TAG)); ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_update_context_quark); if (!ctx) { /* Create context and keep it as object data */ ctx = g_new0 (SignalQualityUpdateContext, 1); g_object_set_qdata_full ( G_OBJECT (self), signal_quality_update_context_quark, ctx, (GDestroyNotify)signal_quality_update_context_free); } /* Keep current timestamp */ ctx->last_update = time (NULL); /* Note: we always set the new value, even if the signal quality level * is the same, in order to provide an up to date 'recent' flag. * The only exception being if 'expire' is FALSE; in that case we assume * the value won't expire and therefore can be considered obsolete * already. */ mm_gdbus_modem_set_signal_quality (skeleton, g_variant_new ("(ub)", signal_quality, expire)); dbus_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (self)); mm_info ("Modem %s: signal quality updated (%u)", dbus_path, signal_quality); /* Remove any previous expiration refresh timeout */ if (ctx->recent_timeout_source) { g_source_remove (ctx->recent_timeout_source); ctx->recent_timeout_source = 0; } /* If we got a new expirable value, setup new timeout */ if (expire) ctx->recent_timeout_source = (g_timeout_add_seconds ( SIGNAL_QUALITY_RECENT_TIMEOUT_SEC, (GSourceFunc)expire_signal_quality, self)); g_object_unref (skeleton); } void mm_iface_modem_update_signal_quality (MMIfaceModem *self, guint signal_quality) { update_signal_quality (self, signal_quality, TRUE); } /*****************************************************************************/ typedef struct { guint timeout_source; gboolean running; } SignalQualityCheckContext; static void signal_quality_check_context_free (SignalQualityCheckContext *ctx) { if (ctx->timeout_source) g_source_remove (ctx->timeout_source); g_free (ctx); } static void signal_quality_check_ready (MMIfaceModem *self, GAsyncResult *res) { GError *error = NULL; guint signal_quality; SignalQualityCheckContext *ctx; signal_quality = MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality_finish (self, res, &error); if (error) { mm_dbg ("Couldn't refresh signal quality: '%s'", error->message); g_error_free (error); } else update_signal_quality (self, signal_quality, TRUE); /* Remove the running tag. Note that the context may have been removed by * mm_iface_modem_shutdown when this function is invoked as a callback of * load_signal_quality. */ ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_check_context_quark); if (ctx) ctx->running = FALSE; } static gboolean periodic_signal_quality_check (MMIfaceModem *self) { SignalQualityCheckContext *ctx; ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_check_context_quark); /* Only launch a new one if not one running already OR if the last one run * was more than 15s ago. */ if (!ctx->running || (time (NULL) - get_last_signal_quality_update_time (self) > 15)) { ctx->running = TRUE; MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality ( self, (GAsyncReadyCallback)signal_quality_check_ready, NULL); } return TRUE; } static void periodic_signal_quality_check_disable (MMIfaceModem *self) { if (G_UNLIKELY (!signal_quality_check_context_quark)) signal_quality_check_context_quark = (g_quark_from_static_string ( SIGNAL_QUALITY_CHECK_CONTEXT_TAG)); /* Clear signal quality */ update_signal_quality (self, 0, FALSE); /* Overwriting the data will free the previous context */ g_object_set_qdata (G_OBJECT (self), signal_quality_check_context_quark, NULL); mm_dbg ("Periodic signal quality checks disabled"); } static void periodic_signal_quality_check_enable (MMIfaceModem *self) { SignalQualityCheckContext *ctx; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality || !MM_IFACE_MODEM_GET_INTERFACE (self)->load_signal_quality_finish) { /* If loading signal quality not supported, don't even bother setting up * a timeout */ return; } if (G_UNLIKELY (!signal_quality_check_context_quark)) signal_quality_check_context_quark = (g_quark_from_static_string ( SIGNAL_QUALITY_CHECK_CONTEXT_TAG)); ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_check_context_quark); /* If context is already there, we're already enabled */ if (ctx) { periodic_signal_quality_check (self); return; } /* Create context and keep it as object data */ mm_dbg ("Periodic signal quality checks enabled"); ctx = g_new0 (SignalQualityCheckContext, 1); ctx->timeout_source = g_timeout_add_seconds (SIGNAL_QUALITY_CHECK_TIMEOUT_SEC, (GSourceFunc)periodic_signal_quality_check, self); g_object_set_qdata_full (G_OBJECT (self), signal_quality_check_context_quark, ctx, (GDestroyNotify)signal_quality_check_context_free); /* Get first signal quality value */ periodic_signal_quality_check (self); } /*****************************************************************************/ static void bearer_list_count_connected (MMBearer *bearer, guint *count) { if (mm_bearer_get_status (bearer) == MM_BEARER_STATUS_CONNECTED) (*count)++; } void mm_iface_modem_update_state (MMIfaceModem *self, MMModemState new_state, MMModemStateChangeReason reason) { MMModemState old_state = MM_MODEM_STATE_UNKNOWN; MmGdbusModem *skeleton = NULL; MMBearerList *bearer_list = NULL; g_object_get (self, MM_IFACE_MODEM_STATE, &old_state, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, MM_IFACE_MODEM_BEARER_LIST, &bearer_list, NULL); /* While connected we don't want registration status changes to change * the modem's state away from CONNECTED. */ if ((new_state == MM_MODEM_STATE_SEARCHING || new_state == MM_MODEM_STATE_REGISTERED) && bearer_list && old_state > MM_MODEM_STATE_REGISTERED) { guint connected = 0; mm_bearer_list_foreach (bearer_list, (MMBearerListForeachFunc)bearer_list_count_connected, &connected); if (connected > 0) /* Don't update state */ new_state = old_state; } /* Update state only if different */ if (new_state != old_state) { const gchar *dbus_path; dbus_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (self)); mm_info ("Modem%s%s: state changed (%s -> %s)", dbus_path ? " " : "", dbus_path ? dbus_path : "", mm_modem_state_get_string (old_state), mm_modem_state_get_string (new_state)); /* The property in the interface is bound to the property * in the skeleton, so just updating here is enough */ g_object_set (self, MM_IFACE_MODEM_STATE, new_state, NULL); /* Signal status change */ if (skeleton) mm_gdbus_modem_emit_state_changed (skeleton, old_state, new_state, reason); /* If we go to registered state (from unregistered), setup signal * quality and access technologies periodic retrieval */ if (new_state == MM_MODEM_STATE_REGISTERED && old_state < MM_MODEM_STATE_REGISTERED) { periodic_signal_quality_check_enable (self); periodic_access_technologies_check_enable (self); } /* If we go from a registered/connected state to unregistered, * cleanup signal quality retrieval */ else if (old_state >= MM_MODEM_STATE_REGISTERED && new_state < MM_MODEM_STATE_REGISTERED) { periodic_signal_quality_check_disable (self); periodic_access_technologies_check_disable (self); } } if (skeleton) g_object_unref (skeleton); if (bearer_list) g_object_unref (bearer_list); } /*****************************************************************************/ typedef struct { gchar *subsystem; MMModemState state; } SubsystemState; static void subsystem_state_array_free (GArray *array) { guint i; for (i = 0; i < array->len; i++) { SubsystemState *s; s = &g_array_index (array, SubsystemState, i); g_free (s->subsystem); } g_array_free (array, TRUE); } static MMModemState get_current_consolidated_state (MMIfaceModem *self) { MMModemState consolidated = MM_MODEM_STATE_DISABLED; GArray *subsystem_states; if (G_UNLIKELY (!state_update_context_quark)) state_update_context_quark = (g_quark_from_static_string ( STATE_UPDATE_CONTEXT_TAG)); subsystem_states = g_object_get_qdata (G_OBJECT (self), state_update_context_quark); /* Build consolidated state, expected fixes are: * - Enabled (meaning unregistered) --> Searching|Registered * - Searching --> Registered */ if (subsystem_states) { guint i; for (i = 0; i < subsystem_states->len; i++) { SubsystemState *s; s = &g_array_index (subsystem_states, SubsystemState, i); if (s->state > consolidated) consolidated = s->state; } } return consolidated; } static MMModemState get_updated_consolidated_state (MMIfaceModem *self, const gchar *subsystem, MMModemState subsystem_state) { guint i; GArray *subsystem_states; /* Reported subsystem states will be REGISTRATION-related. This means * that we would only expect a subset of the states being reported for * the subystem. Warn if we get others */ g_warn_if_fail (subsystem_state == MM_MODEM_STATE_ENABLED || subsystem_state == MM_MODEM_STATE_SEARCHING || subsystem_state == MM_MODEM_STATE_REGISTERED); if (G_UNLIKELY (!state_update_context_quark)) state_update_context_quark = (g_quark_from_static_string ( STATE_UPDATE_CONTEXT_TAG)); subsystem_states = g_object_get_qdata (G_OBJECT (self), state_update_context_quark); if (!subsystem_states) { subsystem_states = g_array_sized_new (FALSE, FALSE, sizeof (SubsystemState), 2); g_object_set_qdata_full (G_OBJECT (self), state_update_context_quark, subsystem_states, (GDestroyNotify)subsystem_state_array_free); } /* Store new subsystem state */ for (i = 0; i < subsystem_states->len; i++) { SubsystemState *s; s = &g_array_index (subsystem_states, SubsystemState, i); if (g_str_equal (s->subsystem, subsystem)) { s->state = subsystem_state; break; } } /* If not found, insert new element */ if (i == subsystem_states->len) { SubsystemState s; mm_dbg ("Will start keeping track of state for subsystem '%s'", subsystem); s.subsystem = g_strdup (subsystem); s.state = subsystem_state; g_array_append_val (subsystem_states, s); } return get_current_consolidated_state (self); } void mm_iface_modem_update_subsystem_state (MMIfaceModem *self, const gchar *subsystem, MMModemState new_state, MMModemStateChangeReason reason) { MMModemState consolidated; /* We may have different subsystems being handled (e.g. 3GPP and CDMA), and * the registration status value is unique, so if we get subsystem-specific * state updates, we'll need to merge all to get a consolidated one. */ consolidated = get_updated_consolidated_state (self, subsystem, new_state); mm_iface_modem_update_state (self, consolidated, reason); } /*****************************************************************************/ typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; gboolean enable; } HandleEnableContext; static void handle_enable_context_free (HandleEnableContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void enable_ready (MMBaseModem *self, GAsyncResult *res, HandleEnableContext *ctx) { GError *error = NULL; if (ctx->enable) { if (!mm_base_modem_enable_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_enable (ctx->skeleton, ctx->invocation); } else { if (!mm_base_modem_disable_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_enable (ctx->skeleton, ctx->invocation); } handle_enable_context_free (ctx); } static void handle_enable_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleEnableContext *ctx) { GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_enable_context_free (ctx); return; } if (ctx->enable) mm_base_modem_enable (self, (GAsyncReadyCallback)enable_ready, ctx); else mm_base_modem_disable (self, (GAsyncReadyCallback)enable_ready, ctx); } static gboolean handle_enable (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, gboolean enable, MMIfaceModem *self) { HandleEnableContext *ctx; ctx = g_new (HandleEnableContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->enable = enable; mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_enable_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; } HandleResetContext; static void handle_reset_context_free (HandleResetContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void handle_reset_ready (MMIfaceModem *self, GAsyncResult *res, HandleResetContext *ctx) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->reset_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_reset (ctx->skeleton, ctx->invocation); handle_reset_context_free (ctx); } static void handle_reset_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleResetContext *ctx) { MMModemState modem_state; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_reset_context_free (ctx); return; } /* If reseting is not implemented, report an error */ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->reset || !MM_IFACE_MODEM_GET_INTERFACE (self)->reset_finish) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot reset the modem: operation not supported"); handle_reset_context_free (ctx); return; } modem_state = MM_MODEM_STATE_UNKNOWN; g_object_get (self, MM_IFACE_MODEM_STATE, &modem_state, NULL); if (modem_state < MM_MODEM_STATE_DISABLED) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Cannot reset modem: not initialized/unlocked yet"); handle_reset_context_free (ctx); return; } MM_IFACE_MODEM_GET_INTERFACE (self)->reset (MM_IFACE_MODEM (self), (GAsyncReadyCallback)handle_reset_ready, ctx); } static gboolean handle_reset (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, MMIfaceModem *self) { HandleResetContext *ctx; ctx = g_new (HandleResetContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_reset_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; gchar *code; } HandleFactoryResetContext; static void handle_factory_reset_context_free (HandleFactoryResetContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx->code); g_free (ctx); } static void handle_factory_reset_ready (MMIfaceModem *self, GAsyncResult *res, HandleFactoryResetContext *ctx) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->factory_reset_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_factory_reset (ctx->skeleton, ctx->invocation); handle_factory_reset_context_free (ctx); } static void handle_factory_reset_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleFactoryResetContext *ctx) { MMModemState modem_state; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_factory_reset_context_free (ctx); return; } /* If reseting is not implemented, report an error */ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->factory_reset || !MM_IFACE_MODEM_GET_INTERFACE (self)->factory_reset_finish) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot reset the modem to factory defaults: " "operation not supported"); handle_factory_reset_context_free (ctx); return; } modem_state = MM_MODEM_STATE_UNKNOWN; g_object_get (self, MM_IFACE_MODEM_STATE, &modem_state, NULL); if (modem_state < MM_MODEM_STATE_DISABLED) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Cannot reset the modem to factory defaults: " "not initialized/unlocked yet"); handle_factory_reset_context_free (ctx); return; } MM_IFACE_MODEM_GET_INTERFACE (self)->factory_reset (MM_IFACE_MODEM (self), ctx->code, (GAsyncReadyCallback)handle_factory_reset_ready, ctx); } static gboolean handle_factory_reset (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, const gchar *code, MMIfaceModem *self) { HandleFactoryResetContext *ctx; ctx = g_new (HandleFactoryResetContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->code = g_strdup (code); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_factory_reset_auth_ready, ctx); return TRUE; } /*****************************************************************************/ /* BANDS */ typedef struct { MMIfaceModem *self; MmGdbusModem *skeleton; GSimpleAsyncResult *result; GArray *bands_array; } SetBandsContext; static void set_bands_context_complete_and_free (SetBandsContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->result); g_object_unref (ctx->self); g_object_unref (ctx->skeleton); g_array_unref (ctx->bands_array); g_free (ctx); } gboolean mm_iface_modem_set_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void set_bands_ready (MMIfaceModem *self, GAsyncResult *res, SetBandsContext *ctx) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->set_bands_finish (self, res, &error)) g_simple_async_result_take_error (ctx->result, error); else { mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_bands_garray_to_variant (ctx->bands_array)); g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); } set_bands_context_complete_and_free (ctx); } static gboolean validate_bands (const GArray *supported_bands_array, const GArray *bands_array, GError **error) { /* When the array has more than one element, there MUST NOT include ANY or * UNKNOWN */ if (bands_array->len > 1) { guint i; for (i = 0; i < bands_array->len; i++) { MMModemBand band; band = g_array_index (bands_array, MMModemBand, i); if (band == MM_MODEM_BAND_UNKNOWN || band == MM_MODEM_BAND_ANY) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Wrong list of bands: " "'%s' should have been the only element in the list", mm_modem_band_get_string (band)); return FALSE; } if (supported_bands_array->len > 1 || g_array_index (supported_bands_array, MMModemBand, 0) != MM_MODEM_BAND_ANY) { gboolean found = FALSE; guint j; /* The band given in allowed MUST be available in supported */ for (j = 0; !found && j < supported_bands_array->len; j++) { if (band == g_array_index (supported_bands_array, MMModemBand, j)) found = TRUE; } if (!found) { gchar *supported_bands_str; supported_bands_str = (mm_common_build_bands_string ( (const MMModemBand *)supported_bands_array->data, supported_bands_array->len)); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Given allowed band (%s) is not supported (%s)", mm_modem_band_get_string (band), supported_bands_str); g_free (supported_bands_str); return FALSE; } } } } return TRUE; } void mm_iface_modem_set_bands (MMIfaceModem *self, GArray *bands_array, GAsyncReadyCallback callback, gpointer user_data) { SetBandsContext *ctx; GArray *supported_bands_array; GError *error = NULL; /* If setting allowed bands is not implemented, report an error */ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->set_bands || !MM_IFACE_MODEM_GET_INTERFACE (self)->set_bands_finish) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Setting allowed bands not supported"); return; } /* Setup context */ ctx = g_new0 (SetBandsContext, 1); ctx->self = g_object_ref (self); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, mm_iface_modem_set_bands); g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, NULL); ctx->bands_array = g_array_ref (bands_array); /* Get list of supported bands */ supported_bands_array = (mm_common_bands_variant_to_garray ( mm_gdbus_modem_get_supported_bands (ctx->skeleton))); /* Validate input list of bands */ if (!validate_bands (supported_bands_array, ctx->bands_array, &error)) { g_array_unref (supported_bands_array); g_simple_async_result_take_error (ctx->result, error); set_bands_context_complete_and_free (ctx); return; } MM_IFACE_MODEM_GET_INTERFACE (self)->set_bands ( self, bands_array, (GAsyncReadyCallback)set_bands_ready, ctx); g_array_unref (supported_bands_array); } typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; GVariant *bands; } HandleSetBandsContext; static void handle_set_bands_context_free (HandleSetBandsContext *ctx) { g_variant_unref (ctx->bands); g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void handle_set_bands_ready (MMIfaceModem *self, GAsyncResult *res, HandleSetBandsContext *ctx) { GError *error = NULL; if (!mm_iface_modem_set_bands_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_set_bands (ctx->skeleton, ctx->invocation); handle_set_bands_context_free (ctx); } static void handle_set_bands_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleSetBandsContext *ctx) { GArray *bands_array; MMModemState modem_state; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_set_bands_context_free (ctx); return; } modem_state = MM_MODEM_STATE_UNKNOWN; g_object_get (self, MM_IFACE_MODEM_STATE, &modem_state, NULL); if (modem_state < MM_MODEM_STATE_DISABLED) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Cannot set allowed bands: " "not initialized/unlocked yet"); handle_set_bands_context_free (ctx); return; } bands_array = mm_common_bands_variant_to_garray (ctx->bands); mm_iface_modem_set_bands (MM_IFACE_MODEM (self), bands_array, (GAsyncReadyCallback)handle_set_bands_ready, ctx); g_array_unref (bands_array); } static gboolean handle_set_bands (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, GVariant *bands_variant, MMIfaceModem *self) { HandleSetBandsContext *ctx; ctx = g_new (HandleSetBandsContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->bands = g_variant_ref (bands_variant); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_set_bands_auth_ready, ctx); return TRUE; } /*****************************************************************************/ /* ALLOWED MODES */ typedef struct { MMIfaceModem *self; MmGdbusModem *skeleton; GSimpleAsyncResult *result; MMModemMode allowed; MMModemMode preferred; } SetAllowedModesContext; static void set_allowed_modes_context_complete_and_free (SetAllowedModesContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->result); g_object_unref (ctx->self); g_object_unref (ctx->skeleton); g_free (ctx); } gboolean mm_iface_modem_set_allowed_modes_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void set_allowed_modes_ready (MMIfaceModem *self, GAsyncResult *res, SetAllowedModesContext *ctx) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->set_allowed_modes_finish (self, res, &error)) g_simple_async_result_take_error (ctx->result, error); else { mm_gdbus_modem_set_allowed_modes (ctx->skeleton, ctx->allowed); mm_gdbus_modem_set_preferred_mode (ctx->skeleton, ctx->preferred); g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); } set_allowed_modes_context_complete_and_free (ctx); } void mm_iface_modem_set_allowed_modes (MMIfaceModem *self, MMModemMode allowed, MMModemMode preferred, GAsyncReadyCallback callback, gpointer user_data) { SetAllowedModesContext *ctx; MMModemMode supported; MMModemMode not_supported; /* If setting allowed modes is not implemented, report an error */ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->set_allowed_modes || !MM_IFACE_MODEM_GET_INTERFACE (self)->set_allowed_modes_finish) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Setting allowed modes not supported"); return; } /* Setup context */ ctx = g_new0 (SetAllowedModesContext, 1); ctx->self = g_object_ref (self); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, mm_iface_modem_set_allowed_modes); g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, NULL); /* Get list of supported modes */ supported = mm_gdbus_modem_get_supported_modes (ctx->skeleton); /* Whenever we get 'any', just reset to be equal to the list of supported modes */ if (allowed == MM_MODEM_MODE_ANY) allowed = supported; ctx->allowed = allowed; ctx->preferred = preferred; /* Check if we already are in the requested setup */ if (mm_gdbus_modem_get_allowed_modes (ctx->skeleton) == allowed && mm_gdbus_modem_get_preferred_mode (ctx->skeleton) == preferred) { g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); set_allowed_modes_context_complete_and_free (ctx); return; } /* Check if any of the modes being allowed is not supported */ not_supported = ((supported ^ allowed) & allowed); /* Ensure allowed is a subset of supported */ if (not_supported) { gchar *not_supported_str; gchar *supported_str; not_supported_str = mm_modem_mode_build_string_from_mask (not_supported); supported_str = mm_modem_mode_build_string_from_mask (supported); g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Some of the allowed modes (%s) are not " "supported (%s)", not_supported_str, supported_str); g_free (supported_str); g_free (not_supported_str); set_allowed_modes_context_complete_and_free (ctx); return; } /* Ensure preferred, if given, is a subset of allowed */ if ((allowed ^ preferred) & preferred) { gchar *preferred_str; gchar *allowed_str; preferred_str = mm_modem_mode_build_string_from_mask (preferred); allowed_str = mm_modem_mode_build_string_from_mask (allowed); g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Preferred mode (%s) is not allowed (%s)", preferred_str, allowed_str); g_free (preferred_str); g_free (allowed_str); set_allowed_modes_context_complete_and_free (ctx); return; } MM_IFACE_MODEM_GET_INTERFACE (self)->set_allowed_modes (self, allowed, preferred, (GAsyncReadyCallback)set_allowed_modes_ready, ctx); } typedef struct { MmGdbusModem *skeleton; GDBusMethodInvocation *invocation; MMIfaceModem *self; MMModemMode allowed; MMModemMode preferred; } HandleSetAllowedModesContext; static void handle_set_allowed_modes_context_free (HandleSetAllowedModesContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void handle_set_allowed_modes_ready (MMIfaceModem *self, GAsyncResult *res, HandleSetAllowedModesContext *ctx) { GError *error = NULL; if (!mm_iface_modem_set_allowed_modes_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_modem_complete_set_allowed_modes (ctx->skeleton, ctx->invocation); handle_set_allowed_modes_context_free (ctx); } static void handle_set_allowed_modes_auth_ready (MMBaseModem *self, GAsyncResult *res, HandleSetAllowedModesContext *ctx) { MMModemState modem_state; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_set_allowed_modes_context_free (ctx); return; } modem_state = MM_MODEM_STATE_UNKNOWN; g_object_get (self, MM_IFACE_MODEM_STATE, &modem_state, NULL); if (modem_state < MM_MODEM_STATE_DISABLED) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Cannot set allowed modes: " "not initialized/unlocked yet"); handle_set_allowed_modes_context_free (ctx); return; } mm_iface_modem_set_allowed_modes (MM_IFACE_MODEM (self), ctx->allowed, ctx->preferred, (GAsyncReadyCallback)handle_set_allowed_modes_ready, ctx); } static gboolean handle_set_allowed_modes (MmGdbusModem *skeleton, GDBusMethodInvocation *invocation, guint allowed, guint preferred, MMIfaceModem *self) { HandleSetAllowedModesContext *ctx; ctx = g_new (HandleSetAllowedModesContext, 1); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->allowed = allowed; ctx->preferred = preferred; mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)handle_set_allowed_modes_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct _UnlockCheckContext UnlockCheckContext; struct _UnlockCheckContext { MMIfaceModem *self; guint pin_check_tries; guint pin_check_timeout_id; GSimpleAsyncResult *result; MmGdbusModem *skeleton; }; static UnlockCheckContext * unlock_check_context_new (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { UnlockCheckContext *ctx; ctx = g_new0 (UnlockCheckContext, 1); ctx->self = g_object_ref (self); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, unlock_check_context_new); g_object_get (ctx->self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, NULL); g_assert (ctx->skeleton != NULL); return ctx; } static void unlock_check_context_free (UnlockCheckContext *ctx) { g_object_unref (ctx->self); g_object_unref (ctx->result); g_object_unref (ctx->skeleton); g_free (ctx); } static void reinitialize_ready (MMBaseModem *self, GAsyncResult *res) { GError *error = NULL; mm_base_modem_initialize_finish (self, res, &error); if (error) { mm_warn ("Modem reinitialization failed: '%s'", error->message); g_error_free (error); } } static void modem_after_sim_unlock_ready (MMIfaceModem *self, GAsyncResult *res) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->modem_after_sim_unlock_finish (self, res, &error)) { mm_warn ("After SIM unlock failed setup: '%s'", error->message); g_error_free (error); } /* Go on */ mm_base_modem_initialize (MM_BASE_MODEM (self), (GAsyncReadyCallback) reinitialize_ready, NULL); } static gboolean restart_initialize_idle (MMIfaceModem *self) { /* If we were asked to run something after having sent the PIN unlock, * do it now. This may be just a timeout or some other command that gives us * the real SIM state */ if (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_after_sim_unlock != NULL && MM_IFACE_MODEM_GET_INTERFACE (self)->modem_after_sim_unlock_finish != NULL) { MM_IFACE_MODEM_GET_INTERFACE (self)->modem_after_sim_unlock( self, (GAsyncReadyCallback)modem_after_sim_unlock_ready, NULL); } else { /* If no wait needed, just go on */ mm_base_modem_initialize (MM_BASE_MODEM (self), (GAsyncReadyCallback) reinitialize_ready, NULL); } return FALSE; } static void set_lock_status (MMIfaceModem *self, MmGdbusModem *skeleton, MMModemLock lock) { MMModemLock old_lock; old_lock = mm_gdbus_modem_get_unlock_required (skeleton); mm_gdbus_modem_set_unlock_required (skeleton, lock); /* We don't care about SIM-PIN2/SIM-PUK2 since the device is * operational without it. */ if (lock == MM_MODEM_LOCK_NONE || lock == MM_MODEM_LOCK_SIM_PIN2 || lock == MM_MODEM_LOCK_SIM_PUK2) { /* Notify transition from INITIALIZING/LOCKED to DISABLED */ if (old_lock != MM_MODEM_LOCK_NONE && old_lock != MM_MODEM_LOCK_SIM_PIN2 && old_lock != MM_MODEM_LOCK_SIM_PUK2) { /* Only restart initialization if leaving LOCKED. * If this is the case, we do NOT update the state yet, we wait * to be completely re-initialized to do so. */ if (old_lock != MM_MODEM_LOCK_UNKNOWN) g_idle_add ((GSourceFunc)restart_initialize_idle, self); } } else { if (old_lock == MM_MODEM_LOCK_UNKNOWN) { /* Notify transition from INITIALIZING to LOCKED */ mm_iface_modem_update_state (self, MM_MODEM_STATE_LOCKED, MM_MODEM_STATE_CHANGE_REASON_UNKNOWN); } } } MMModemLock mm_iface_modem_unlock_check_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) return MM_MODEM_LOCK_UNKNOWN; return (MMModemLock) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); } static void unlock_check_ready (MMIfaceModem *self, GAsyncResult *res, UnlockCheckContext *ctx); static gboolean unlock_check_again (UnlockCheckContext *ctx) { ctx->pin_check_timeout_id = 0; MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_unlock_required ( ctx->self, (GAsyncReadyCallback)unlock_check_ready, ctx); return FALSE; } static void unlock_check_ready (MMIfaceModem *self, GAsyncResult *res, UnlockCheckContext *ctx) { GError *error = NULL; MMModemLock lock; lock = MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_required_finish (self, res, &error); if (error) { /* Treat several SIM related, serial and other core errors as critical * and abort the checks. These will end up moving the modem to a FAILED * state. */ if (error->domain == MM_SERIAL_ERROR || g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED) || g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) || g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE) || g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) { g_simple_async_result_take_error (ctx->result, error); g_simple_async_result_complete (ctx->result); unlock_check_context_free (ctx); return; } mm_dbg ("Couldn't check if unlock required: '%s'", error->message); g_error_free (error); /* Retry up to 3 times */ if (mm_gdbus_modem_get_unlock_required (ctx->skeleton) != MM_MODEM_LOCK_NONE && ++ctx->pin_check_tries < 3) { mm_dbg ("Retrying (%u) unlock required check", ctx->pin_check_tries); if (ctx->pin_check_timeout_id) g_source_remove (ctx->pin_check_timeout_id); ctx->pin_check_timeout_id = g_timeout_add_seconds ( 2, (GSourceFunc)unlock_check_again, ctx); return; } /* If reached max retries and still reporting error, set UNKNOWN */ lock = MM_MODEM_LOCK_UNKNOWN; } /* Update lock status and modem status if needed */ set_lock_status (self, ctx->skeleton, lock); g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (lock), NULL); g_simple_async_result_complete (ctx->result); unlock_check_context_free (ctx); } void mm_iface_modem_unlock_check (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { UnlockCheckContext *ctx; ctx = unlock_check_context_new (self, callback, user_data); /* If we're already unlocked, we're done */ if (mm_gdbus_modem_get_unlock_required (ctx->skeleton) != MM_MODEM_LOCK_NONE && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_unlock_required && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_unlock_required_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_unlock_required ( self, (GAsyncReadyCallback)unlock_check_ready, ctx); return; } /* Just assume that no lock is required */ g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (MM_MODEM_LOCK_NONE), NULL); g_simple_async_result_complete_in_idle (ctx->result); unlock_check_context_free (ctx); } /*****************************************************************************/ /* Unlock retry count */ gboolean mm_iface_modem_update_unlock_retries_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) return FALSE; return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res)); } static void update_unlock_retries (MMIfaceModem *self, MMUnlockRetries *unlock_retries) { MmGdbusModem *skeleton = NULL; GError *error = NULL; GVariant *previous_dictionary; MMUnlockRetries *previous_unlock_retries; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); previous_dictionary = mm_gdbus_modem_get_unlock_retries (skeleton); previous_unlock_retries = mm_unlock_retries_new_from_dictionary (previous_dictionary); if (error) { mm_warn ("Couldn't build previous unlock retries: '%s'", error->message); g_error_free (error); } else { /* If they are different, update */ if (!mm_unlock_retries_cmp (unlock_retries, previous_unlock_retries)) { GVariant *new_dictionary; new_dictionary = mm_unlock_retries_get_dictionary (unlock_retries); mm_gdbus_modem_set_unlock_retries (skeleton, new_dictionary); g_variant_unref (new_dictionary); } } g_object_unref (previous_unlock_retries); g_object_unref (skeleton); } static void unlock_retries_ready (MMIfaceModem *self, GAsyncResult *res, GSimpleAsyncResult *simple) { GError *error = NULL; MMUnlockRetries *unlock_retries; unlock_retries = MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_retries_finish (self, res, &error); if (!unlock_retries) { g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); g_object_unref (simple); return; } /* Update the dictionary in the DBus interface */ update_unlock_retries (self, unlock_retries); g_object_unref (unlock_retries); g_simple_async_result_set_op_res_gboolean (simple, TRUE); g_simple_async_result_complete (simple); g_object_unref (simple); } void mm_iface_modem_update_unlock_retries (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, mm_iface_modem_update_unlock_retries); if (MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_retries && MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_retries_finish) { MM_IFACE_MODEM_GET_INTERFACE (self)->load_unlock_retries ( self, (GAsyncReadyCallback)unlock_retries_ready, result); return; } /* Return FALSE when we cannot load unlock retries */ g_simple_async_result_set_op_res_gboolean (result, FALSE); g_simple_async_result_complete_in_idle (result); g_object_unref (result); } /*****************************************************************************/ /* MODEM DISABLING */ typedef struct _DisablingContext DisablingContext; static void interface_disabling_step (DisablingContext *ctx); typedef enum { DISABLING_STEP_FIRST, DISABLING_STEP_CURRENT_BANDS, DISABLING_STEP_ALLOWED_MODES, DISABLING_STEP_MODEM_POWER_DOWN, DISABLING_STEP_CLOSE_PORTS, DISABLING_STEP_LAST } DisablingStep; struct _DisablingContext { MMIfaceModem *self; MMAtSerialPort *primary; MMAtSerialPort *secondary; MMQcdmSerialPort *qcdm; DisablingStep step; MMModemState previous_state; gboolean disabled; GSimpleAsyncResult *result; MmGdbusModem *skeleton; }; static DisablingContext * disabling_context_new (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { DisablingContext *ctx; ctx = g_new0 (DisablingContext, 1); ctx->self = g_object_ref (self); ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self)); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, disabling_context_new); ctx->step = DISABLING_STEP_FIRST; g_object_get (ctx->self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, MM_IFACE_MODEM_STATE, &ctx->previous_state, NULL); g_assert (ctx->skeleton != NULL); mm_iface_modem_update_state (ctx->self, MM_MODEM_STATE_DISABLING, MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED); return ctx; } static void disabling_context_complete_and_free (DisablingContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); if (ctx->disabled) mm_iface_modem_update_state (ctx->self, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED); else /* Fallback to previous state */ mm_iface_modem_update_state (ctx->self, ctx->previous_state, MM_MODEM_STATE_CHANGE_REASON_UNKNOWN); g_object_unref (ctx->self); if (ctx->primary) g_object_unref (ctx->primary); if (ctx->secondary) g_object_unref (ctx->secondary); if (ctx->qcdm) g_object_unref (ctx->qcdm); g_object_unref (ctx->result); g_object_unref (ctx->skeleton); g_free (ctx); } gboolean mm_iface_modem_disable_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void modem_power_down_ready (MMIfaceModem *self, GAsyncResult *res, DisablingContext *ctx) { GError *error = NULL; MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down_finish (self, res, &error); if (error) { g_simple_async_result_take_error (ctx->result, error); disabling_context_complete_and_free (ctx); return; } mm_dbg ("Modem properly powered down..."); /* Go on to next step */ ctx->step++; interface_disabling_step (ctx); } static void interface_disabling_step (DisablingContext *ctx) { switch (ctx->step) { case DISABLING_STEP_FIRST: /* Fall down to next step */ ctx->step++; case DISABLING_STEP_CURRENT_BANDS: /* Clear current bands */ mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_build_bands_unknown ()); /* Fall down to next step */ ctx->step++; case DISABLING_STEP_ALLOWED_MODES: /* Clear allowed/preferred modes */ mm_gdbus_modem_set_allowed_modes (ctx->skeleton, MM_MODEM_MODE_NONE); mm_gdbus_modem_set_preferred_mode (ctx->skeleton, MM_MODEM_MODE_NONE); /* Fall down to next step */ ctx->step++; case DISABLING_STEP_MODEM_POWER_DOWN: /* CFUN=0 is dangerous and often will shoot devices in the head (that's * what it's supposed to do). So don't use CFUN=0 by default, but let * specific plugins use it when they know it's safe to do so. For * example, CFUN=0 will often make phones turn themselves off, but some * dedicated devices (ex Sierra WWAN cards) will just turn off their * radio but otherwise still work. */ if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_down && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_down_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_down ( ctx->self, (GAsyncReadyCallback)modem_power_down_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case DISABLING_STEP_CLOSE_PORTS: /* While the modem is enabled ports are kept open, so we need to close * them when the modem gets disabled. As this (should) be the last * closing in order to get it really closed (open count = 1), it should * be safe to check whether they are really open before trying to close. */ mm_dbg ("Closing all ports..."); if (ctx->primary && mm_serial_port_is_open (MM_SERIAL_PORT (ctx->primary))) mm_serial_port_close (MM_SERIAL_PORT (ctx->primary)); if (ctx->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (ctx->secondary))) mm_serial_port_close (MM_SERIAL_PORT (ctx->secondary)); if (ctx->qcdm && mm_serial_port_is_open (MM_SERIAL_PORT (ctx->qcdm))) mm_serial_port_close (MM_SERIAL_PORT (ctx->qcdm)); /* Fall down to next step */ ctx->step++; case DISABLING_STEP_LAST: /* We are done without errors! */ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); ctx->disabled = TRUE; disabling_context_complete_and_free (ctx); return; } g_assert_not_reached (); } void mm_iface_modem_disable (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { interface_disabling_step (disabling_context_new (self, callback, user_data)); } /*****************************************************************************/ /* MODEM ENABLING */ typedef struct _EnablingContext EnablingContext; static void interface_enabling_step (EnablingContext *ctx); typedef enum { ENABLING_STEP_FIRST, ENABLING_STEP_OPEN_PORTS, ENABLING_STEP_FLASH_PORT, ENABLING_STEP_MODEM_INIT, ENABLING_STEP_MODEM_POWER_UP, ENABLING_STEP_MODEM_AFTER_POWER_UP, ENABLING_STEP_FLOW_CONTROL, ENABLING_STEP_SUPPORTED_CHARSETS, ENABLING_STEP_CHARSET, ENABLING_STEP_ALLOWED_MODES, ENABLING_STEP_CURRENT_BANDS, ENABLING_STEP_LAST } EnablingStep; struct _EnablingContext { MMIfaceModem *self; MMAtSerialPort *primary; gboolean primary_open; MMAtSerialPort *secondary; gboolean secondary_open; MMQcdmSerialPort *qcdm; gboolean qcdm_open; EnablingStep step; MMModemCharset supported_charsets; const MMModemCharset *current_charset; gboolean enabled; GSimpleAsyncResult *result; GCancellable *cancellable; MmGdbusModem *skeleton; }; static EnablingContext * enabling_context_new (MMIfaceModem *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { EnablingContext *ctx; ctx = g_new0 (EnablingContext, 1); ctx->self = g_object_ref (self); ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self)); ctx->cancellable = g_object_ref (cancellable); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, enabling_context_new); ctx->step = ENABLING_STEP_FIRST; g_object_get (ctx->self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, NULL); g_assert (ctx->skeleton != NULL); mm_iface_modem_update_state (ctx->self, MM_MODEM_STATE_ENABLING, MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED); return ctx; } static void enabling_context_complete_and_free (EnablingContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); if (ctx->enabled) mm_iface_modem_update_state (ctx->self, MM_MODEM_STATE_ENABLED, MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED); else { MMModemLock lock; /* Fallback to DISABLED/LOCKED */ lock = mm_gdbus_modem_get_unlock_required (ctx->skeleton); mm_iface_modem_update_state ( ctx->self, ((lock == MM_MODEM_LOCK_NONE || lock == MM_MODEM_LOCK_SIM_PIN2 || lock == MM_MODEM_LOCK_SIM_PUK2) ? MM_MODEM_STATE_DISABLED : MM_MODEM_STATE_LOCKED), MM_MODEM_STATE_CHANGE_REASON_UNKNOWN); /* Close the ports if enabling failed */ if (ctx->primary_open) mm_serial_port_close (MM_SERIAL_PORT (ctx->primary)); if (ctx->secondary_open) mm_serial_port_close (MM_SERIAL_PORT (ctx->secondary)); if (ctx->qcdm_open) mm_serial_port_close (MM_SERIAL_PORT (ctx->qcdm)); } g_object_unref (ctx->self); if (ctx->primary) g_object_unref (ctx->primary); if (ctx->secondary) g_object_unref (ctx->secondary); if (ctx->qcdm) g_object_unref (ctx->qcdm); g_object_unref (ctx->result); g_object_unref (ctx->cancellable); g_object_unref (ctx->skeleton); g_free (ctx); } static gboolean enabling_context_complete_and_free_if_cancelled (EnablingContext *ctx) { if (!g_cancellable_is_cancelled (ctx->cancellable)) return FALSE; g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Interface enabling cancelled"); enabling_context_complete_and_free (ctx); return TRUE; } gboolean mm_iface_modem_enable_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } #undef VOID_REPLY_READY_FN #define VOID_REPLY_READY_FN(NAME) \ static void \ NAME##_ready (MMIfaceModem *self, \ GAsyncResult *res, \ EnablingContext *ctx) \ { \ GError *error = NULL; \ \ MM_IFACE_MODEM_GET_INTERFACE (self)->NAME##_finish (self, res, &error); \ if (error) { \ g_simple_async_result_take_error (ctx->result, error); \ enabling_context_complete_and_free (ctx); \ return; \ } \ \ /* Go on to next step */ \ ctx->step++; \ interface_enabling_step (ctx); \ } VOID_REPLY_READY_FN (modem_init); VOID_REPLY_READY_FN (modem_power_up); VOID_REPLY_READY_FN (modem_after_power_up); VOID_REPLY_READY_FN (setup_flow_control); static void load_supported_charsets_ready (MMIfaceModem *self, GAsyncResult *res, EnablingContext *ctx) { GError *error = NULL; ctx->supported_charsets = MM_IFACE_MODEM_GET_INTERFACE (self)->load_supported_charsets_finish (self, res, &error); if (error) { mm_warn ("couldn't load Supported Charsets: '%s'", error->message); g_error_free (error); } /* Go on to next step */ ctx->step++; interface_enabling_step (ctx); } static void setup_charset_ready (MMIfaceModem *self, GAsyncResult *res, EnablingContext *ctx) { GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->setup_charset_finish (self, res, &error)) { mm_dbg ("couldn't set charset '%s': '%s'", mm_modem_charset_to_string (*ctx->current_charset), error->message); g_error_free (error); /* Will retry step with some other charset type */ } else /* Done, Go on to next step */ ctx->step++; interface_enabling_step (ctx); } static void load_allowed_modes_ready (MMIfaceModem *self, GAsyncResult *res, EnablingContext *ctx) { MMModemMode allowed = MM_MODEM_MODE_NONE; MMModemMode preferred = MM_MODEM_MODE_NONE; GError *error = NULL; if (!MM_IFACE_MODEM_GET_INTERFACE (self)->load_allowed_modes_finish (self, res, &allowed, &preferred, &error)) { /* Errors when getting allowed/preferred won't be critical */ mm_warn ("couldn't load current allowed/preferred modes: '%s'", error->message); g_error_free (error); /* If errors getting allowed modes, assume allowed=supported, * and none preferred */ allowed = mm_gdbus_modem_get_supported_modes (ctx->skeleton); preferred = MM_MODEM_MODE_NONE; } mm_gdbus_modem_set_allowed_modes (ctx->skeleton, allowed); mm_gdbus_modem_set_preferred_mode (ctx->skeleton, preferred); /* Done, Go on to next step */ ctx->step++; interface_enabling_step (ctx); } static void load_current_bands_ready (MMIfaceModem *self, GAsyncResult *res, EnablingContext *ctx) { GArray *bands_array; GError *error = NULL; bands_array = MM_IFACE_MODEM_GET_INTERFACE (self)->load_current_bands_finish (self, res, &error); if (bands_array) { mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_bands_garray_to_variant (bands_array)); g_array_unref (bands_array); } else mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_build_bands_unknown ()); /* Errors when getting current bands won't be critical */ if (error) { mm_warn ("couldn't load current Bands: '%s'", error->message); g_error_free (error); } /* Done, Go on to next step */ ctx->step++; interface_enabling_step (ctx); } static void interface_enabling_flash_done (MMSerialPort *port, GError *error, gpointer user_data) { EnablingContext *ctx = user_data; if (error) { g_simple_async_result_set_from_error (ctx->result, error); enabling_context_complete_and_free (ctx); return; } /* Go on to next step */ ctx->step++; interface_enabling_step (ctx); } static const MMModemCharset best_charsets[] = { MM_MODEM_CHARSET_UTF8, MM_MODEM_CHARSET_UCS2, MM_MODEM_CHARSET_8859_1, MM_MODEM_CHARSET_IRA, MM_MODEM_CHARSET_GSM, MM_MODEM_CHARSET_UNKNOWN }; static void interface_enabling_step (EnablingContext *ctx) { /* Don't run new steps if we're cancelled */ if (enabling_context_complete_and_free_if_cancelled (ctx)) return; switch (ctx->step) { case ENABLING_STEP_FIRST: /* Fall down to next step */ ctx->step++; case ENABLING_STEP_OPEN_PORTS: { GError *error = NULL; /* Open primary port */ if (!ctx->primary) { g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot enable: no primary port"); enabling_context_complete_and_free (ctx); return; } if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->primary), &error)) { g_simple_async_result_take_error (ctx->result, error); enabling_context_complete_and_free (ctx); return; } ctx->primary_open = TRUE; /* If there is a secondary AT port, open it */ if (ctx->secondary) { if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->secondary), &error)) { g_simple_async_result_take_error (ctx->result, error); enabling_context_complete_and_free (ctx); return; } ctx->secondary_open = TRUE; } /* If there is a qcdm AT port, open it */ if (ctx->qcdm) { if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->qcdm), &error)) { g_simple_async_result_take_error (ctx->result, error); enabling_context_complete_and_free (ctx); return; } ctx->qcdm_open = TRUE; } /* Fall down to next step */ ctx->step++; } case ENABLING_STEP_FLASH_PORT: /* Flash port */ mm_serial_port_flash (MM_SERIAL_PORT (ctx->primary), 100, FALSE, interface_enabling_flash_done, ctx); return; case ENABLING_STEP_MODEM_INIT: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_init && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_init_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_init ( ctx->self, (GAsyncReadyCallback)modem_init_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_MODEM_POWER_UP: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_up && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_up_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_power_up ( ctx->self, (GAsyncReadyCallback)modem_power_up_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_MODEM_AFTER_POWER_UP: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_after_power_up && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_after_power_up_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->modem_after_power_up ( ctx->self, (GAsyncReadyCallback)modem_after_power_up_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_FLOW_CONTROL: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_flow_control && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_flow_control_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_flow_control ( ctx->self, (GAsyncReadyCallback)setup_flow_control_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_SUPPORTED_CHARSETS: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_charsets && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_charsets_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_charsets ( ctx->self, (GAsyncReadyCallback)load_supported_charsets_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_CHARSET: /* Only try to set charsets if we were able to load supported ones */ if (ctx->supported_charsets > 0 && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_charset && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_charset_finish) { gboolean next_to_try = FALSE; while (!next_to_try) { if (!ctx->current_charset) /* Switch the device's charset; we prefer UTF-8, but UCS2 will do too */ ctx->current_charset = &best_charsets[0]; else /* Try with the next one */ ctx->current_charset++; if (*ctx->current_charset == MM_MODEM_CHARSET_UNKNOWN) break; if (ctx->supported_charsets & (*ctx->current_charset)) next_to_try = TRUE; } if (next_to_try) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->setup_charset ( ctx->self, *ctx->current_charset, (GAsyncReadyCallback)setup_charset_ready, ctx); return; } g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to find a usable modem character set"); enabling_context_complete_and_free (ctx); return; } /* Fall down to next step */ ctx->step++; case ENABLING_STEP_ALLOWED_MODES: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_allowed_modes && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_allowed_modes_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_allowed_modes ( ctx->self, (GAsyncReadyCallback)load_allowed_modes_ready, ctx); return; } /* If no way to get allowed modes, assume allowed=supported, * and none preferred */ mm_gdbus_modem_set_allowed_modes (ctx->skeleton, mm_gdbus_modem_get_supported_modes (ctx->skeleton)); mm_gdbus_modem_set_preferred_mode (ctx->skeleton, MM_MODEM_MODE_NONE); /* Fall down to next step */ ctx->step++; case ENABLING_STEP_CURRENT_BANDS: if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_bands && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_bands_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_bands ( ctx->self, (GAsyncReadyCallback)load_current_bands_ready, ctx); return; } else mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_build_bands_unknown ()); /* Fall down to next step */ ctx->step++; case ENABLING_STEP_LAST: /* We are done without errors! */ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); ctx->enabled = TRUE; enabling_context_complete_and_free (ctx); return; } g_assert_not_reached (); } void mm_iface_modem_enable (MMIfaceModem *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { interface_enabling_step (enabling_context_new (self, cancellable, callback, user_data)); } /*****************************************************************************/ /* MODEM INITIALIZATION */ typedef struct _InitializationContext InitializationContext; static void interface_initialization_step (InitializationContext *ctx); typedef enum { INITIALIZATION_STEP_FIRST, INITIALIZATION_STEP_CURRENT_CAPABILITIES, INITIALIZATION_STEP_MODEM_CAPABILITIES, INITIALIZATION_STEP_BEARERS, INITIALIZATION_STEP_MANUFACTURER, INITIALIZATION_STEP_MODEL, INITIALIZATION_STEP_REVISION, INITIALIZATION_STEP_EQUIPMENT_ID, INITIALIZATION_STEP_DEVICE_ID, INITIALIZATION_STEP_OWN_NUMBERS, INITIALIZATION_STEP_UNLOCK_REQUIRED, INITIALIZATION_STEP_UNLOCK_RETRIES, INITIALIZATION_STEP_SIM, INITIALIZATION_STEP_SUPPORTED_MODES, INITIALIZATION_STEP_SUPPORTED_BANDS, INITIALIZATION_STEP_LAST } InitializationStep; struct _InitializationContext { MMIfaceModem *self; InitializationStep step; GSimpleAsyncResult *result; GCancellable *cancellable; MmGdbusModem *skeleton; GError *fatal_error; }; static InitializationContext * initialization_context_new (MMIfaceModem *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { InitializationContext *ctx; ctx = g_new0 (InitializationContext, 1); ctx->self = g_object_ref (self); ctx->cancellable = g_object_ref (cancellable); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, initialization_context_new); ctx->step = INITIALIZATION_STEP_FIRST; g_object_get (ctx->self, MM_IFACE_MODEM_DBUS_SKELETON, &ctx->skeleton, NULL); g_assert (ctx->skeleton != NULL); return ctx; } static void initialization_context_complete_and_free (InitializationContext *ctx) { g_assert (ctx->fatal_error == NULL); g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->cancellable); g_object_unref (ctx->self); g_object_unref (ctx->result); g_object_unref (ctx->skeleton); g_free (ctx); } static gboolean initialization_context_complete_and_free_if_cancelled (InitializationContext *ctx) { if (!g_cancellable_is_cancelled (ctx->cancellable)) return FALSE; g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Interface initialization cancelled"); initialization_context_complete_and_free (ctx); return TRUE; } #undef STR_REPLY_READY_FN #define STR_REPLY_READY_FN(NAME,DISPLAY) \ static void \ load_##NAME##_ready (MMIfaceModem *self, \ GAsyncResult *res, \ InitializationContext *ctx) \ { \ GError *error = NULL; \ gchar *val; \ \ val = MM_IFACE_MODEM_GET_INTERFACE (self)->load_##NAME##_finish (self, res, &error); \ mm_gdbus_modem_set_##NAME (ctx->skeleton, val); \ g_free (val); \ \ if (error) { \ mm_warn ("couldn't load %s: '%s'", DISPLAY, error->message); \ g_error_free (error); \ } \ \ /* Go on to next step */ \ ctx->step++; \ interface_initialization_step (ctx); \ } #undef UINT_REPLY_READY_FN #define UINT_REPLY_READY_FN(NAME,DISPLAY,FATAL) \ static void \ load_##NAME##_ready (MMIfaceModem *self, \ GAsyncResult *res, \ InitializationContext *ctx) \ { \ GError *error = NULL; \ \ mm_gdbus_modem_set_##NAME ( \ ctx->skeleton, \ MM_IFACE_MODEM_GET_INTERFACE (self)->load_##NAME##_finish (self, res, &error)); \ \ if (error) { \ if (FATAL) { \ g_propagate_error (&ctx->fatal_error, error); \ g_prefix_error (&ctx->fatal_error, "couldn't load %s: ", DISPLAY); \ /* Jump to the last step */ \ ctx->step = INITIALIZATION_STEP_LAST; \ } else { \ mm_warn ("couldn't load %s: '%s'", DISPLAY, error->message); \ g_error_free (error); \ /* Go on to next step */ \ ctx->step++; \ } \ } else { \ /* Go on to next step */ \ ctx->step++; \ } \ \ interface_initialization_step (ctx); \ } UINT_REPLY_READY_FN (current_capabilities, "Current Capabilities", TRUE) UINT_REPLY_READY_FN (modem_capabilities, "Modem Capabilities", FALSE) STR_REPLY_READY_FN (manufacturer, "Manufacturer") STR_REPLY_READY_FN (model, "Model") STR_REPLY_READY_FN (revision, "Revision") STR_REPLY_READY_FN (equipment_identifier, "Equipment Identifier") STR_REPLY_READY_FN (device_identifier, "Device Identifier") static void load_own_numbers_ready (MMIfaceModem *self, GAsyncResult *res, InitializationContext *ctx) { GError *error = NULL; GStrv str_list; str_list = MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers_finish (self, res, &error); if (error) { mm_warn ("couldn't load list of Own Numbers: '%s'", error->message); g_error_free (error); } if (str_list) { mm_gdbus_modem_set_own_numbers (ctx->skeleton, (const gchar *const *) str_list); g_strfreev (str_list); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void load_supported_modes_ready (MMIfaceModem *self, GAsyncResult *res, InitializationContext *ctx) { GError *error = NULL; MMModemMode modes; modes = MM_IFACE_MODEM_GET_INTERFACE (self)->load_supported_modes_finish (self, res, &error); if (modes != MM_MODEM_MODE_NONE) { mm_gdbus_modem_set_supported_modes (ctx->skeleton, modes); mm_gdbus_modem_set_allowed_modes (ctx->skeleton, modes); } if (error) { mm_warn ("couldn't load Supported Modes: '%s'", error->message); g_error_free (error); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void load_supported_bands_ready (MMIfaceModem *self, GAsyncResult *res, InitializationContext *ctx) { GError *error = NULL; GArray *bands_array; bands_array = MM_IFACE_MODEM_GET_INTERFACE (self)->load_supported_bands_finish (self, res, &error); if (bands_array) { mm_gdbus_modem_set_supported_bands (ctx->skeleton, mm_common_bands_garray_to_variant (bands_array)); g_array_unref (bands_array); } if (error) { mm_warn ("couldn't load Supported Bands: '%s'", error->message); g_error_free (error); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void load_unlock_required_ready (MMIfaceModem *self, GAsyncResult *res, InitializationContext *ctx) { /* NOTE: we already propagated the lock state, no need to do it again */ mm_iface_modem_unlock_check_finish (self, res, &ctx->fatal_error); if (ctx->fatal_error) { g_prefix_error (&ctx->fatal_error, "Couldn't check unlock status: "); /* Jump to the last step */ ctx->step = INITIALIZATION_STEP_LAST; } else /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void update_unlock_retries_ready (MMIfaceModem *self, GAsyncResult *res, InitializationContext *ctx) { GError *error = NULL; mm_iface_modem_update_unlock_retries_finish (self, res, &error); if (error) { mm_warn ("couldn't update unlock retries: '%s'", error->message); g_error_free (error); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void sim_new_ready (GAsyncInitable *initable, GAsyncResult *res, InitializationContext *ctx) { MMSim *sim; GError *error = NULL; sim = MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->create_sim_finish (ctx->self, res, &error); if (error) { mm_warn ("couldn't create SIM: '%s'", error->message); g_simple_async_result_take_error (ctx->result, error); initialization_context_complete_and_free (ctx); return; } /* We may get error with !sim, when the implementation doesn't want to * handle any (e.g. CDMA) */ if (sim) { g_object_bind_property (sim, MM_SIM_PATH, ctx->skeleton, "sim", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_set (ctx->self, MM_IFACE_MODEM_SIM, sim, NULL); g_object_unref (sim); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void sim_reinit_ready (MMSim *sim, GAsyncResult *res, InitializationContext *ctx) { GError *error = NULL; if (!mm_sim_initialize_finish (sim, res, &error)) { mm_warn ("SIM re-initialization failed: '%s'", error ? error->message : "Unknown error"); g_clear_error (&error); } /* Go on to next step */ ctx->step++; interface_initialization_step (ctx); } static void interface_initialization_step (InitializationContext *ctx) { /* Don't run new steps if we're cancelled */ if (initialization_context_complete_and_free_if_cancelled (ctx)) return; switch (ctx->step) { case INITIALIZATION_STEP_FIRST: /* Load device if not done before */ if (!mm_gdbus_modem_get_device (ctx->skeleton)) { gchar *device; g_object_get (ctx->self, MM_BASE_MODEM_DEVICE, &device, NULL); mm_gdbus_modem_set_device (ctx->skeleton, device); g_free (device); } /* Load driver if not done before */ if (!mm_gdbus_modem_get_driver (ctx->skeleton)) { gchar *driver; g_object_get (ctx->self, MM_BASE_MODEM_DRIVER, &driver, NULL); mm_gdbus_modem_set_driver (ctx->skeleton, driver); g_free (driver); } /* Load plugin if not done before */ if (!mm_gdbus_modem_get_plugin (ctx->skeleton)) { gchar *plugin; g_object_get (ctx->self, MM_BASE_MODEM_PLUGIN, &plugin, NULL); mm_gdbus_modem_set_plugin (ctx->skeleton, plugin); g_free (plugin); } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_CURRENT_CAPABILITIES: /* Current capabilities may change during runtime, i.e. if new firmware reloaded; but we'll * try to handle that by making sure the capabilities are cleared when the new firmware is * reloaded. So if we're asked to re-initialize, if we already have current capabilities loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_current_capabilities (ctx->skeleton) == MM_MODEM_CAPABILITY_NONE && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_capabilities && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_capabilities_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_current_capabilities ( ctx->self, (GAsyncReadyCallback)load_current_capabilities_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_MODEM_CAPABILITIES: /* Modem capabilities are meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_modem_capabilities (ctx->skeleton) == MM_MODEM_CAPABILITY_NONE && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_modem_capabilities && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_modem_capabilities_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_modem_capabilities ( ctx->self, (GAsyncReadyCallback)load_modem_capabilities_ready, ctx); return; } /* If no specific way of getting modem capabilities, assume they are * equal to the current capabilities */ mm_gdbus_modem_set_modem_capabilities ( ctx->skeleton, mm_gdbus_modem_get_current_capabilities (ctx->skeleton)); /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_BEARERS: { MMBearerList *list = NULL; /* Bearers setup is meant to be loaded only once during the whole * lifetime of the modem. The list may have been created by the object * implementing the interface; if so use it. */ g_object_get (ctx->self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); if (!list) { list = mm_bearer_list_new (1, 1); /* Create new default list */ g_object_set (ctx->self, MM_IFACE_MODEM_BEARER_LIST, list, NULL); } if (mm_gdbus_modem_get_max_bearers (ctx->skeleton) == 0) mm_gdbus_modem_set_max_bearers ( ctx->skeleton, mm_bearer_list_get_max (list)); if (mm_gdbus_modem_get_max_active_bearers (ctx->skeleton) == 0) mm_gdbus_modem_set_max_active_bearers ( ctx->skeleton, mm_bearer_list_get_max_active (list)); g_object_unref (list); /* Fall down to next step */ ctx->step++; } case INITIALIZATION_STEP_MANUFACTURER: /* Manufacturer is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_manufacturer (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_manufacturer && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_manufacturer_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_manufacturer ( ctx->self, (GAsyncReadyCallback)load_manufacturer_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_MODEL: /* Model is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_model (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_model && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_model_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_model ( ctx->self, (GAsyncReadyCallback)load_model_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_REVISION: /* Revision is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_revision (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_revision && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_revision_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_revision ( ctx->self, (GAsyncReadyCallback)load_revision_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_EQUIPMENT_ID: /* Equipment ID is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_equipment_identifier (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_equipment_identifier && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_equipment_identifier_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_equipment_identifier ( ctx->self, (GAsyncReadyCallback)load_equipment_identifier_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_DEVICE_ID: /* Device ID is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_device_identifier (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_device_identifier && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_device_identifier_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_device_identifier ( ctx->self, (GAsyncReadyCallback)load_device_identifier_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_OWN_NUMBERS: /* Own numbers is meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_own_numbers (ctx->skeleton) == NULL && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_own_numbers && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_own_numbers_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_own_numbers ( ctx->self, (GAsyncReadyCallback)load_own_numbers_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_UNLOCK_REQUIRED: /* Only check unlock required if we were previously not unlocked */ if (mm_gdbus_modem_get_unlock_required (ctx->skeleton) != MM_MODEM_LOCK_NONE) { mm_iface_modem_unlock_check (ctx->self, (GAsyncReadyCallback)load_unlock_required_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_UNLOCK_RETRIES: mm_iface_modem_update_unlock_retries (ctx->self, (GAsyncReadyCallback)update_unlock_retries_ready, ctx); return; case INITIALIZATION_STEP_SIM: /* If the modem doesn't need any SIM, skip */ if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->create_sim && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->create_sim_finish) { MMSim *sim = NULL; g_object_get (ctx->self, MM_IFACE_MODEM_SIM, &sim, NULL); if (!sim) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->create_sim ( MM_IFACE_MODEM (ctx->self), (GAsyncReadyCallback)sim_new_ready, ctx); return; } /* If already available the sim object, relaunch initialization. * This will try to load any missing property value that couldn't be * retrieved before due to having the SIM locked. */ mm_sim_initialize (sim, NULL, /* TODO: cancellable */ (GAsyncReadyCallback)sim_reinit_ready, ctx); g_object_unref (sim); return; } case INITIALIZATION_STEP_SUPPORTED_MODES: g_assert (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_modes != NULL); g_assert (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_modes_finish != NULL); /* Supported modes are meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (mm_gdbus_modem_get_supported_modes (ctx->skeleton) == MM_MODEM_MODE_NONE) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_modes ( ctx->self, (GAsyncReadyCallback)load_supported_modes_ready, ctx); return; } /* Fall down to next step */ ctx->step++; case INITIALIZATION_STEP_SUPPORTED_BANDS: { GArray *supported_bands; supported_bands = (mm_common_bands_variant_to_garray ( mm_gdbus_modem_get_supported_bands (ctx->skeleton))); /* Supported bands are meant to be loaded only once during the whole * lifetime of the modem. Therefore, if we already have them loaded, * don't try to load them again. */ if (supported_bands->len == 0 || g_array_index (supported_bands, MMModemBand, 0) == MM_MODEM_BAND_UNKNOWN) { if (MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_bands && MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_bands_finish) { MM_IFACE_MODEM_GET_INTERFACE (ctx->self)->load_supported_bands ( ctx->self, (GAsyncReadyCallback)load_supported_bands_ready, ctx); g_array_unref (supported_bands); return; } /* Loading supported bands not implemented, default to ANY */ mm_gdbus_modem_set_supported_bands (ctx->skeleton, mm_common_build_bands_any ()); mm_gdbus_modem_set_bands (ctx->skeleton, mm_common_build_bands_any ()); } g_array_unref (supported_bands); /* Fall down to next step */ ctx->step++; } case INITIALIZATION_STEP_LAST: if (ctx->fatal_error) { g_simple_async_result_take_error (ctx->result, ctx->fatal_error); ctx->fatal_error = NULL; } else { /* We are done without errors! * Handle method invocations */ g_signal_connect (ctx->skeleton, "handle-create-bearer", G_CALLBACK (handle_create_bearer), ctx->self); g_signal_connect (ctx->skeleton, "handle-command", G_CALLBACK (handle_command), ctx->self); g_signal_connect (ctx->skeleton, "handle-delete-bearer", G_CALLBACK (handle_delete_bearer), ctx->self); g_signal_connect (ctx->skeleton, "handle-list-bearers", G_CALLBACK (handle_list_bearers), ctx->self); g_signal_connect (ctx->skeleton, "handle-enable", G_CALLBACK (handle_enable), ctx->self); g_signal_connect (ctx->skeleton, "handle-reset", G_CALLBACK (handle_reset), ctx->self); g_signal_connect (ctx->skeleton, "handle-factory-reset", G_CALLBACK (handle_factory_reset), ctx->self); g_signal_connect (ctx->skeleton, "handle-set-bands", G_CALLBACK (handle_set_bands), ctx->self); g_signal_connect (ctx->skeleton, "handle-set-allowed-modes", G_CALLBACK (handle_set_allowed_modes), ctx->self); g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); } /* Finally, export the new interface, even if we got errors */ mm_gdbus_object_skeleton_set_modem (MM_GDBUS_OBJECT_SKELETON (ctx->self), MM_GDBUS_MODEM (ctx->skeleton)); initialization_context_complete_and_free (ctx); return; } g_assert_not_reached (); } gboolean mm_iface_modem_initialize_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { g_return_val_if_fail (MM_IS_IFACE_MODEM (self), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } void mm_iface_modem_initialize (MMIfaceModem *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { MmGdbusModem *skeleton = NULL; g_return_if_fail (MM_IS_IFACE_MODEM (self)); /* Did we already create it? */ g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); if (!skeleton) { skeleton = mm_gdbus_modem_skeleton_new (); /* Set all initial property defaults */ mm_gdbus_modem_set_sim (skeleton, NULL); mm_gdbus_modem_set_current_capabilities (skeleton, MM_MODEM_CAPABILITY_NONE); mm_gdbus_modem_set_modem_capabilities (skeleton, MM_MODEM_CAPABILITY_NONE); mm_gdbus_modem_set_max_bearers (skeleton, 0); mm_gdbus_modem_set_max_active_bearers (skeleton, 0); mm_gdbus_modem_set_manufacturer (skeleton, NULL); mm_gdbus_modem_set_model (skeleton, NULL); mm_gdbus_modem_set_revision (skeleton, NULL); mm_gdbus_modem_set_own_numbers (skeleton, NULL); mm_gdbus_modem_set_device_identifier (skeleton, NULL); mm_gdbus_modem_set_device (skeleton, NULL); mm_gdbus_modem_set_driver (skeleton, NULL); mm_gdbus_modem_set_plugin (skeleton, NULL); mm_gdbus_modem_set_equipment_identifier (skeleton, NULL); mm_gdbus_modem_set_unlock_required (skeleton, MM_MODEM_LOCK_UNKNOWN); mm_gdbus_modem_set_unlock_retries (skeleton, 0); mm_gdbus_modem_set_access_technologies (skeleton, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); mm_gdbus_modem_set_signal_quality (skeleton, g_variant_new ("(ub)", 0, FALSE)); mm_gdbus_modem_set_supported_modes (skeleton, MM_MODEM_MODE_NONE); mm_gdbus_modem_set_allowed_modes (skeleton, MM_MODEM_MODE_NONE); mm_gdbus_modem_set_preferred_mode (skeleton, MM_MODEM_MODE_NONE); mm_gdbus_modem_set_supported_bands (skeleton, mm_common_build_bands_unknown ()); mm_gdbus_modem_set_bands (skeleton, mm_common_build_bands_unknown ()); /* Bind our State property */ g_object_bind_property (self, MM_IFACE_MODEM_STATE, skeleton, "state", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_set (self, MM_IFACE_MODEM_DBUS_SKELETON, skeleton, NULL); } /* Perform async initialization here */ interface_initialization_step (initialization_context_new (self, cancellable, callback, user_data)); g_object_unref (skeleton); return; } void mm_iface_modem_shutdown (MMIfaceModem *self) { g_return_if_fail (MM_IS_IFACE_MODEM (self)); /* Remove SignalQualityCheckContext object to make sure any pending * invocation of periodic_signal_quality_check is cancelled before * SignalQualityUpdateContext is removed (as signal_quality_check_ready may * call update_signal_quality). */ if (G_LIKELY (signal_quality_check_context_quark)) g_object_set_qdata (G_OBJECT (self), signal_quality_check_context_quark, NULL); /* Remove SignalQualityUpdateContext object to make sure any pending * invocation of expire_signal_quality is canceled before the DBus skeleton * is removed. */ if (G_LIKELY (signal_quality_update_context_quark)) g_object_set_qdata (G_OBJECT (self), signal_quality_update_context_quark, NULL); /* Remove AccessTechnologiesCheckContext object to make sure any pending * invocation of periodic_access_technologies_check is canceled before the * DBus skeleton is removed. */ if (G_LIKELY (access_technologies_check_context_quark)) g_object_set_qdata (G_OBJECT (self), access_technologies_check_context_quark, NULL); /* Remove SIM object */ g_object_set (self, MM_IFACE_MODEM_SIM, NULL, NULL); /* Unexport DBus interface and remove the skeleton */ mm_gdbus_object_skeleton_set_modem (MM_GDBUS_OBJECT_SKELETON (self), NULL); g_object_set (self, MM_IFACE_MODEM_DBUS_SKELETON, NULL, NULL); } /*****************************************************************************/ MMModemMode mm_iface_modem_get_supported_modes (MMIfaceModem *self) { MMModemMode supported = MM_MODEM_MODE_NONE; MmGdbusModem *skeleton; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); if (skeleton) { supported = mm_gdbus_modem_get_supported_modes (skeleton); g_object_unref (skeleton); } return supported; } gboolean mm_iface_modem_is_2g (MMIfaceModem *self) { return (mm_iface_modem_get_supported_modes (self) & MM_MODEM_MODE_2G); } gboolean mm_iface_modem_is_2g_only (MMIfaceModem *self) { MMModemMode supported; supported = mm_iface_modem_get_supported_modes (self); return !((MM_MODEM_MODE_2G ^ supported) & supported); } gboolean mm_iface_modem_is_3g (MMIfaceModem *self) { return (mm_iface_modem_get_supported_modes (self) & MM_MODEM_MODE_3G); } gboolean mm_iface_modem_is_3g_only (MMIfaceModem *self) { MMModemMode supported; supported = mm_iface_modem_get_supported_modes (self); return !((MM_MODEM_MODE_3G ^ supported) & supported); } gboolean mm_iface_modem_is_4g (MMIfaceModem *self) { return (mm_iface_modem_get_supported_modes (self) & MM_MODEM_MODE_4G); } gboolean mm_iface_modem_is_4g_only (MMIfaceModem *self) { MMModemMode supported; supported = mm_iface_modem_get_supported_modes (self); return !((MM_MODEM_MODE_4G ^ supported) & supported); } /*****************************************************************************/ MMModemCapability mm_iface_modem_get_current_capabilities (MMIfaceModem *self) { MMModemCapability current = MM_MODEM_CAPABILITY_NONE; MmGdbusModem *skeleton; g_object_get (self, MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, NULL); if (skeleton) { current = mm_gdbus_modem_get_current_capabilities (skeleton); g_object_unref (skeleton); } return current; } gboolean mm_iface_modem_is_3gpp (MMIfaceModem *self) { return (mm_iface_modem_get_current_capabilities (self) & MM_MODEM_CAPABILITY_3GPP); } gboolean mm_iface_modem_is_3gpp_lte (MMIfaceModem *self) { return (mm_iface_modem_get_current_capabilities (self) & MM_MODEM_CAPABILITY_3GPP_LTE); } gboolean mm_iface_modem_is_cdma (MMIfaceModem *self) { return (mm_iface_modem_get_current_capabilities (self) & MM_MODEM_CAPABILITY_CDMA_EVDO); } gboolean mm_iface_modem_is_3gpp_only (MMIfaceModem *self) { MMModemCapability capabilities; capabilities = mm_iface_modem_get_current_capabilities (self); return !((MM_MODEM_CAPABILITY_3GPP ^ capabilities) & capabilities); } gboolean mm_iface_modem_is_3gpp_lte_only (MMIfaceModem *self) { MMModemCapability capabilities; capabilities = mm_iface_modem_get_current_capabilities (self); return !((MM_MODEM_CAPABILITY_3GPP_LTE ^ capabilities) & capabilities); } gboolean mm_iface_modem_is_cdma_only (MMIfaceModem *self) { MMModemCapability capabilities; capabilities = mm_iface_modem_get_current_capabilities (self); return !((MM_MODEM_CAPABILITY_CDMA_EVDO ^ capabilities) & capabilities); } /*****************************************************************************/ static void iface_modem_init (gpointer g_iface) { static gboolean initialized = FALSE; if (initialized) return; /* Properties */ g_object_interface_install_property (g_iface, g_param_spec_object (MM_IFACE_MODEM_DBUS_SKELETON, "Modem DBus skeleton", "DBus skeleton for the Modem interface", MM_GDBUS_TYPE_MODEM_SKELETON, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_object (MM_IFACE_MODEM_SIM, "SIM", "SIM object", MM_TYPE_SIM, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_enum (MM_IFACE_MODEM_STATE, "State", "State of the modem", MM_TYPE_MODEM_STATE, MM_MODEM_STATE_UNKNOWN, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_object (MM_IFACE_MODEM_BEARER_LIST, "Bearer list", "List of bearers handled by the modem", MM_TYPE_BEARER_LIST, G_PARAM_READWRITE)); initialized = TRUE; } GType mm_iface_modem_get_type (void) { static GType iface_modem_type = 0; if (!G_UNLIKELY (iface_modem_type)) { static const GTypeInfo info = { sizeof (MMIfaceModem), /* class_size */ iface_modem_init, /* base_init */ NULL, /* base_finalize */ }; iface_modem_type = g_type_register_static (G_TYPE_INTERFACE, "MMIfaceModem", &info, 0); g_type_interface_add_prerequisite (iface_modem_type, MM_TYPE_BASE_MODEM); } return iface_modem_type; }