/* -*- 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) 2019 Daniele Palmas */ #include #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-log-object.h" #include "mm-iface-modem.h" #include "mm-iface-modem-location.h" #include "mm-base-modem.h" #include "mm-base-modem-at.h" #include "mm-modem-helpers-telit.h" #include "mm-shared-telit.h" /*****************************************************************************/ /* Private data context */ #define TELIT_LM940_EXT_LTE_BND_SW_REVISION "24.01.516" #define PRIVATE_TAG "shared-telit-private-tag" static GQuark private_quark; typedef struct { MMIfaceModem *iface_modem_parent; gboolean alternate_3g_bands; gboolean ext_4g_bands; GArray *supported_bands; GArray *supported_modes; gchar *software_package_version; } Private; static void private_free (Private *priv) { if (priv->supported_bands) g_array_unref (priv->supported_bands); if (priv->supported_modes) g_array_unref (priv->supported_modes); g_free (priv->software_package_version); g_slice_free (Private, priv); } static gboolean has_alternate_3g_bands (const gchar *revision) { MMTelitModel model; model = mm_telit_model_from_revision (revision); return (model == MM_TELIT_MODEL_FN980 || model == MM_TELIT_MODEL_FN990 || model == MM_TELIT_MODEL_LM940 || model == MM_TELIT_MODEL_LM960 || model == MM_TELIT_MODEL_LN920); } static gboolean is_bnd_4g_format_hex (const gchar *revision) { MMTelitModel model; model = mm_telit_model_from_revision (revision); return (model == MM_TELIT_MODEL_FN980 || model == MM_TELIT_MODEL_LE910C1 || model == MM_TELIT_MODEL_LM940 || model == MM_TELIT_MODEL_LM960 || model == MM_TELIT_MODEL_LN920); } static gboolean has_extended_4g_bands (const gchar *revision) { MMTelitModel model; model = mm_telit_model_from_revision (revision); if (model == MM_TELIT_MODEL_LM940) return mm_telit_software_revision_cmp (revision, TELIT_LM940_EXT_LTE_BND_SW_REVISION) >= MM_TELIT_SW_REV_CMP_EQUAL; return (model == MM_TELIT_MODEL_FN980 || model == MM_TELIT_MODEL_FN990 || model == MM_TELIT_MODEL_LM960 || model == MM_TELIT_MODEL_LN920); } static Private * get_private (MMSharedTelit *self) { Private *priv; if (G_UNLIKELY (!private_quark)) private_quark = g_quark_from_static_string (PRIVATE_TAG); priv = g_object_get_qdata (G_OBJECT (self), private_quark); if (!priv) { priv = g_slice_new0 (Private); if (MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface) priv->iface_modem_parent = MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface (self); g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); } return priv; } void mm_shared_telit_store_supported_modes (MMSharedTelit *self, GArray *modes) { Private *priv; priv = get_private (MM_SHARED_TELIT (self)); priv->supported_modes = g_array_ref (modes); } void mm_shared_telit_store_revision (MMSharedTelit *self, const gchar *revision) { Private *priv; priv = get_private (MM_SHARED_TELIT (self)); g_clear_pointer (&priv->software_package_version, g_free); priv->software_package_version = g_strdup (revision); priv->alternate_3g_bands = has_alternate_3g_bands (revision); priv->ext_4g_bands = has_extended_4g_bands (revision); } void mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self, MMTelitBNDParseConfig *config) { Private *priv; priv = get_private (MM_SHARED_TELIT (self)); config->modem_is_2g = mm_iface_modem_is_2g (self); config->modem_is_3g = mm_iface_modem_is_3g (self); config->modem_is_4g = mm_iface_modem_is_4g (self); config->modem_alternate_3g_bands = priv->alternate_3g_bands; config->modem_has_hex_format_4g_bands = is_bnd_4g_format_hex (priv->software_package_version); config->modem_ext_4g_bands = priv->ext_4g_bands; } /*****************************************************************************/ /* Load current mode (Modem interface) */ gboolean mm_shared_telit_load_current_modes_finish (MMIfaceModem *self, GAsyncResult *res, MMModemMode *allowed, MMModemMode *preferred, GError **error) { const gchar *response; const gchar *str; gint a; response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); if (!response) return FALSE; str = mm_strip_tag (response, "+WS46: "); if (!sscanf (str, "%d", &a)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +WS46 response: '%s'", response); return FALSE; } *preferred = MM_MODEM_MODE_NONE; switch (a) { case 12: *allowed = MM_MODEM_MODE_2G; return TRUE; case 22: *allowed = MM_MODEM_MODE_3G; return TRUE; case 25: if (mm_iface_modem_is_3gpp_lte (self)) *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); else *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); return TRUE; case 28: *allowed = MM_MODEM_MODE_4G; return TRUE; case 29: *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); return TRUE; case 30: *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); return TRUE; case 31: *allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); return TRUE; case 36: *allowed = MM_MODEM_MODE_5G; return TRUE; case 37: *allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_5G); return TRUE; case 38: *allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G); return TRUE; case 40: *allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_5G); return TRUE; default: break; } g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse unexpected +WS46 response: '%s'", response); return FALSE; } void mm_shared_telit_load_current_modes (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { mm_base_modem_at_command (MM_BASE_MODEM (self), "+WS46?", 3, FALSE, callback, user_data); } /*****************************************************************************/ /* Load supported bands (Modem interface) */ GArray * mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void load_supported_bands_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { const gchar *response; GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_TELIT (self)); response = mm_base_modem_at_command_finish (self, res, &error); if (!response) g_task_return_error (task, error); else { GArray *bands; MMTelitBNDParseConfig config; mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config); bands = mm_telit_parse_bnd_test_response (response, &config, self, &error); if (!bands) g_task_return_error (task, error); else { /* Store supported bands to be able to build ANY when setting */ priv->supported_bands = g_array_ref (bands); if (priv->ext_4g_bands) mm_obj_dbg (self, "telit modem using extended 4G band setup"); g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); } } g_object_unref (task); } static void load_supported_bands_at (MMIfaceModem *self, GTask *task) { mm_base_modem_at_command (MM_BASE_MODEM (self), "#BND=?", 3, TRUE, (GAsyncReadyCallback) load_supported_bands_ready, task); } static void parent_load_supported_bands_ready (MMIfaceModem *self, GAsyncResult *res, GTask *task) { GArray *bands; GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_TELIT (self)); bands = priv->iface_modem_parent->load_supported_bands_finish (MM_IFACE_MODEM (self), res, &error); if (bands) { g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); g_object_unref (task); } else { mm_obj_dbg (self, "parent load supported bands failure, falling back to AT commands"); load_supported_bands_at (self, task); g_clear_error (&error); } } void mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_TELIT (self)); if (priv->iface_modem_parent && priv->iface_modem_parent->load_supported_bands && priv->iface_modem_parent->load_supported_bands_finish) { priv->iface_modem_parent->load_supported_bands (self, (GAsyncReadyCallback) parent_load_supported_bands_ready, task); } else load_supported_bands_at (self, task); } /*****************************************************************************/ /* Load current bands (Modem interface) */ GArray * mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void load_current_bands_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { const gchar *response; GError *error = NULL; response = mm_base_modem_at_command_finish (self, res, &error); if (!response) g_task_return_error (task, error); else { GArray *bands; MMTelitBNDParseConfig config; mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config); bands = mm_telit_parse_bnd_query_response (response, &config, self, &error); if (!bands) g_task_return_error (task, error); else g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); } g_object_unref (task); } static void load_current_bands_at (MMIfaceModem *self, GTask *task) { mm_base_modem_at_command (MM_BASE_MODEM (self), "#BND?", 3, FALSE, (GAsyncReadyCallback) load_current_bands_ready, task); } static void parent_load_current_bands_ready (MMIfaceModem *self, GAsyncResult *res, GTask *task) { GArray *bands; GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_TELIT (self)); bands = priv->iface_modem_parent->load_current_bands_finish (MM_IFACE_MODEM (self), res, &error); if (bands) { g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); g_object_unref (task); } else { mm_obj_dbg (self, "parent load current bands failure, falling back to AT commands"); load_current_bands_at (self, task); g_clear_error (&error); } } void mm_shared_telit_modem_load_current_bands (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_TELIT (self)); if (priv->iface_modem_parent && priv->iface_modem_parent->load_current_bands && priv->iface_modem_parent->load_current_bands_finish) { priv->iface_modem_parent->load_current_bands (self, (GAsyncReadyCallback) parent_load_current_bands_ready, task); } else load_current_bands_at (self, task); } /*****************************************************************************/ /* Set current bands (Modem interface) */ gboolean mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void set_current_bands_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { GError *error = NULL; mm_base_modem_at_command_finish (self, res, &error); if (error) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } static void set_current_bands_at (MMIfaceModem *self, GTask *task) { GError *error = NULL; gchar *cmd; GArray *bands_array; MMTelitBNDParseConfig config; bands_array = g_task_get_task_data (task); g_assert (bands_array); if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { Private *priv; priv = get_private (MM_SHARED_TELIT (self)); if (!priv->supported_bands) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't build ANY band settings: unknown supported bands"); g_object_unref (task); return; } bands_array = priv->supported_bands; } mm_shared_telit_get_bnd_parse_config (self, &config); cmd = mm_telit_build_bnd_request (bands_array, &config, &error); if (!cmd) { g_task_return_error (task, error); g_object_unref (task); return; } mm_base_modem_at_command (MM_BASE_MODEM (self), cmd, 20, FALSE, (GAsyncReadyCallback)set_current_bands_ready, task); g_free (cmd); } static void parent_set_current_bands_ready (MMIfaceModem *self, GAsyncResult *res, GTask *task) { GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_TELIT (self)); if (priv->iface_modem_parent->set_current_bands_finish (MM_IFACE_MODEM (self), res, &error)) { g_task_return_boolean (task, TRUE); g_object_unref (task); } else { g_clear_error (&error); set_current_bands_at (self, task); } } void mm_shared_telit_modem_set_current_bands (MMIfaceModem *self, GArray *bands_array, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; priv = get_private (MM_SHARED_TELIT (self)); task = g_task_new (self, NULL, callback, user_data); g_task_set_task_data (task, g_array_ref (bands_array), (GDestroyNotify)g_array_unref); if (priv->iface_modem_parent && priv->iface_modem_parent->set_current_bands && priv->iface_modem_parent->set_current_bands_finish) { priv->iface_modem_parent->set_current_bands (self, bands_array, (GAsyncReadyCallback) parent_set_current_bands_ready, task); } else set_current_bands_at (self, task); } /*****************************************************************************/ /* Set current modes (Modem interface) */ gboolean mm_shared_telit_set_current_modes_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void ws46_set_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { GError *error = NULL; mm_base_modem_at_command_finish (self, res, &error); if (error) /* Let the error be critical. */ g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } void mm_shared_telit_set_current_modes (MMIfaceModem *self, MMModemMode allowed, MMModemMode preferred, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; gchar *command; Private *priv; gint ws46_mode = -1; priv = get_private (MM_SHARED_TELIT (self)); task = g_task_new (self, NULL, callback, user_data); if (allowed == MM_MODEM_MODE_ANY && priv->supported_modes) { guint i; allowed = MM_MODEM_MODE_NONE; /* Process list of modes to gather supported ones */ for (i = 0; i < priv->supported_modes->len; i++) { if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_2G) allowed |= MM_MODEM_MODE_2G; if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_3G) allowed |= MM_MODEM_MODE_3G; if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_4G) allowed |= MM_MODEM_MODE_4G; if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_5G) allowed |= MM_MODEM_MODE_5G; } } if (allowed == MM_MODEM_MODE_2G) ws46_mode = 12; else if (allowed == MM_MODEM_MODE_3G) ws46_mode = 22; else if (allowed == MM_MODEM_MODE_4G) ws46_mode = 28; else if (allowed == MM_MODEM_MODE_5G) ws46_mode = 36; else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { if (mm_iface_modem_is_3gpp_lte (self)) ws46_mode = 29; else ws46_mode = 25; } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G)) ws46_mode = 30; else if (allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) ws46_mode = 31; else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) ws46_mode = 25; else if (allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_5G)) ws46_mode = 40; else if (allowed == (MM_MODEM_MODE_4G | MM_MODEM_MODE_5G)) ws46_mode = 37; else if (allowed == (MM_MODEM_MODE_3G |MM_MODEM_MODE_4G | MM_MODEM_MODE_5G)) ws46_mode = 38; /* Telit modems do not support preferred mode selection */ if ((ws46_mode < 0) || (preferred != MM_MODEM_MODE_NONE)) { gchar *allowed_str; gchar *preferred_str; allowed_str = mm_modem_mode_build_string_from_mask (allowed); preferred_str = mm_modem_mode_build_string_from_mask (preferred); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Requested mode (allowed: '%s', preferred: '%s') not " "supported by the modem.", allowed_str, preferred_str); g_free (allowed_str); g_free (preferred_str); g_object_unref (task); return; } command = g_strdup_printf ("AT+WS46=%d", ws46_mode); mm_base_modem_at_command ( MM_BASE_MODEM (self), command, 10, FALSE, (GAsyncReadyCallback) ws46_set_ready, task); g_free (command); } /*****************************************************************************/ /* Revision loading */ gchar * mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void load_revision_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { GError *error; GVariant *result; result = mm_base_modem_at_sequence_finish (self, res, NULL, &error); if (!result) { g_task_return_error (task, error); g_object_unref (task); } else { gchar *revision = NULL; revision = g_variant_dup_string (result, NULL); mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision); g_task_return_pointer (task, revision, g_free); g_object_unref (task); } } /* * parse AT#SWPKGV command * Execution command returns the software package version without #SWPKGV: command echo. * The response is as follows: * * AT#SWPKGV * - * (Usually the same value returned by AT+GMR) * * */ static MMBaseModemAtResponseProcessorResult software_package_version_ready (MMBaseModem *self, gpointer none, const gchar *command, const gchar *response, gboolean last_command, const GError *error, GVariant **result, GError **result_error) { gchar *version = NULL; if (error) { *result = NULL; /* Ignore AT errors (ie, ERROR or CMx ERROR) */ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) { *result_error = g_error_copy (error); return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE; } *result_error = NULL; return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; } version = mm_telit_parse_swpkgv_response (response); if (!version) return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; *result = g_variant_new_take_string (version); return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS; } static const MMBaseModemAtCommand revisions[] = { { "#SWPKGV", 3, TRUE, software_package_version_ready }, { "+CGMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors }, { "+GMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors }, { NULL } }; void mm_shared_telit_modem_load_revision (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_TELIT (self)); mm_obj_dbg (self, "loading revision..."); if (priv->software_package_version) { g_task_return_pointer (task, g_strdup (priv->software_package_version), g_free); g_object_unref (task); return; } mm_base_modem_at_sequence ( MM_BASE_MODEM (self), revisions, NULL, /* response_processor_context */ NULL, /* response_processor_context_free */ (GAsyncReadyCallback) load_revision_ready, task); } /*****************************************************************************/ static void shared_telit_init (gpointer g_iface) { } GType mm_shared_telit_get_type (void) { static GType shared_telit_type = 0; if (!G_UNLIKELY (shared_telit_type)) { static const GTypeInfo info = { sizeof (MMSharedTelit), /* class_size */ shared_telit_init, /* base_init */ NULL, /* base_finalize */ }; shared_telit_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedTelit", &info, 0); g_type_interface_add_prerequisite (shared_telit_type, MM_TYPE_IFACE_MODEM); } return shared_telit_type; }