| /* -*- 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 <ModemManager.h> |
| #include <libmm-common.h> |
| |
| #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); |
| |
| 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 */ |
| ctx = g_object_get_qdata (G_OBJECT (self), access_technologies_check_context_quark); |
| 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; |
| |
| 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); |
| |
| g_object_get (self, |
| MM_IFACE_MODEM_DBUS_SKELETON, &skeleton, |
| 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 */ |
| ctx = g_object_get_qdata (G_OBJECT (self), signal_quality_check_context_quark); |
| 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 gboolean |
| restart_initialize_idle (MMIfaceModem *self) |
| { |
| 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 { |
| /* Fallback to DISABLED/LOCKED */ |
| mm_iface_modem_update_state ( |
| ctx->self, |
| (mm_gdbus_modem_get_unlock_required (ctx->skeleton) == MM_MODEM_LOCK_NONE ? |
| 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 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; |
| } |